LCOV - code coverage report
Current view: top level - src/audio/backend - audio_backend_jack.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 96.4 % 111 107
Test Date: 2026-06-01 11:15:25 Functions: 100.0 % 18 18

            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
        

Generated by: LCOV version 2.0-1