Line data Source code
1 : // =============================================================================
2 : // PortAudio backend — lifecycle management
3 : // =============================================================================
4 :
5 : #include "audio/engine/audio_engine.h"
6 : #include "audio/backend/audio_backend.h"
7 : #include "audio/backend/audio_backend_portaudio_helpers.h"
8 : #include "audio/backend/audio_backend_portaudio_internal.h"
9 : #include <portaudio.h>
10 : #ifdef _WIN32
11 : #include <pa_win_wasapi.h>
12 : #endif
13 : #include <cstring>
14 : #include <iostream>
15 : #include <vector>
16 : #include <algorithm>
17 :
18 : namespace Amplitron {
19 :
20 : // PortAudio callback
21 1328 : int pa_audio_callback(const void* input, void* output,
22 : unsigned long frame_count,
23 : const PaStreamCallbackTimeInfo* /*time_info*/,
24 : PaStreamCallbackFlags /*status_flags*/,
25 : void* user_data) {
26 1328 : auto* engine = static_cast<AudioEngine*>(user_data);
27 1328 : const auto* in = static_cast<const float*>(input);
28 1328 : auto* out = static_cast<float*>(output);
29 :
30 1328 : if (!in || !out) {
31 4 : if (out) std::memset(out, 0, frame_count * 2 * sizeof(float));
32 4 : return paContinue;
33 : }
34 :
35 1324 : engine->process_audio(in, out, static_cast<int>(frame_count));
36 1324 : return paContinue;
37 1325 : }
38 :
39 : // Device auto-detection
40 512 : static void auto_detect_devices(int& input_device, int& output_device, int& sample_rate) {
41 512 : int device_count = Pa_GetDeviceCount();
42 512 : int num_apis = Pa_GetHostApiCount();
43 :
44 : // Phase 1: Print all devices
45 512 : std::cout << "\nDetected audio devices:" << std::endl;
46 1269 : for (int i = 0; i < device_count; ++i) {
47 757 : const PaDeviceInfo* info = Pa_GetDeviceInfo(i);
48 757 : if (!info) continue;
49 755 : bool is_usb = is_usb_device_name(info->name);
50 755 : const PaHostApiInfo* api = Pa_GetHostApiInfo(info->hostApi);
51 755 : const char* api_name = api ? api->name : "Unknown";
52 755 : if (info->maxInputChannels > 0) {
53 504 : std::cout << " [IN] " << info->name
54 500 : << " (" << api_name << ")"
55 503 : << (is_usb ? " [USB]" : "") << std::endl;
56 500 : }
57 755 : if (info->maxOutputChannels > 0) {
58 504 : std::cout << " [OUT] " << info->name
59 500 : << " (" << api_name << ")"
60 507 : << (is_usb ? " [USB]" : "") << std::endl;
61 500 : }
62 751 : }
63 :
64 : // Phase 2: For each host API (ranked by priority), find the best
65 : // USB input + non-USB/non-projector output PAIR on the SAME API.
66 3492 : struct ApiCandidate {
67 : int api_index;
68 : int priority;
69 : int usb_input;
70 : int best_output;
71 : std::string api_name;
72 : };
73 :
74 512 : std::vector<ApiCandidate> candidates;
75 1763 : for (int a = 0; a < num_apis; ++a) {
76 1251 : const PaHostApiInfo* api = Pa_GetHostApiInfo(a);
77 1251 : if (!api) continue;
78 :
79 1251 : ApiCandidate c;
80 1251 : c.api_index = a;
81 1251 : c.priority = get_host_api_priority(api->type);
82 1251 : c.usb_input = -1;
83 1251 : c.best_output = -1;
84 1251 : c.api_name = api->name;
85 :
86 2016 : for (int d = 0; d < api->deviceCount; ++d) {
87 765 : int dev_idx = Pa_HostApiDeviceIndexToDeviceIndex(a, d);
88 765 : const PaDeviceInfo* info = Pa_GetDeviceInfo(dev_idx);
89 765 : if (!info) continue;
90 :
91 757 : bool is_usb = is_usb_device_name(info->name);
92 :
93 757 : if (is_usb && info->maxInputChannels > 0 && c.usb_input < 0) {
94 2 : c.usb_input = dev_idx;
95 1 : }
96 1512 : if (!is_usb && !is_projector_or_hdmi(info->name)
97 755 : && info->maxOutputChannels > 0 && c.best_output < 0) {
98 253 : c.best_output = dev_idx;
99 251 : }
100 752 : }
101 :
102 1251 : if (c.best_output < 0) {
103 1004 : for (int d = 0; d < api->deviceCount; ++d) {
104 6 : int dev_idx = Pa_HostApiDeviceIndexToDeviceIndex(a, d);
105 6 : const PaDeviceInfo* info = Pa_GetDeviceInfo(dev_idx);
106 6 : if (!info) continue;
107 0 : if (!is_usb_device_name(info->name) && info->maxOutputChannels > 0) {
108 0 : c.best_output = dev_idx;
109 0 : break;
110 : }
111 0 : }
112 1 : }
113 :
114 1251 : candidates.push_back(c);
115 1251 : }
116 :
117 512 : std::sort(candidates.begin(), candidates.end(),
118 1245 : [](const ApiCandidate& a, const ApiCandidate& b) {
119 1245 : return a.priority > b.priority;
120 : });
121 :
122 : // Phase 3: Pick the best API that has BOTH a USB input AND an output
123 512 : bool found_pair = false;
124 1761 : for (auto& c : candidates) {
125 1251 : if (c.usb_input >= 0 && c.best_output >= 0) {
126 2 : input_device = c.usb_input;
127 2 : output_device = c.best_output;
128 :
129 2 : const PaDeviceInfo* in_info = Pa_GetDeviceInfo(input_device);
130 2 : const PaDeviceInfo* out_info = Pa_GetDeviceInfo(output_device);
131 :
132 3 : std::cout << "\n>> Auto-selected (same API: " << c.api_name << "):" << std::endl;
133 2 : std::cout << " INPUT: " << in_info->name << " [USB Guitar Cable]" << std::endl;
134 2 : std::cout << " OUTPUT: " << out_info->name << " [Speakers]" << std::endl;
135 :
136 2 : if (in_info->defaultSampleRate > 0) {
137 2 : sample_rate = static_cast<int>(in_info->defaultSampleRate);
138 2 : std::cout << " Rate: " << sample_rate << " Hz (device native)" << std::endl;
139 1 : }
140 :
141 1 : found_pair = true;
142 1 : break;
143 : }
144 : }
145 :
146 : // Fallback: no USB input found
147 256 : if (!found_pair) {
148 1508 : for (auto& c : candidates) {
149 1249 : if (c.best_output >= 0) {
150 251 : const PaHostApiInfo* api = Pa_GetHostApiInfo(c.api_index);
151 253 : for (int d = 0; d < api->deviceCount; ++d) {
152 253 : int dev_idx = Pa_HostApiDeviceIndexToDeviceIndex(c.api_index, d);
153 253 : const PaDeviceInfo* info = Pa_GetDeviceInfo(dev_idx);
154 253 : if (info && info->maxInputChannels > 0) {
155 251 : input_device = dev_idx;
156 251 : output_device = c.best_output;
157 :
158 251 : std::cout << "\n>> No USB guitar cable detected." << std::endl;
159 252 : std::cout << " Using " << c.api_name << " defaults:" << std::endl;
160 251 : std::cout << " INPUT: " << info->name << std::endl;
161 1248 : std::cout << " OUTPUT: " << Pa_GetDeviceInfo(output_device)->name << std::endl;
162 :
163 250 : found_pair = true;
164 250 : break;
165 : }
166 1 : }
167 250 : if (found_pair) break;
168 0 : }
169 : }
170 255 : }
171 :
172 : // Last resort: system defaults
173 511 : if (!found_pair) {
174 259 : input_device = Pa_GetDefaultInputDevice();
175 259 : output_device = Pa_GetDefaultOutputDevice();
176 259 : std::cout << "\n>> Using system default input/output devices." << std::endl;
177 5 : }
178 512 : }
179 :
180 514 : bool AudioEngine::initialize() {
181 514 : PaError err = Pa_Initialize();
182 514 : if (err != paNoError) {
183 2 : std::cerr << "PortAudio init failed: " << Pa_GetErrorText(err) << std::endl;
184 2 : return false;
185 : }
186 512 : initialized_ = true;
187 :
188 512 : auto_detect_devices(input_device_, output_device_, sample_rate_);
189 :
190 512 : return true;
191 257 : }
192 :
193 1037 : void AudioEngine::shutdown() {
194 1037 : stop();
195 1037 : if (initialized_) {
196 512 : Pa_Terminate();
197 512 : initialized_ = false;
198 256 : }
199 1037 : }
200 :
201 :
202 :
203 43 : bool AudioEngine::start() {
204 43 : if (!initialized_ || running_) return false;
205 :
206 37 : const PaDeviceInfo* in_dev = Pa_GetDeviceInfo(input_device_);
207 37 : const PaDeviceInfo* out_dev = Pa_GetDeviceInfo(output_device_);
208 17 : (void)in_dev; (void)out_dev;
209 :
210 37 : double desired_latency = static_cast<double>(buffer_size_) / sample_rate_;
211 :
212 17 : PaStreamParameters input_params;
213 37 : input_params.device = input_device_;
214 37 : input_params.channelCount = 1;
215 37 : input_params.sampleFormat = paFloat32;
216 37 : input_params.suggestedLatency = desired_latency;
217 37 : input_params.hostApiSpecificStreamInfo = nullptr;
218 :
219 17 : PaStreamParameters output_params;
220 37 : output_params.device = output_device_;
221 37 : output_params.channelCount = 2;
222 37 : output_params.sampleFormat = paFloat32;
223 37 : output_params.suggestedLatency = desired_latency;
224 37 : output_params.hostApiSpecificStreamInfo = nullptr;
225 :
226 : #ifdef _WIN32
227 17 : PaWasapiStreamInfo wasapi_in_info = {};
228 17 : PaWasapiStreamInfo wasapi_out_info = {};
229 17 : if (in_dev) {
230 11 : const PaHostApiInfo* api = Pa_GetHostApiInfo(in_dev->hostApi);
231 11 : if (api && api->type == paWASAPI) {
232 0 : wasapi_in_info.size = sizeof(PaWasapiStreamInfo);
233 0 : wasapi_in_info.hostApiType = paWASAPI;
234 0 : wasapi_in_info.version = 1;
235 0 : wasapi_in_info.flags = paWinWasapiExclusive;
236 0 : input_params.hostApiSpecificStreamInfo = &wasapi_in_info;
237 :
238 0 : wasapi_out_info.size = sizeof(PaWasapiStreamInfo);
239 0 : wasapi_out_info.hostApiType = paWASAPI;
240 0 : wasapi_out_info.version = 1;
241 0 : wasapi_out_info.flags = paWinWasapiExclusive;
242 0 : output_params.hostApiSpecificStreamInfo = &wasapi_out_info;
243 :
244 0 : std::cout << " Using WASAPI Exclusive Mode" << std::endl;
245 : }
246 : }
247 : #endif
248 :
249 37 : unsigned long frames = static_cast<unsigned long>(buffer_size_);
250 :
251 54 : PaError err = Pa_OpenStream(
252 37 : &backend_->stream,
253 : &input_params,
254 : &output_params,
255 37 : sample_rate_,
256 20 : frames,
257 : paClipOff | paDitherOff,
258 : pa_audio_callback,
259 : this
260 : );
261 :
262 37 : if (err != paNoError) {
263 10 : std::cerr << "Failed to open stream: " << Pa_GetErrorText(err) << std::endl;
264 10 : std::cerr << "Retrying with buffer size " << buffer_size_ << "..." << std::endl;
265 17 : err = Pa_OpenStream(
266 10 : &backend_->stream,
267 : &input_params,
268 : &output_params,
269 10 : sample_rate_,
270 10 : buffer_size_,
271 : paClipOff | paDitherOff,
272 : pa_audio_callback,
273 : this
274 : );
275 10 : if (err != paNoError) {
276 10 : std::cerr << "Failed to open stream: " << Pa_GetErrorText(err) << std::endl;
277 10 : return false;
278 : }
279 0 : }
280 :
281 27 : const PaStreamInfo* si = Pa_GetStreamInfo(backend_->stream);
282 27 : if (si && si->sampleRate > 0.0) {
283 25 : const int actual_rate = static_cast<int>(si->sampleRate + 0.5);
284 25 : if (actual_rate != sample_rate_) {
285 6 : sample_rate_ = actual_rate;
286 6 : update_metronome_timing();
287 6 : std::lock_guard<std::mutex> lock(effect_mutex_);
288 6 : for (const auto& node : main_graph_.get_nodes()) {
289 0 : if (node.pedal) {
290 0 : node.pedal->set_sample_rate(sample_rate_);
291 0 : node.pedal->reset();
292 0 : }
293 : }
294 6 : if (tuner_tap_) {
295 0 : tuner_tap_->set_sample_rate(sample_rate_);
296 0 : tuner_tap_->reset();
297 0 : }
298 6 : }
299 16 : }
300 :
301 27 : err = Pa_StartStream(backend_->stream);
302 27 : if (err != paNoError) {
303 2 : std::cerr << "Failed to start stream: " << Pa_GetErrorText(err) << std::endl;
304 2 : Pa_CloseStream(backend_->stream);
305 2 : backend_->stream = nullptr;
306 2 : return false;
307 : }
308 :
309 25 : running_ = true;
310 25 : si = Pa_GetStreamInfo(backend_->stream);
311 25 : const PaDeviceInfo* in_info = Pa_GetDeviceInfo(input_device_);
312 25 : const PaDeviceInfo* out_info = Pa_GetDeviceInfo(output_device_);
313 25 : std::cout << "Audio stream started:" << std::endl;
314 25 : std::cout << " Input: " << (in_info ? in_info->name : "Unknown") << std::endl;
315 25 : std::cout << " Output: " << (out_info ? out_info->name : "Unknown") << std::endl;
316 34 : std::cout << " Rate: " << (si ? si->sampleRate : sample_rate_) << " Hz" << std::endl;
317 25 : if (si) {
318 25 : std::cout << " Latency: in=" << (si->inputLatency * 1000.0) << " ms"
319 43 : << " out=" << (si->outputLatency * 1000.0) << " ms" << std::endl;
320 16 : }
321 16 : return true;
322 25 : }
323 :
324 1064 : void AudioEngine::stop() {
325 1064 : if (backend_->stream) {
326 25 : if (running_) {
327 25 : Pa_StopStream(backend_->stream);
328 25 : running_ = false;
329 16 : }
330 25 : Pa_CloseStream(backend_->stream);
331 25 : backend_->stream = nullptr;
332 16 : }
333 1064 : }
334 :
335 4 : bool AudioEngine::restart() {
336 4 : stop();
337 4 : bool ok = start();
338 4 : if (!ok) {
339 2 : last_error_ = "Failed to restart audio stream. Check device settings.";
340 4 : std::cerr << "[Amplitron] " << last_error_ << std::endl;
341 0 : } else {
342 2 : last_error_.clear();
343 : }
344 4 : return ok;
345 : }
346 :
347 : } // namespace Amplitron
|