Line data Source code
1 : #include "audio/recorder/recorder.h"
2 : #include "audio/recorder/recorder_impl.h"
3 : #include <iostream>
4 : #include <cmath>
5 : #include <algorithm>
6 :
7 : namespace Amplitron {
8 :
9 78 : bool Recorder::start(const std::string& filepath, int sample_rate, int channels) {
10 78 : if (recording_) return false;
11 :
12 78 : filepath_ = filepath;
13 78 : sample_rate_ = sample_rate;
14 78 : channels_ = channels;
15 78 : samples_written_ = 0;
16 78 : paused_ = false;
17 78 : has_unsaved_ = false;
18 78 : pause_duration_ = 0.0f;
19 :
20 : // Reset waveform buffer
21 40014 : for (auto& v : waveform_buf_) v.store(0.0f);
22 78 : waveform_write_pos_ = 0;
23 78 : current_peak_ = 0.0f;
24 78 : bin_sample_count_ = 0;
25 78 : bin_peak_ = 0.0f;
26 : // ~60 waveform updates per second (at 48kHz, ~800 samples per bin)
27 78 : samples_per_bin_ = std::max(1, sample_rate / (WAVEFORM_SIZE * 2));
28 :
29 : // Initialize ring buffer and pre-allocated PCM conversion buffer
30 78 : ring_buffer_.resize(RING_BUFFER_SIZE, 0.0f);
31 78 : ring_write_pos_ = 0;
32 78 : ring_read_pos_ = 0;
33 78 : pcm_buffer_.resize(4096);
34 :
35 : // Ensure the parent directory exists for the target filepath
36 26 : {
37 78 : std::string parent;
38 78 : size_t sep = filepath.find_last_of("/\\");
39 78 : if (sep != std::string::npos) {
40 75 : parent = filepath.substr(0, sep);
41 75 : mkdirs(parent);
42 25 : }
43 78 : }
44 :
45 78 : file_.open(filepath, std::ios::binary);
46 78 : if (!file_.is_open()) {
47 8 : std::cerr << "Recorder: failed to open " << filepath << std::endl;
48 6 : return false;
49 : }
50 :
51 72 : write_wav_header();
52 72 : recording_ = true;
53 72 : start_time_ = std::chrono::steady_clock::now();
54 :
55 : // Start the disk writer thread (keeps file I/O off the real-time audio thread)
56 72 : writer_running_ = true;
57 72 : writer_thread_ = std::thread(&Recorder::writer_thread_func, this);
58 :
59 96 : std::cout << "Recording started: " << filepath << std::endl;
60 72 : return true;
61 26 : }
62 :
63 75 : void Recorder::stop() {
64 75 : if (!recording_) return;
65 72 : recording_ = false;
66 72 : paused_ = false;
67 :
68 : // Stop writer thread and wait for it to drain remaining buffered data
69 72 : writer_running_ = false;
70 72 : if (writer_thread_.joinable()) {
71 72 : writer_thread_.join();
72 24 : }
73 :
74 72 : finalize_wav_header();
75 72 : file_.close();
76 :
77 72 : has_unsaved_ = true;
78 :
79 72 : float dur = get_duration();
80 96 : std::cout << "Recording stopped: " << samples_written_.load() << " samples ("
81 96 : << dur << "s) saved to " << filepath_ << std::endl;
82 25 : }
83 :
84 9 : void Recorder::pause() {
85 9 : if (!recording_ || paused_) return;
86 9 : paused_ = true;
87 9 : pause_start_ = std::chrono::steady_clock::now();
88 3 : }
89 :
90 9 : void Recorder::resume() {
91 9 : if (!recording_ || !paused_) return;
92 9 : auto now = std::chrono::steady_clock::now();
93 9 : float elapsed = std::chrono::duration<float>(now - pause_start_).count();
94 9 : pause_duration_ += elapsed;
95 9 : paused_ = false;
96 3 : }
97 :
98 12 : bool Recorder::save_to(const std::string& dest_path) {
99 12 : if (!has_unsaved_) return false;
100 9 : if (filepath_ == dest_path) {
101 3 : has_unsaved_ = false;
102 3 : return true;
103 : }
104 : // Copy file to destination
105 6 : std::ifstream src(filepath_, std::ios::binary);
106 6 : std::ofstream dst(dest_path, std::ios::binary);
107 6 : if (!src.is_open() || !dst.is_open()) return false;
108 3 : dst << src.rdbuf();
109 : // Check for errors during copy
110 3 : if (!dst.good() || !src.good()) {
111 0 : std::cerr << "Recorder: failed to copy recording to " << dest_path << std::endl;
112 0 : src.close();
113 0 : dst.close();
114 0 : return false;
115 : }
116 3 : src.close();
117 3 : dst.close();
118 : // Remove temp file
119 3 : std::remove(filepath_.c_str());
120 : // Remove metadata sidecar if exists
121 3 : std::string meta = filepath_;
122 3 : size_t dot = meta.rfind('.');
123 3 : if (dot != std::string::npos) meta = meta.substr(0, dot);
124 3 : meta += ".meta.json";
125 3 : std::remove(meta.c_str());
126 3 : filepath_ = dest_path;
127 3 : has_unsaved_ = false;
128 3 : return true;
129 8 : }
130 :
131 9 : void Recorder::discard() {
132 9 : if (!has_unsaved_ && !recording_) return;
133 9 : if (recording_) stop();
134 9 : std::remove(filepath_.c_str());
135 : // Remove metadata sidecar if exists
136 9 : std::string meta = filepath_;
137 9 : size_t dot = meta.rfind('.');
138 9 : if (dot != std::string::npos) meta = meta.substr(0, dot);
139 9 : meta += ".meta.json";
140 9 : std::remove(meta.c_str());
141 9 : has_unsaved_ = false;
142 9 : filepath_.clear();
143 9 : }
144 :
145 : } // namespace Amplitron
|