Line data Source code
1 : // =============================================================================
2 : // JACK backend — Linux low-latency audio server
3 : // Minimal implementation: registers JACK client, creates in/out ports, and
4 : // provides create_audio_backend()/destroy_audio_backend() factory functions.
5 : // Compiled only when -DWITH_JACK=ON and JACK headers/libs are available.
6 : // =============================================================================
7 :
8 : #include "audio/backend/audio_backend.h"
9 : #include "audio/engine/audio_engine.h"
10 : #include <iostream>
11 : #ifdef WITH_JACK
12 : #include <jack/jack.h>
13 : #endif
14 :
15 : namespace Amplitron
16 : {
17 :
18 : #ifdef WITH_JACK
19 : struct AudioBackendState
20 : {
21 : jack_client_t *client = nullptr;
22 : jack_port_t *in_port = nullptr;
23 : jack_port_t *out_port = nullptr;
24 : bool ports_registered = false;
25 : AudioEngine *engine = nullptr;
26 : };
27 :
28 9 : static bool has_active_client(const AudioBackendState *state)
29 : {
30 9 : return state && state->client;
31 : }
32 :
33 2 : static int jack_process(jack_nframes_t nframes, void *arg)
34 : {
35 2 : auto *state = static_cast<AudioBackendState *>(arg);
36 2 : if (!state || !state->engine)
37 1 : return 0;
38 :
39 1 : if (!state->in_port || !state->out_port)
40 0 : return 0;
41 :
42 1 : float *in = static_cast<float *>(jack_port_get_buffer(state->in_port, nframes));
43 1 : float *out = static_cast<float *>(jack_port_get_buffer(state->out_port, nframes));
44 1 : if (in && out)
45 : {
46 1 : state->engine->process_audio(in, out, static_cast<int>(nframes));
47 : }
48 :
49 1 : return 0;
50 : }
51 :
52 6 : static bool ensure_ports_registered(AudioBackendState *state, AudioEngine *engine)
53 : {
54 6 : if (!state || !state->client || state->ports_registered)
55 1 : return state && state->ports_registered;
56 :
57 5 : state->in_port = jack_port_register(state->client, "in_1", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
58 5 : state->out_port = jack_port_register(state->client, "out_1", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
59 5 : if (!state->in_port || !state->out_port)
60 : {
61 1 : std::cerr << "[Amplitron] JACK: failed to register in/out ports." << std::endl;
62 1 : return false;
63 : }
64 :
65 4 : state->engine = engine;
66 4 : jack_set_process_callback(state->client, jack_process, state);
67 4 : state->ports_registered = true;
68 4 : return true;
69 : }
70 :
71 260 : AudioBackendState *create_audio_backend()
72 : {
73 260 : AudioBackendState *s = new AudioBackendState();
74 260 : jack_status_t status = static_cast<jack_status_t>(0);
75 260 : s->client = jack_client_open("Amplitron", JackNoStartServer, &status);
76 260 : if (!s->client)
77 : {
78 1 : std::cerr << "[Amplitron] JACK: could not open JACK server (is jackd running?)." << std::endl;
79 : // Return a state object without an active client so destroy is safe.
80 1 : return s;
81 : }
82 :
83 259 : return s;
84 : }
85 :
86 : #ifdef AMPLITRON_TESTS
87 1 : AudioBackendState *create_disconnected_audio_backend_for_test()
88 : {
89 1 : return new AudioBackendState();
90 : }
91 :
92 1 : bool jack_backend_has_active_client_for_test(const AudioBackendState *state)
93 : {
94 1 : return has_active_client(state);
95 : }
96 : #endif
97 :
98 262 : void destroy_audio_backend(AudioBackendState *state)
99 : {
100 262 : if (!state)
101 1 : return;
102 261 : if (state->client)
103 : {
104 1 : jack_client_close(state->client);
105 : }
106 261 : delete state;
107 : }
108 :
109 248 : bool AudioEngine::initialize()
110 : {
111 248 : if (initialized_)
112 1 : return true;
113 :
114 247 : if (!backend_)
115 : {
116 0 : backend_ = create_audio_backend();
117 : }
118 :
119 247 : initialized_ = true;
120 247 : last_error_.clear();
121 247 : return true;
122 : }
123 :
124 503 : void AudioEngine::shutdown()
125 : {
126 503 : stop();
127 :
128 503 : auto *state = static_cast<AudioBackendState *>(backend_);
129 503 : if (state && state->client)
130 : {
131 258 : jack_client_close(state->client);
132 258 : state->client = nullptr;
133 258 : state->in_port = nullptr;
134 258 : state->out_port = nullptr;
135 258 : state->ports_registered = false;
136 : }
137 :
138 503 : initialized_ = false;
139 503 : }
140 :
141 9 : bool AudioEngine::start()
142 : {
143 9 : if (!initialized_ || running_)
144 1 : return false;
145 :
146 8 : auto *state = static_cast<AudioBackendState *>(backend_);
147 8 : if (!has_active_client(state))
148 : {
149 2 : last_error_ = "JACK backend is not connected.";
150 2 : return false;
151 : }
152 :
153 6 : if (!ensure_ports_registered(state, this))
154 : {
155 1 : last_error_ = "Failed to initialise JACK ports.";
156 1 : return false;
157 : }
158 5 : if (jack_activate(state->client))
159 : {
160 1 : last_error_ = "Failed to activate JACK client.";
161 1 : return false;
162 : }
163 :
164 4 : running_ = true;
165 4 : last_error_.clear();
166 4 : return true;
167 : }
168 :
169 507 : void AudioEngine::stop()
170 : {
171 507 : auto *state = static_cast<AudioBackendState *>(backend_);
172 507 : if (state && state->client && running_)
173 : {
174 5 : jack_deactivate(state->client);
175 : }
176 507 : running_ = false;
177 507 : }
178 :
179 2 : bool AudioEngine::restart()
180 : {
181 2 : stop();
182 2 : bool ok = start();
183 2 : if (!ok)
184 : {
185 0 : last_error_ = "Failed to restart JACK audio.";
186 0 : std::cerr << "[Amplitron] " << last_error_ << std::endl;
187 : }
188 2 : return ok;
189 : }
190 :
191 18 : std::string AudioEngine::get_input_device_name() const { return "JACK in_1"; }
192 18 : std::string AudioEngine::get_output_device_name() const { return "JACK out_1"; }
193 :
194 3 : std::vector<AudioDeviceInfo> AudioEngine::get_input_devices() const
195 : {
196 12 : return {{0, "JACK in_1", 1, 0, static_cast<double>(sample_rate_), false}};
197 9 : }
198 :
199 3 : std::vector<AudioDeviceInfo> AudioEngine::get_output_devices() const
200 : {
201 12 : return {{0, "JACK out_1", 0, 1, static_cast<double>(sample_rate_), false}};
202 9 : }
203 :
204 3 : bool AudioEngine::set_input_device(int device_index)
205 : {
206 3 : if (device_index != 0)
207 : {
208 1 : last_error_ = "Invalid JACK input device.";
209 1 : return false;
210 : }
211 2 : input_device_ = 0;
212 2 : return true;
213 : }
214 :
215 3 : bool AudioEngine::set_output_device(int device_index)
216 : {
217 3 : if (device_index != 0)
218 : {
219 1 : last_error_ = "Invalid JACK output device.";
220 1 : return false;
221 : }
222 2 : output_device_ = 0;
223 2 : return true;
224 : }
225 : #else
226 : // Fallback stub when built without JACK; should never be compiled in this TU
227 : // unless WITH_JACK is defined in CMake.
228 : AudioBackendState *create_audio_backend() { return nullptr; }
229 : void destroy_audio_backend(AudioBackendState *state) { (void)state; }
230 : #endif
231 :
232 : } // namespace Amplitron
|