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