• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 30544755768

30 Jul 2026 12:57PM UTC coverage: 15.262% (-0.001%) from 15.263%
30544755768

Pull #2163

github

web-flow
Merge a783c93be into 1c4f37393
Pull Request #2163: Fix backwards playback for audio plug-ins (VST, VST3, LV2, JSFX)

0 of 56 new or added lines in 4 files covered. (0.0%)

1 existing line in 1 file now uncovered.

30460 of 199582 relevant lines covered (15.26%)

1075.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/plugins/score-plugin-clap/Clap/Executor.cpp
1
#include "Executor.hpp"
2

3
#include <Process/Dataflow/Port.hpp>
4
#include <Process/ExecutionContext.hpp>
5

6
#include <ossia/audio/audio_parameter.hpp>
7
#include <ossia/dataflow/execution_state.hpp>
8
#include <ossia/dataflow/graph_node.hpp>
9
#include <ossia/dataflow/port.hpp>
10
#include <ossia/detail/hash_map.hpp>
11
#include <ossia/detail/lockfree_queue.hpp>
12
#include <ossia/detail/nullable_variant.hpp>
13

14
#include <QCoreApplication>
15
#include <QDebug>
16
#include <QThread>
17
#include <QTimer>
18

19
#include <clap/ext/state.h>
20
#include <libremidi/detail/conversion.hpp>
21

22
#include <atomic>
23
#include <ranges>
24

25
#if defined(_WIN32)
26
#include <windows.h>
27
#else
28
#include <dlfcn.h>
29
#endif
30

31
namespace Clap
32
{
33
namespace
34
{
35
// Snapshot a plug-in's state via clap.state (returns empty if the plug-in
36
// doesn't implement the extension). [main-thread]
37
inline QByteArray snapshot_clap_state(const clap_plugin_t* plugin)
×
38
{
39
  QByteArray out;
×
40
  if(!plugin)
×
41
    return out;
×
42
  auto state = static_cast<const clap_plugin_state_t*>(
×
43
      plugin->get_extension(plugin, CLAP_EXT_STATE));
×
44
  if(!state || !state->save)
×
45
    return out;
×
46

47
  clap_ostream_t stream{};
×
48
  stream.ctx = &out;
×
49
  stream.write
×
50
      = [](const clap_ostream_t* s, const void* buf, uint64_t sz) -> int64_t {
×
51
    auto* b = static_cast<QByteArray*>(s->ctx);
×
52
    const auto old = b->size();
×
53
    b->resize(old + sz);
×
54
    std::memcpy(b->data() + old, buf, sz);
×
55
    return static_cast<int64_t>(sz);
×
56
  };
57

58
  if(!state->save(plugin, &stream))
×
59
    out.clear();
×
60
  return out;
×
61
}
×
62

63
// Apply a previously-captured snapshot to a fresh plug-in instance.
64
// [main-thread]
65
inline void apply_clap_state(const clap_plugin_t* plugin, const QByteArray& blob)
×
66
{
67
  if(!plugin || blob.isEmpty())
×
68
    return;
×
69
  auto state = static_cast<const clap_plugin_state_t*>(
×
70
      plugin->get_extension(plugin, CLAP_EXT_STATE));
×
71
  if(!state || !state->load)
×
72
    return;
×
73

74
  struct read_ctx
75
  {
76
    const char* data;
77
    qsizetype size;
78
    qsizetype pos;
79
  } ctx{blob.constData(), blob.size(), 0};
×
80

81
  clap_istream_t stream{};
×
82
  stream.ctx = &ctx;
×
83
  stream.read
×
84
      = [](const clap_istream_t* s, void* buf, uint64_t sz) -> int64_t {
×
85
    auto* c = static_cast<read_ctx*>(s->ctx);
×
86
    const auto remaining = c->size - c->pos;
×
87
    if(remaining <= 0)
×
88
      return 0;
×
89
    const auto to_read = std::min<int64_t>(sz, remaining);
×
90
    std::memcpy(buf, c->data + c->pos, to_read);
×
91
    c->pos += to_read;
×
92
    return to_read;
×
93
  };
×
94

95
  state->load(plugin, &stream);
×
96
}
×
97

98
// Per-voice slice; mirrors LV2's voice_control_value in score-plugin-lv2/LV2/Node.hpp.
99
inline std::optional<double>
100
voice_control_value(const ossia::value& val, std::size_t voice_idx) noexcept
×
101
{
102
  return ossia::apply_nonnull(
×
103
      [voice_idx](const auto& v) -> std::optional<double> {
×
104
    using T = std::decay_t<decltype(v)>;
105
    if constexpr(std::is_same_v<T, std::vector<ossia::value>>)
106
    {
107
      if(v.empty())
×
108
        return std::nullopt;
×
109
      const auto& elem = (voice_idx < v.size()) ? v[voice_idx] : v.back();
×
110
      return ossia::convert<double>(elem);
×
111
    }
112
    else if constexpr(std::is_same_v<T, ossia::vec2f>)
113
      return v[std::min<std::size_t>(voice_idx, 1)];
×
114
    else if constexpr(std::is_same_v<T, ossia::vec3f>)
115
      return v[std::min<std::size_t>(voice_idx, 2)];
×
116
    else if constexpr(std::is_same_v<T, ossia::vec4f>)
117
      return v[std::min<std::size_t>(voice_idx, 3)];
×
118
    else if constexpr(
119
        std::is_same_v<T, float> || std::is_same_v<T, double>
120
        || std::is_same_v<T, int> || std::is_same_v<T, bool>)
121
      return double(v);
×
122
    else
123
      return std::nullopt;
×
124
  },
×
125
      val.v);
×
126
}
127

128
inline bool is_vector_value(const ossia::value& val) noexcept
×
129
{
130
  return ossia::apply_nonnull([](const auto& v) noexcept -> bool {
×
131
    using T = std::decay_t<decltype(v)>;
132
    return std::is_same_v<T, std::vector<ossia::value>>
×
133
           || std::is_same_v<T, ossia::vec2f>
134
           || std::is_same_v<T, ossia::vec3f>
135
           || std::is_same_v<T, ossia::vec4f>;
136
  }, val.v);
×
137
}
138
}
139

140
static auto dummy_audio_buffer() noexcept
×
141
{
142
  clap_audio_buffer_t buffer{};
×
143
  buffer.data32 = nullptr;
×
144
  buffer.data64 = nullptr;
×
145
  buffer.channel_count = 0;
×
146
  buffer.latency = 0;
×
147
  buffer.constant_mask = 0;
×
148
  return buffer;
×
149
}
150

151
struct event_storage
152
{
153
  std::vector<clap_event_midi_t> midi_events;
154
  std::vector<clap_event_midi2_t> midi2_events;
155
  std::vector<clap_event_note_t> note_events;
156
  std::vector<clap_event_param_value_t> param_events;
157
  // Sysex events captured from plug-in output. The CLAP spec says
158
  // clap_event_midi_sysex_t.buffer is only valid for the duration of
159
  // try_push, so we copy each payload into sysex_data and rewrite the
160
  // event to point at our copy.
161
  std::vector<clap_event_midi_sysex_t> sysex_events;
162
  std::vector<std::vector<uint8_t>> sysex_data;
163
  std::vector<clap_event_header_t*> all_events;
164

165
  void clear()
×
166
  {
167
    midi_events.clear();
×
168
    midi2_events.clear();
×
169
    note_events.clear();
×
170
    param_events.clear();
×
171
    sysex_events.clear();
×
172
    sysex_data.clear();
×
173
    all_events.clear();
×
174
  }
×
175
};
176

177
class clap_node_base : public ossia::graph_node
178
{
179
public:
180
  std::shared_ptr<Clap::PluginHandle> handle;
181
  explicit clap_node_base(const Clap::Model& proc)
×
182
      : handle{proc.handle()}
×
183
      , m_param_ins{handle->m_parameters_ins}
×
184
      , m_param_outs{handle->m_parameters_outs}
×
185
      , m_midi_ins{handle->m_midi_ins}
×
186
      , m_midi_outs{handle->m_midi_outs}
×
187
      , m_param_out_index{handle->param_outs_by_id}
×
188
  {
×
189
    set_not_fp_safe();
×
190
    midi_ins.reserve(m_midi_ins.size());
×
191
    midi_outs.reserve(m_midi_outs.size());
×
192
    parameter_ins.reserve(m_param_ins.size());
×
193
    parameter_outs.reserve(m_param_outs.size());
×
194
    m_input_events.all_events.reserve(4096);
×
195

196
    // Create ports based on the model
197
    for(auto inlet : proc.inlets())
×
198
    {
199
      if(qobject_cast<Process::AudioInlet*>(inlet))
×
200
      {
201
        audio_ins.push_back(new ossia::audio_inlet);
×
202
        m_inlets.push_back(audio_ins.back());
×
203
      }
×
204
      else if(qobject_cast<Process::MidiInlet*>(inlet))
×
205
      {
206
        midi_ins.push_back(new ossia::midi_inlet);
×
207
        m_inlets.push_back(midi_ins.back());
×
208
      }
×
209
      else if(auto inl = qobject_cast<Process::ControlInlet*>(inlet))
×
210
      {
211
        parameter_ins.push_back(new ossia::value_inlet);
×
212
        m_inlets.push_back(parameter_ins.back());
×
213
        parameter_ins.back()->data.write_value(inl->value(), 0);
×
214
      }
×
215
    }
216

217
    for(auto outlet : proc.outlets())
×
218
    {
219
      if(qobject_cast<Process::AudioOutlet*>(outlet))
×
220
      {
221
        audio_outs.push_back(new ossia::audio_outlet);
×
222
        m_outlets.push_back(audio_outs.back());
×
223
      }
×
224
      else if(qobject_cast<Process::MidiOutlet*>(outlet))
×
225
      {
226
        midi_outs.push_back(new ossia::midi_outlet);
×
227
        m_outlets.push_back(midi_outs.back());
×
228
      }
×
229
      else if(qobject_cast<Process::ControlOutlet*>(outlet))
×
230
      {
231
        parameter_outs.push_back(new ossia::value_outlet);
×
232
        m_outlets.push_back(parameter_outs.back());
×
233
      }
×
234
    }
235

236
    SCORE_ASSERT(parameter_ins.size() == m_param_ins.size());
×
237
    SCORE_ASSERT(parameter_outs.size() == m_param_outs.size());
×
238
    SCORE_ASSERT(audio_ins.size() == proc.audioInputs().size());
×
239
    SCORE_ASSERT(audio_outs.size() == proc.audioOutputs().size());
×
240
    SCORE_ASSERT(midi_ins.size() == proc.midiInputs().size());
×
241
    SCORE_ASSERT(midi_outs.size() == proc.midiOutputs().size());
×
242
  }
×
243

244
  std::string label() const noexcept override
×
245
  { //FIXME
246
    return "clap";
×
247
  }
248

249
  [[nodiscard]] bool activate_plugin(
×
250
      const clap_plugin_t* plugin, double sample_rate, uint32_t max_buffer_size)
251
  {
252
    if(!plugin)
×
253
      return false;
×
254

255
    m_sample_rate = sample_rate;
×
256
    m_buffer_size = max_buffer_size;
×
257

258
    if(plugin->activate(plugin, sample_rate, 1, max_buffer_size))
×
259
    {
260
      return true;
×
261
    }
262
    return false;
×
263
  }
×
264

265
  [[nodiscard]] bool start_plugin(const clap_plugin_t* plugin)
×
266
  {
267
    // CLAP spec (plugin.h): reset() "clears all buffers, performs a full
268
    // reset of the processing state (filters, oscillators, envelopes,
269
    // lfo, ...) and kills all voices" and is the right call when
270
    // clap_process.steady_time may jump backward — exactly what happens
271
    // when score (re)starts a transport that has been idle. It's a cheap
272
    // mandatory entry point, and gets rid of residual reverb tails /
273
    // filter ringing between plays.
274
    plugin->reset(plugin);
×
275

276
    if(plugin->start_processing(plugin))
×
277
    {
278
      init_parameter_values(plugin);
×
279
      return true;
×
280
    }
281
    return false;
×
282
  }
×
283

284
  void stop_plugin(const clap_plugin_t* plugin) { plugin->stop_processing(plugin); }
×
285

286
  void init_parameter_values(const clap_plugin_t* plugin)
×
287
  {
288
    if(!plugin || m_param_ins.empty())
×
289
      return;
×
290

291
    // Push initial parameter values via clap.params.flush, not process().
292
    // process() requires the plugin's declared audio port layout (a plugin may
293
    // assert on it, e.g. Floe), and the spec reserves process() for actual
294
    // audio rendering; flush() exists precisely to set parameters with no
295
    // audio. We're on the audio thread, active and not concurrently
296
    // processing, which is a legal context for flush.
297
    auto params = static_cast<const clap_plugin_params_t*>(
×
298
        plugin->get_extension(plugin, CLAP_EXT_PARAMS));
×
299
    if(!params || !params->flush)
×
300
      return;
×
301

302
    event_storage in;
×
303
    in.param_events.reserve(m_param_ins.size());
×
304
    in.all_events.reserve(m_param_ins.size());
×
305
    for(std::size_t i = 0; i < m_param_ins.size(); ++i)
×
306
    {
307
      const auto& param_info = m_param_ins[i];
×
308
      clap_event_param_value_t ev{};
×
309
      ev.header.size = sizeof(clap_event_param_value_t);
×
310
      ev.header.time = 0;
×
311
      ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
×
312
      ev.header.type = CLAP_EVENT_PARAM_VALUE;
×
313
      ev.header.flags = 0;
×
314
      ev.param_id = param_info.id;
×
315
      ev.cookie = param_info.cookie;
×
316
      ev.note_id = -1;
×
317
      ev.port_index = -1;
×
318
      ev.channel = -1;
×
319
      ev.key = -1;
×
320
      const auto& v = parameter_ins[i]->data.get_data();
×
321
      ev.value = v.empty() ? param_info.default_value
×
322
                           : ossia::convert<float>(v.back().value);
×
323
      in.param_events.push_back(ev);
×
324
    }
×
325
    for(auto& ev : in.param_events)
×
326
      in.all_events.push_back(reinterpret_cast<clap_event_header_t*>(&ev));
×
327

328
    clap_input_events evs{
×
329
        .ctx = &in,
330
        .size = +[](const clap_input_events* list) -> uint32_t {
×
331
      return static_cast<event_storage*>(list->ctx)->all_events.size();
×
332
    },
333
        .get = +[](const clap_input_events* list,
×
334
                   uint32_t index) -> const clap_event_header_t* {
335
      auto* storage = static_cast<event_storage*>(list->ctx);
×
336
      return index < storage->all_events.size() ? storage->all_events[index]
×
337
                                                : nullptr;
338
    }};
339
    clap_output_events_t o_evs{
×
340
        .ctx = nullptr,
341
        .try_push = [](const struct clap_output_events*,
×
342
                       const clap_event_header_t*) -> bool { return false; }};
×
343

344
    params->flush(plugin, &evs, &o_evs);
×
345
  }
×
346

347
  [[nodiscard]]
348
  bool deactivate_plugin(const clap_plugin_t* plugin)
×
349
  {
350
    if(!plugin)
×
351
      return false;
×
352

353
    plugin->deactivate(plugin);
×
354
    return true;
×
355
  }
×
356

357
  auto make_transport(const ossia::token_request& tk, ossia::exec_state_facade st)
×
358
  {
359
    const double song_pos_beats = tk.musical_start_position;
×
360
    const double song_pos_seconds = tk.prev_date.impl * st.samplesToModel();
×
361
    uint32_t transport_flags
×
362
        = CLAP_TRANSPORT_HAS_TEMPO | CLAP_TRANSPORT_HAS_BEATS_TIMELINE
363
          | CLAP_TRANSPORT_HAS_SECONDS_TIMELINE | CLAP_TRANSPORT_HAS_TIME_SIGNATURE;
364
    if(tk.prev_date != tk.date)
×
365
      transport_flags |= CLAP_TRANSPORT_IS_PLAYING;
×
366

367
    // Bar information
368
    const double bar_start = tk.musical_start_last_bar;
×
369
    const int32_t bar_number = static_cast<int32_t>(
×
370
        tk.musical_start_last_bar / (4.0 * tk.signature.upper / tk.signature.lower));
×
371

372
    clap_event_transport_t transport{
×
373
        .header = {
×
374
            .size = sizeof(clap_event_transport_t),
375
            .time = 0,
376
            .space_id = CLAP_CORE_EVENT_SPACE_ID,
377
            .type = CLAP_EVENT_TRANSPORT,
378
            .flags = 0,
379
        },
380
        .flags = transport_flags,
×
381
        .song_pos_beats = (clap_beattime)std::floor(song_pos_beats),
×
382
        .song_pos_seconds = (clap_sectime)std::floor(song_pos_seconds),
×
383
        .tempo = tk.tempo,
×
384
        .tempo_inc = 0.0, // FIXME
385
        .loop_start_beats = 0,
386
        .loop_end_beats = 0,
387
        .loop_start_seconds = 0,
388
        .loop_end_seconds = 0,
389
        .bar_start = (clap_beattime) std::floor(bar_start),
×
390
        .bar_number = bar_number,
×
391
        .tsig_num = static_cast<uint16_t>(tk.signature.upper),
×
392
        .tsig_denom = static_cast<uint16_t>(tk.signature.lower)
×
393
    };
394

395
    return transport;
×
396
  }
397
  void process_controls(uint32_t samples)
×
398
  {
399
    // Process control inlets and create parameter events. Each ossia
400
    // timed_value carries its own frame offset within the block, so emit
401
    // one CLAP_EVENT_PARAM_VALUE per timestamp instead of collapsing them
402
    // all to frame 0 — that lets sample-accurate automation (LFO, curve,
403
    // MIDI mapping) drive plug-in smoothers correctly.
404
    for(std::size_t i = 0; i < parameter_ins.size(); ++i)
×
405
    {
406
      const auto& data = parameter_ins[i]->data.get_data();
×
407
      if(data.empty())
×
408
        continue;
×
409
      const auto& param_info = m_param_ins[i];
×
410
      for(const auto& tv : data)
×
411
      {
412
        // Vector values are addressed per-voice by prepare_voice_overrides.
413
        if(is_vector_value(tv.value))
×
414
          continue;
×
415
        double value = std::clamp(
×
416
            ossia::convert<double>(tv.value), param_info.min_value,
×
417
            param_info.max_value);
×
418

419
        // Map ossia's signed frame offset onto CLAP's unsigned uint32 time
420
        // field. Port timestamps are relative to the audio buffer while the
421
        // plug-in only sees [m_tick_start; m_tick_start + samples[ of it, so
422
        // rebase first; anything outside still clamps into the block.
NEW
423
        const int64_t t = tv.timestamp - m_tick_start;
×
424
        const uint32_t time
×
425
            = t <= 0 ? 0u
×
426
                     : static_cast<uint32_t>(
427
                           std::min<int64_t>(t, samples > 0 ? samples - 1 : 0));
×
428

429
        clap_event_param_value_t param_event{};
×
430
        param_event.header.size = sizeof(clap_event_param_value_t);
×
431
        param_event.header.time = time;
×
432
        param_event.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
×
433
        param_event.header.type = CLAP_EVENT_PARAM_VALUE;
×
434
        param_event.header.flags = CLAP_EVENT_IS_LIVE;
×
435
        param_event.param_id = param_info.id;
×
436
        param_event.cookie = param_info.cookie;
×
437
        param_event.note_id = -1;
×
438
        param_event.port_index = -1;
×
439
        param_event.channel = -1;
×
440
        param_event.key = -1;
×
441
        param_event.value = value;
×
442

443
        m_input_events.param_events.push_back(param_event);
×
444
        m_input_events.all_events.push_back(
×
445
            reinterpret_cast<clap_event_header_t*>(
×
446
                &m_input_events.param_events.back()));
×
447
      }
448
    }
×
449
  }
×
450

451
  void process_midi()
×
452
  {
NEW
453
    const auto stamp = [this](int64_t ts) -> uint32_t {
×
NEW
454
      return uint32_t(std::clamp<int64_t>(
×
NEW
455
          ts - m_tick_start, 0, m_tick_frames > 0 ? m_tick_frames - 1 : 0));
×
456
    };
457
    uint16_t midi_port_index = 0;
×
458
    for(ossia::midi_inlet* midi_in : midi_ins)
×
459
    {
460
      const auto& spec = m_midi_ins[midi_port_index];
×
461
      const auto& msgs = midi_in->data.messages;
×
462

463
      if(spec.supported_dialects & clap_note_dialect::CLAP_NOTE_DIALECT_MIDI2)
×
464
      {
465
        for(const auto& m : msgs)
×
466
        {
467
          clap_event_midi2_t ev{};
×
468
          ev.header.size = sizeof(clap_event_midi2_t);
×
NEW
469
          ev.header.time = stamp(m.timestamp);
×
470
          ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
×
471
          ev.header.type = CLAP_EVENT_MIDI2;
×
472
          ev.header.flags = 0;
×
473
          ev.port_index = midi_port_index;
×
474
          ev.data[0] = m.data[0];
×
475
          ev.data[1] = m.data[1];
×
476
          ev.data[2] = m.data[2];
×
477
          ev.data[3] = m.data[3];
×
478

479
          m_input_events.midi2_events.push_back(ev);
×
480
          m_input_events.all_events.push_back(
×
481
              reinterpret_cast<clap_event_header_t*>(
×
482
                  &m_input_events.midi2_events.back()));
×
483
        }
484
      }
×
485
      else if(spec.supported_dialects & clap_note_dialect::CLAP_NOTE_DIALECT_CLAP)
×
486
      {
487
        for(const auto& m : msgs)
×
488
        {
489
          if(m.get_type() != libremidi::midi2::message_type::MIDI_2_CHANNEL)
×
490
            continue;
×
491
          clap_event_note_t ev{};
×
492
          ev.header.size = sizeof(clap_event_note_t);
×
NEW
493
          ev.header.time = stamp(m.timestamp);
×
494
          ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
×
495
          ev.header.flags = 0;
×
496
          ev.port_index = midi_port_index;
×
497
          ev.note_id = -1;
×
498
          switch(libremidi::message_type(m.get_status_code()))
×
499
          {
500
            case libremidi::message_type::NOTE_ON: {
501
              auto [channel, note, value] = libremidi::as_01::note_off(m);
×
502
              if(value > 0)
×
503
              {
504
                ev.header.type = CLAP_EVENT_NOTE_ON;
×
505
                ev.channel = channel;
×
506
                ev.key = note;
×
507
                ev.velocity = value;
×
508
              }
×
509
              else
510
              {
511
                ev.header.type = CLAP_EVENT_NOTE_OFF;
×
512
                ev.channel = channel;
×
513
                ev.key = note;
×
514
                ev.velocity = 0.0;
×
515
              }
516
              m_input_events.note_events.push_back(ev);
×
517
              m_input_events.all_events.push_back(
×
518
                  reinterpret_cast<clap_event_header_t*>(
×
519
                      &m_input_events.note_events.back()));
×
520
              break;
×
521
            }
522
            case libremidi::message_type::NOTE_OFF: {
523
              auto [channel, note, value] = libremidi::as_01::note_off(m);
×
524
              ev.header.type = CLAP_EVENT_NOTE_OFF;
×
525
              ev.channel = channel;
×
526
              ev.key = note;
×
527
              ev.velocity = value;
×
528
              m_input_events.note_events.push_back(ev);
×
529
              m_input_events.all_events.push_back(
×
530
                  reinterpret_cast<clap_event_header_t*>(
×
531
                      &m_input_events.note_events.back()));
×
532
              break;
×
533
            }
534
            default:
535
              break;
×
536
          }
537
        }
538
      }
×
539
      else if(spec.supported_dialects & clap_note_dialect::CLAP_NOTE_DIALECT_MIDI)
×
540
      {
541
        for(const auto& m : msgs)
×
542
        {
543
          // Convert UMP to MIDI 1.0
544
          uint8_t midi_bytes[16];
545
          auto bytes_written = cmidi2_convert_single_ump_to_midi1(
×
546
              midi_bytes, sizeof(midi_bytes), (cmidi2_ump*)m.data);
×
547

548
          if(bytes_written > 0 && bytes_written <= 3)
×
549
          {
550
            clap_event_midi_t ev{};
×
551
            ev.header.size = sizeof(clap_event_midi_t);
×
NEW
552
            ev.header.time = stamp(m.timestamp);
×
553
            ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
×
554
            ev.header.type = CLAP_EVENT_MIDI;
×
555
            ev.header.flags = 0;
×
556
            ev.port_index = midi_port_index;
×
557
            std::memcpy(ev.data, midi_bytes, bytes_written);
×
558

559
            m_input_events.midi_events.push_back(ev);
×
560
            m_input_events.all_events.push_back(
×
561
                reinterpret_cast<clap_event_header_t*>(
×
562
                    &m_input_events.midi_events.back()));
×
563
          }
×
564
        }
565
      }
×
566
      // FIXME sysex
567
      midi_port_index++;
×
568
    }
569
  }
×
570

NEW
571
  void prepare_input_events(int64_t offset, int samples)
×
572
  {
NEW
573
    m_tick_start = offset;
×
NEW
574
    m_tick_frames = samples;
×
575
    m_input_events.clear();
×
576
    m_output_events.clear();
×
577

578
    int param_event_count = 0;
×
579
    int midi_event_count = 0;
×
580
    for(auto port : this->parameter_ins)
×
581
      param_event_count += port->data.get_data().size();
×
582
    for(auto port : this->midi_ins)
×
583
      midi_event_count += port->data.messages.size();
×
584
    m_input_events.midi_events.reserve(midi_event_count * 1.1);
×
585
    m_input_events.midi2_events.reserve(midi_event_count * 1.1);
×
586
    m_input_events.note_events.reserve(midi_event_count * 1.1);
×
587
    m_input_events.param_events.reserve(
×
588
        param_event_count * 1.1
×
589
        + 2
×
590
              * this->parameter_ins
×
591
                    .size()); // Important to reserve one additional buffer of parameters for the mono case
×
592
    m_input_events.all_events.reserve((param_event_count + midi_event_count + 1) * 1.1);
×
593

594
    if(m_pending_all_notes_off.exchange(false, std::memory_order_acq_rel))
×
595
      inject_all_notes_off();
×
596

597
    // Process parameter changes
598
    process_controls(samples);
×
599
    process_midi();
×
600

601
    // Events need to be sorted
602
    std::sort(
×
603
        m_input_events.all_events.begin(), m_input_events.all_events.end(),
×
604
        [](const clap_event_header_t* a, const clap_event_header_t* b) {
×
605
      return a->time < b->time;
×
606
    });
607
  }
×
608

609
  // NOTE_CHOKE wildcard + CC#120/123 on all 16 channels covers every dialect.
610
  void inject_all_notes_off()
×
611
  {
612
    {
613
      clap_event_note_t ev{};
×
614
      ev.header.size = sizeof(clap_event_note_t);
×
615
      ev.header.time = 0;
×
616
      ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
×
617
      ev.header.type = CLAP_EVENT_NOTE_CHOKE;
×
618
      ev.header.flags = 0;
×
619
      ev.note_id = -1;
×
620
      ev.port_index = -1;
×
621
      ev.channel = -1;
×
622
      ev.key = -1;
×
623
      ev.velocity = 0.0;
×
624
      m_input_events.note_events.push_back(ev);
×
625
      m_input_events.all_events.push_back(
×
626
          reinterpret_cast<clap_event_header_t*>(&m_input_events.note_events.back()));
×
627
    }
628
    for(uint8_t ch = 0; ch < 16; ++ch)
×
629
    {
630
      for(uint8_t cc : {uint8_t(120), uint8_t(123)})
×
631
      {
632
        clap_event_midi_t ev{};
×
633
        ev.header.size = sizeof(clap_event_midi_t);
×
634
        ev.header.time = 0;
×
635
        ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
×
636
        ev.header.type = CLAP_EVENT_MIDI;
×
637
        ev.header.flags = 0;
×
638
        ev.port_index = 0;
×
639
        ev.data[0] = uint8_t(0xB0 | ch);
×
640
        ev.data[1] = cc;
×
641
        ev.data[2] = 0;
×
642
        m_input_events.midi_events.push_back(ev);
×
643
        m_input_events.all_events.push_back(
×
644
            reinterpret_cast<clap_event_header_t*>(&m_input_events.midi_events.back()));
×
645
      }
646
    }
×
647
  }
×
648

649
  void all_notes_off() noexcept override
×
650
  {
651
    m_pending_all_notes_off.store(true, std::memory_order_release);
×
652
  }
×
653

654
  // Forward any CLAP_EVENT_PARAM_VALUE events the plugin emitted during
655
  // process() to the matching `parameter_outs` value_outlet so downstream
656
  // ossia nodes (and the GUI bargraphs) see live values.
657
  void dispatch_param_outputs()
×
658
  {
659
    if(m_output_events.param_events.empty())
×
660
      return;
×
661

662
    for(const auto& ev : m_output_events.param_events)
×
663
    {
664
      auto it = m_param_out_index.find(ev.param_id);
×
665
      if(it == m_param_out_index.end())
×
666
        continue;
×
667
      const auto idx = it->second;
×
668
      if(idx < parameter_outs.size())
×
669
        parameter_outs[idx]->data.write_value(
×
670
            ossia::value{ev.value}, static_cast<int64_t>(ev.header.time));
×
671
    }
672
  }
×
673

674
  ossia::small_vector<ossia::audio_inlet*, 2> audio_ins;
675
  ossia::small_vector<ossia::audio_outlet*, 2> audio_outs;
676
  ossia::small_vector<ossia::midi_inlet*, 2> midi_ins;
677
  ossia::small_vector<ossia::midi_outlet*, 2> midi_outs;
678
  std::vector<ossia::value_inlet*> parameter_ins;
679
  std::vector<ossia::value_outlet*> parameter_outs;
680
  clap_event_transport_t m_current_transport{};
×
681

682
  // Buffer span this tick was given, as reported by exec_state_facade::timings().
NEW
683
  int64_t m_tick_start{};
×
NEW
684
  int64_t m_tick_frames{};
×
685

686
  event_storage m_input_events;
687
  event_storage m_output_events;
688

689
  const std::vector<clap_param_info_t>& m_param_ins;
690
  const std::vector<clap_param_info_t>& m_param_outs;
691
  const std::vector<clap_note_port_info_t>& m_midi_ins;
692
  const std::vector<clap_note_port_info_t>& m_midi_outs;
693
  const ossia::flat_map<clap_id, std::uint32_t>& m_param_out_index;
694
  double m_sample_rate{44100.0};
×
695
  uint32_t m_buffer_size{512};
×
696

697
  // Last clap_process_status returned by the plugin. CLAP_PROCESS_SLEEP
698
  // tells us "no more processing needed until next event or audio input
699
  // variation" — we use this to skip plugin->process on instruments that
700
  // are idle (audio_ins empty + no events). For effects (audio_ins
701
  // non-empty) we always process: detecting "audio input variation"
702
  // accurately would require a per-frame scan we don't want to pay for.
703
  clap_process_status m_last_status{CLAP_PROCESS_CONTINUE};
×
704

705
  std::atomic<bool> m_pending_all_notes_off{false};
×
706
};
707

708
// Normal implementation
709
class clap_node : public clap_node_base
710
{
711
public:
712
  std::vector<double> dummy_io_buffer;
713
  clap_node(const Clap::Model& proc, Clap::PluginHandle& handle, int sampleRate, int bs)
×
714
      : clap_node_base{proc}
×
715
      , m_instance{handle}
×
716
  {
×
717
    m_expected_audio_inputs = proc.audioInputs();
×
718
    m_expected_audio_outputs = proc.audioOutputs();
×
719

720
    if(handle.activated)
×
721
      (void)deactivate_plugin(handle.plugin);
×
722
    m_activated = activate_plugin(m_instance.plugin, sampleRate, bs);
×
723
    handle.activated = m_activated;
×
724
    dummy_io_buffer.resize(2 * bs);
×
725
  }
×
726

727
  ~clap_node()
×
728
  {
×
729
    // We do not deactivate in the audio thread where the dtor of clap_node runs
730
    // but later in the main thread (driven by Model::resetExecution / ~Model
731
    // through the synced handle.activated flag set in the constructor).
732
  }
×
733

734
  void do_process(
×
735
      clap_process_t& process, event_storage& input_storage,
736
      event_storage& output_storage)
737
  {
738
    // Process audio
739
    process.steady_time = -1;
×
740
    process.transport = &m_current_transport;
×
741

742
    // Setup input events
743
    clap_input_events evs{
×
744
        .ctx = &input_storage,
×
745
        .size = +[](const clap_input_events* list) -> uint32_t {
×
746
      auto* storage = static_cast<event_storage*>(list->ctx);
×
747
      return storage->all_events.size();
×
748
    },
749
        .get = +[](const clap_input_events* list,
×
750
                   uint32_t index) -> const clap_event_header_t* {
751
      auto* storage = static_cast<event_storage*>(list->ctx);
×
752
      if(index < storage->all_events.size())
×
753
        return storage->all_events[index];
×
754
      return nullptr;
×
755
    }};
×
756

757
    // Setup output events
758
    clap_output_events_t o_evs{
×
759
        .ctx = &output_storage,
×
760
        .try_push = [](const struct clap_output_events* list,
×
761
                       const clap_event_header_t* event) -> bool {
762
      auto* storage = static_cast<event_storage*>(list->ctx);
×
763
      if(!event || event->space_id != CLAP_CORE_EVENT_SPACE_ID)
×
764
        return false;
×
765
      switch(event->type)
×
766
      {
767
        case CLAP_EVENT_MIDI:
768
          if(event->size >= sizeof(clap_event_midi_t))
×
769
          {
770
            storage->midi_events.push_back(
×
771
                *reinterpret_cast<const clap_event_midi_t*>(event));
×
772
            return true;
×
773
          }
774
          return false;
×
775
        case CLAP_EVENT_PARAM_VALUE:
776
          if(event->size >= sizeof(clap_event_param_value_t))
×
777
          {
778
            storage->param_events.push_back(
×
779
                *reinterpret_cast<const clap_event_param_value_t*>(event));
×
780
            return true;
×
781
          }
782
          return false;
×
783
        case CLAP_EVENT_NOTE_ON:
784
        case CLAP_EVENT_NOTE_OFF:
785
        case CLAP_EVENT_NOTE_END:
786
        case CLAP_EVENT_NOTE_CHOKE:
787
          // Plug-in voice events: useful both as plain MIDI fanout to a
788
          // downstream MidiOutlet (we convert later) and, for NOTE_END
789
          // specifically, so polyphonic hosts can release a voice slot.
790
          if(event->size >= sizeof(clap_event_note_t))
×
791
          {
792
            storage->note_events.push_back(
×
793
                *reinterpret_cast<const clap_event_note_t*>(event));
×
794
            return true;
×
795
          }
796
          return false;
×
797
        case CLAP_EVENT_MIDI2:
798
          // MIDI 2.0 / UMP packets — forward 1:1 to libremidi::ump in
799
          // the post-process step below.
800
          if(event->size >= sizeof(clap_event_midi2_t))
×
801
          {
802
            storage->midi2_events.push_back(
×
803
                *reinterpret_cast<const clap_event_midi2_t*>(event));
×
804
            return true;
×
805
          }
806
          return false;
×
807
        case CLAP_EVENT_MIDI_SYSEX:
808
          // The buffer pointer is only valid for the duration of this
809
          // try_push call (clap/events.h), so copy the bytes into our
810
          // own storage and rewrite the event to point there.
811
          if(event->size >= sizeof(clap_event_midi_sysex_t))
×
812
          {
813
            auto* sx
×
814
                = reinterpret_cast<const clap_event_midi_sysex_t*>(event);
×
815
            if(sx->size == 0 || sx->buffer == nullptr)
×
816
              return false;
×
817
            storage->sysex_data.emplace_back(
×
818
                sx->buffer, sx->buffer + sx->size);
×
819
            clap_event_midi_sysex_t copy = *sx;
×
820
            copy.buffer = storage->sysex_data.back().data();
×
821
            storage->sysex_events.push_back(copy);
×
822
            return true;
×
823
          }
824
          return false;
×
825
        default:
826
          return false;
×
827
      }
828
    }};
×
829

830
    process.in_events = &evs;
×
831
    process.out_events = &o_evs;
×
832

833
    m_last_status = m_instance.plugin->process(m_instance.plugin, &process);
×
834

835
    dispatch_param_outputs();
×
836

837
    // Process MIDI output
838
    forward_midi_outputs();
×
839
  }
×
840

841
  // Fan plug-in MIDI / note / sysex output events out to score's MIDI
842
  // outlets, converting to UMP packets. Shared between clap_node and
843
  // clap_node_mono so they don't drift.
844
  void forward_midi_outputs()
×
845
  {
846
    std::size_t midi_port_index = 0;
×
847
    for(std::size_t i = 0; i < m_outlets.size(); ++i)
×
848
    {
849
      if(auto midi_out = m_outlets[i]->target<ossia::midi_port>())
×
850
      {
851
        auto& port_messages = midi_out->messages;
×
852
        port_messages.clear();
×
853

854
        // CLAP_EVENT_MIDI: MIDI 1.0 → UMP via cmidi2.
855
        for(const auto& midi_event : m_output_events.midi_events)
×
856
        {
857
          if(midi_event.port_index != midi_port_index)
×
858
            continue;
×
859
          libremidi::ump msg;
×
860
          if(cmidi2_midi1_channel_voice_to_midi2(midi_event.data, 3, msg.data))
×
861
          {
NEW
862
            msg.timestamp = m_tick_start + midi_event.header.time;
×
863
            port_messages.push_back(msg);
×
864
          }
×
865
        }
866

867
        // CLAP_EVENT_NOTE_*: synthesize a MIDI 1.0 message and convert.
868
        // NOTE_END / NOTE_CHOKE have no direct MIDI equivalent — both
869
        // mean "this voice is over", so emit NOTE_OFF (velocity 0) so a
870
        // downstream MIDI device sees the voice released.
871
        for(const auto& ne : m_output_events.note_events)
×
872
        {
873
          if(ne.port_index != midi_port_index)
×
874
            continue;
×
875
          const uint8_t ch = static_cast<uint8_t>(ne.channel & 0x0F);
×
876
          const uint8_t key = static_cast<uint8_t>(std::clamp<int>(ne.key, 0, 127));
×
877
          uint8_t bytes[3]{};
×
878
          switch(ne.header.type)
×
879
          {
880
            case CLAP_EVENT_NOTE_ON: {
881
              const uint8_t vel = static_cast<uint8_t>(std::clamp(
×
882
                  static_cast<int>(ne.velocity * 127.0 + 0.5), 0, 127));
×
883
              bytes[0] = 0x90 | ch;
×
884
              bytes[1] = key;
×
885
              bytes[2] = vel;
×
886
              break;
×
887
            }
888
            case CLAP_EVENT_NOTE_OFF: {
889
              const uint8_t vel = static_cast<uint8_t>(std::clamp(
×
890
                  static_cast<int>(ne.velocity * 127.0 + 0.5), 0, 127));
×
891
              bytes[0] = 0x80 | ch;
×
892
              bytes[1] = key;
×
893
              bytes[2] = vel;
×
894
              break;
×
895
            }
896
            case CLAP_EVENT_NOTE_END:
897
            case CLAP_EVENT_NOTE_CHOKE:
898
              bytes[0] = 0x80 | ch;
×
899
              bytes[1] = key;
×
900
              bytes[2] = 0;
×
901
              break;
×
902
            default:
903
              continue;
×
904
          }
905
          libremidi::ump msg;
×
906
          if(cmidi2_midi1_channel_voice_to_midi2(bytes, 3, msg.data))
×
907
          {
NEW
908
            msg.timestamp = m_tick_start + ne.header.time;
×
909
            port_messages.push_back(msg);
×
910
          }
×
911
        }
912

913
        // CLAP_EVENT_MIDI2: already UMP-shaped — copy verbatim.
914
        for(const auto& m2 : m_output_events.midi2_events)
×
915
        {
916
          if(m2.port_index != midi_port_index)
×
917
            continue;
×
918
          libremidi::ump msg;
×
919
          std::memcpy(msg.data, m2.data, sizeof(m2.data));
×
NEW
920
          msg.timestamp = m_tick_start + m2.header.time;
×
921
          port_messages.push_back(msg);
×
922
        }
923

924
        // CLAP_EVENT_MIDI_SYSEX: encode the captured byte buffer into
925
        // one or more UMP sysex7 packets (6 data bytes per packet).
926
        for(const auto& sx : m_output_events.sysex_events)
×
927
        {
928
          if(sx.port_index != midi_port_index)
×
929
            continue;
×
930
          if(sx.size == 0 || sx.buffer == nullptr)
×
931
            continue;
×
932
          const std::size_t num_packets
×
933
              = cmidi2_ump_sysex_get_num_packets(sx.size, 6);
×
934
          for(std::size_t pkt = 0; pkt < num_packets; ++pkt)
×
935
          {
936
            libremidi::ump msg{};
×
937
            uint64_t r1 = 0, r2 = 0;
×
938
            cmidi2_ump_sysex_get_packet_of(
×
939
                &r1, &r2, /*group*/ 0, sx.size, sx.buffer,
×
940
                static_cast<int32_t>(pkt), CMIDI2_MESSAGE_TYPE_SYSEX7, 6,
×
941
                /*hasStreamId*/ false, /*streamId*/ 0);
942
            // sysex7 packets are 64-bit; libremidi::ump.data[0..1] holds it.
943
            msg.data[0] = static_cast<uint32_t>(r1 >> 32);
×
944
            msg.data[1] = static_cast<uint32_t>(r1 & 0xFFFFFFFFu);
×
945
            msg.timestamp = sx.header.time;
×
946
            port_messages.push_back(msg);
×
947
          }
×
948
        }
949

950
        midi_port_index++;
×
951
      }
×
952
    }
×
953
  }
×
954

955
  PluginHandle& m_instance;
956
  std::vector<clap_audio_port_info_t> m_expected_audio_inputs{};
×
957
  std::vector<clap_audio_port_info_t> m_expected_audio_outputs{};
×
958

959
  std::vector<clap_audio_buffer_t> input_buffers;
960
  std::vector<clap_audio_buffer_t> output_buffers;
961

962
  bool m_activated{};
×
963
  bool m_processing{};
×
964
};
965

966
class clap_node_32 final : public clap_node
967
{
968
  std::vector<std::vector<float>> input_channel_storage;
969
  std::vector<std::vector<float>> output_channel_storage;
970
  std::vector<std::vector<float*>> input_channel_ptrs;
971
  std::vector<std::vector<float*>> output_channel_ptrs;
972

973
public:
974
  using clap_node::clap_node;
975

976
  void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
×
977
  {
978
    if(!m_instance.plugin)
×
979
      return;
×
980

981
    // Activate plugin if needed
982
    if(!m_activated)
×
983
      return;
×
984
    if(!m_processing)
×
985
    {
986
      m_processing = start_plugin(m_instance.plugin);
×
987
      if(!m_processing)
×
988
        return;
×
989
    }
×
990

991
    auto [offset, samples] = e.timings(t);
×
992
    if(samples == 0)
×
993
      return;
×
994

995
    m_current_transport = make_transport(t, e);
×
996

997
    // Clear previous data
998
    input_buffers.clear();
×
999
    output_buffers.clear();
×
1000
    input_channel_storage.clear();
×
1001
    output_channel_storage.clear();
×
1002
    input_channel_ptrs.clear();
×
1003
    output_channel_ptrs.clear();
×
1004

NEW
1005
    prepare_input_events(offset, samples);
×
1006

1007
    // Honour CLAP_PROCESS_SLEEP: the plug-in told us it needs no further
1008
    // work until an event arrives or audio input varies. We trust the
1009
    // event check; for audio-input variation we conservatively bail only
1010
    // on instruments (no audio input ports) — for effects, scanning every
1011
    // input frame for non-zero content would cost more than just running
1012
    // the plug-in's process() and is exactly what plug-ins themselves do.
1013
    if(m_last_status == CLAP_PROCESS_SLEEP && audio_ins.empty()
×
1014
       && m_input_events.all_events.empty())
×
1015
    {
1016
      // Stay asleep — emit silence on the audio outputs so downstream
1017
      // sees a clean signal instead of stale data from the last block.
1018
      std::size_t out_idx = 0;
×
1019
      for(ossia::audio_outlet* audio_out : audio_outs)
×
1020
      {
1021
        if(out_idx >= m_expected_audio_outputs.size())
×
1022
          break;
×
1023
        const auto& info = m_expected_audio_outputs[out_idx];
×
1024
        audio_out->data.set_channels(info.channel_count);
×
1025
        for(auto& ch : audio_out->data.get())
×
1026
        {
1027
          ch.resize(std::max<std::size_t>(ch.size(), e.bufferSize()));
×
1028
          std::fill_n(ch.data() + offset, samples, 0.0);
×
1029
        }
1030
        out_idx++;
×
1031
      }
1032
      // Also clear any midi outlets so we don't repeat last block's MIDI.
1033
      for(ossia::midi_outlet* midi_out : midi_outs)
×
1034
        midi_out->data.messages.clear();
×
1035
      return;
×
1036
    }
1037

1038
    // Setup audio input buffers
1039
    // We must create exactly the number of buffers the plugin expects
1040
    int audio_in_idx = 0;
×
1041
    for(ossia::audio_inlet* audio_in : audio_ins)
×
1042
    {
1043
      const auto& audio_info = this->m_expected_audio_inputs[audio_in_idx];
×
1044
      clap_audio_buffer_t buffer = dummy_audio_buffer();
×
1045
      buffer.channel_count = audio_info.channel_count;
×
1046

1047
      audio_in->data.set_channels(buffer.channel_count);
×
1048
      auto& channels = audio_in->data.get();
×
1049
      if(!channels.empty())
×
1050
      {
1051
        auto& storage = input_channel_storage.emplace_back();
×
1052
        storage.resize(buffer.channel_count);
×
1053

1054
        auto& ptrs = input_channel_ptrs.emplace_back();
×
1055
        ptrs.resize(buffer.channel_count);
×
1056

1057
        storage.resize(samples * buffer.channel_count + 16);
×
1058
        for(uint32_t c = 0; c < buffer.channel_count; ++c)
×
1059
        {
1060
          ptrs[c] = storage.data() + c * samples;
×
1061

1062
          // Convert from double to float
1063
          if(c < channels.size())
×
1064
          {
1065
            auto& channel = channels[c];
×
1066
            //SCORE_SOFT_ASSERT(channel.size() >= e.bufferSize());
1067
            channel.resize(std::max((int)channel.size(), (int)e.bufferSize()));
×
1068
            const double* src = channels[c].data() + offset;
×
1069
            float* dst = ptrs[c];
×
1070
            for(uint32_t s = 0; s < samples; ++s)
×
1071
            {
1072
              dst[s] = static_cast<float>(src[s]);
×
1073
            }
×
1074
          }
×
1075
          else
1076
          {
1077
            // Zero fill if no input data
1078
            std::fill(ptrs[c], ptrs[c] + samples, 0.0f);
×
1079
          }
1080
        }
×
1081

1082
        buffer.data32 = ptrs.data();
×
1083
      }
×
1084

1085
      input_buffers.push_back(buffer);
×
1086
      audio_in_idx++;
×
1087
    }
1088

1089
    // Setup output buffers
1090
    int audio_out_idx = 0;
×
1091
    const bool needs_stereo_main_out
×
1092
        = audio_ins.empty() && !audio_outs.empty()
×
1093
          && !midi_ins.empty(); // FIXME check if instrument?
×
1094
    for(ossia::audio_outlet* audio_out : audio_outs)
×
1095
    {
1096
      const auto& audio_info = this->m_expected_audio_outputs[audio_out_idx];
×
1097
      clap_audio_buffer_t buffer = dummy_audio_buffer();
×
1098
      buffer.channel_count = audio_info.channel_count;
×
1099

1100
      const int ossia_channel_count = audio_out_idx == 0 && needs_stereo_main_out
×
1101
                                          ? std::max((int)buffer.channel_count, 2)
×
1102
                                          : buffer.channel_count;
×
1103
      audio_out->data.set_channels(ossia_channel_count);
×
1104
      auto& channels = audio_out->data.get();
×
1105

1106
      // Allocate float storage for output
1107
      auto& storage = output_channel_storage.emplace_back();
×
1108
      storage.resize(buffer.channel_count);
×
1109

1110
      auto& ptrs = output_channel_ptrs.emplace_back();
×
1111
      ptrs.resize(buffer.channel_count);
×
1112

1113
      storage.clear();
×
1114
      storage.resize(samples * buffer.channel_count + 16);
×
1115
      for(uint32_t c = 0; c < ossia_channel_count; ++c)
×
1116
      {
1117
        channels[c].resize(e.bufferSize());
×
1118
      }
×
1119
      for(uint32_t c = 0; c < buffer.channel_count; ++c)
×
1120
      {
1121
        ptrs[c] = storage.data() + c * samples;
×
1122
      }
×
1123

1124
      buffer.data32 = ptrs.data();
×
1125
      output_buffers.push_back(buffer);
×
1126
      audio_out_idx++;
×
1127
    }
1128

1129
    // Ensure we have exactly the number of buffers the plugin expects
1130
    // *before* taking .data() pointers below.
1131
    while(input_buffers.size() < m_expected_audio_inputs.size())
×
1132
      input_buffers.push_back(dummy_audio_buffer());
×
1133
    while(output_buffers.size() < m_expected_audio_outputs.size())
×
1134
      output_buffers.push_back(dummy_audio_buffer());
×
1135

1136
    clap_process_t process{};
×
1137
    process.frames_count = samples;
×
1138
    process.audio_inputs = input_buffers.data();
×
1139
    process.audio_outputs = output_buffers.data();
×
1140
    process.audio_inputs_count = m_expected_audio_inputs.size();
×
1141
    process.audio_outputs_count = m_expected_audio_outputs.size();
×
1142
    do_process(process, m_input_events, m_output_events);
×
1143

1144
    // Convert audio output from float back to double
1145
    audio_out_idx = 0;
×
1146
    for(ossia::audio_outlet* audio_out : audio_outs)
×
1147
    {
1148
      if(audio_out_idx < output_channel_storage.size())
×
1149
      {
1150
        auto& channels = audio_out->data.get();
×
1151
        const auto& storage = output_channel_storage[audio_out_idx];
×
1152
        const uint32_t plugin_channels
×
1153
            = m_expected_audio_outputs[audio_out_idx].channel_count;
×
1154

1155
        if(audio_out_idx == 0 && needs_stereo_main_out && plugin_channels == 1)
×
1156
        {
1157
          // Basic mono instrument case (e.g. Nekobi): duplicate L → R only
1158
          // when the plugin's main out is actually mono. For ≥2-channel
1159
          // outs the second channel holds valid audio.
1160
          const float* src = storage.data();
×
1161
          double* dst_l = channels[0].data() + offset;
×
1162
          double* dst_r = channels[1].data() + offset;
×
1163
          for(uint32_t s = 0; s < samples; ++s)
×
1164
          {
1165
            dst_l[s] = static_cast<double>(src[s]);
×
1166
            dst_r[s] = dst_l[s];
×
1167
          }
×
1168
        }
×
1169
        else
1170
        {
1171
          const uint32_t channel_max
×
1172
              = std::min<uint32_t>(channels.size(), plugin_channels);
×
1173
          for(uint32_t c = 0; c < channel_max; ++c)
×
1174
          {
1175
            const float* src = storage.data() + c * samples;
×
1176
            double* dst = channels[c].data() + offset;
×
1177
            for(uint32_t s = 0; s < samples; ++s)
×
1178
              dst[s] = static_cast<double>(src[s]);
×
1179
          }
×
1180
        }
1181
      }
×
1182
      audio_out_idx++;
×
1183
    }
1184
  }
×
1185
};
1186

1187
class clap_node_64 final : public clap_node
1188
{
1189
  std::vector<std::vector<double*>> input_channel_ptrs;
1190
  std::vector<std::vector<double*>> output_channel_ptrs;
1191

1192
public:
1193
  using clap_node::clap_node;
1194
  ~clap_node_64() { }
×
1195
  void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
×
1196
  {
1197
    if(!m_instance.plugin)
×
1198
      return;
×
1199

1200
    // Activate plugin if needed
1201
    if(!m_activated)
×
1202
      return;
×
1203
    if(!m_processing)
×
1204
    {
1205
      m_processing = start_plugin(m_instance.plugin);
×
1206
      if(!m_processing)
×
1207
        return;
×
1208
    }
×
1209

1210
    auto [offset, samples] = e.timings(t);
×
1211
    if(samples == 0)
×
1212
      return;
×
1213

1214
    m_current_transport = make_transport(t, e);
×
1215

1216
    // Prepare buffers
1217
    input_buffers.clear();
×
1218
    output_buffers.clear();
×
1219
    m_input_events.clear();
×
1220
    m_output_events.clear();
×
1221

1222
    // Process parameter changes
NEW
1223
    prepare_input_events(offset, samples);
×
1224

1225
    // Honour CLAP_PROCESS_SLEEP — see clap_node_32::run for rationale.
1226
    if(m_last_status == CLAP_PROCESS_SLEEP && audio_ins.empty()
×
1227
       && m_input_events.all_events.empty())
×
1228
    {
1229
      std::size_t out_idx = 0;
×
1230
      for(ossia::audio_outlet* audio_out : audio_outs)
×
1231
      {
1232
        if(out_idx >= m_expected_audio_outputs.size())
×
1233
          break;
×
1234
        const auto& info = m_expected_audio_outputs[out_idx];
×
1235
        audio_out->data.set_channels(info.channel_count);
×
1236
        for(auto& ch : audio_out->data.get())
×
1237
        {
1238
          ch.resize(std::max<std::size_t>(ch.size(), e.bufferSize()));
×
1239
          std::fill_n(ch.data() + offset, samples, 0.0);
×
1240
        }
1241
        out_idx++;
×
1242
      }
1243
      for(ossia::midi_outlet* midi_out : midi_outs)
×
1244
        midi_out->data.messages.clear();
×
1245
      return;
×
1246
    }
1247

1248
    // Setup audio input buffers
1249
    std::size_t audio_in_idx = 0;
×
1250
    for(ossia::audio_inlet* audio_in : audio_ins)
×
1251
    {
1252
      const auto& audio_info = this->m_expected_audio_inputs[audio_in_idx];
×
1253
      clap_audio_buffer_t buffer = dummy_audio_buffer();
×
1254
      buffer.channel_count = audio_info.channel_count;
×
1255

1256
      audio_in->data.set_channels(buffer.channel_count);
×
1257
      auto& channels = audio_in->data.get();
×
1258
      if(!channels.empty())
×
1259
      {
1260
        // Pass exactly what the plugin expects. The previous code clamped
1261
        // this to 2 channels, dropping surround / multi-channel inputs.
1262
        auto& ptrs = input_channel_ptrs.emplace_back();
×
1263
        ptrs.resize(buffer.channel_count);
×
1264

1265
        for(uint32_t c = 0; c < buffer.channel_count; ++c)
×
1266
        {
1267
          if(c < channels.size())
×
1268
          {
1269
            auto& channel = channels[c];
×
1270
            channel.resize(std::max<std::size_t>(channel.size(), e.bufferSize()));
×
1271
            ptrs[c] = const_cast<double*>(channels[c].data() + offset);
×
1272
          }
×
1273
          else
1274
          {
1275
            ptrs[c] = dummy_io_buffer.data();
×
1276
          }
1277
        }
×
1278

1279
        buffer.data64 = ptrs.data();
×
1280
      }
×
1281

1282
      this->input_buffers.push_back(buffer);
×
1283
      audio_in_idx++;
×
1284
    }
1285

1286
    // Setup output buffers
1287
    std::size_t audio_out_idx = 0;
×
1288
    const bool needs_stereo_main_out
×
1289
        = audio_ins.empty() && !audio_outs.empty()
×
1290
          && !midi_ins.empty(); // FIXME check if instrument?
×
1291
    for(ossia::audio_outlet* audio_out : audio_outs)
×
1292
    {
1293
      const auto& audio_info = this->m_expected_audio_outputs[audio_out_idx];
×
1294
      clap_audio_buffer_t buffer = dummy_audio_buffer();
×
1295
      buffer.channel_count = audio_info.channel_count;
×
1296

1297
      const int ossia_channel_count = audio_out_idx == 0 && needs_stereo_main_out
×
1298
                                          ? std::max((int)buffer.channel_count, 2)
×
1299
                                          : buffer.channel_count;
×
1300
      audio_out->data.set_channels(ossia_channel_count);
×
1301
      auto& channels = audio_out->data.get();
×
1302

1303
      auto& ptrs = output_channel_ptrs.emplace_back();
×
1304
      ptrs.resize(buffer.channel_count);
×
1305

1306
      for(uint32_t c = 0; c < ossia_channel_count; ++c)
×
1307
      {
1308
        channels[c].resize(e.bufferSize());
×
1309
      }
×
1310
      for(uint32_t c = 0; c < buffer.channel_count; ++c)
×
1311
      {
1312
        ptrs[c] = channels[c].data() + offset;
×
1313
      }
×
1314

1315
      buffer.data64 = ptrs.data();
×
1316
      this->output_buffers.push_back(buffer);
×
1317
      audio_out_idx++;
×
1318
    }
1319

1320
    // Pad both buffer lists *before* taking .data() pointers so we don't
1321
    // hand the plugin a stale pointer after a reallocation, and so the two
1322
    // counts actually match what the plugin is told below.
1323
    while(this->input_buffers.size() < m_expected_audio_inputs.size())
×
1324
      this->input_buffers.push_back(dummy_audio_buffer());
×
1325
    while(this->output_buffers.size() < m_expected_audio_outputs.size())
×
1326
      this->output_buffers.push_back(dummy_audio_buffer());
×
1327

1328
    clap_process_t process{};
×
1329
    process.frames_count = samples;
×
1330
    process.audio_inputs = this->input_buffers.data();
×
1331
    process.audio_outputs = this->output_buffers.data();
×
1332
    process.audio_inputs_count = m_expected_audio_inputs.size();
×
1333
    process.audio_outputs_count = m_expected_audio_outputs.size();
×
1334
    do_process(process, m_input_events, m_output_events);
×
1335

1336
    // Basic mono instrument case (e.g. Nekobi): only duplicate if the plug-in
1337
    // really has a mono main out. For ≥2-channel outputs the right channel is
1338
    // valid and must not be clobbered.
1339
    if(needs_stereo_main_out && !m_expected_audio_outputs.empty()
×
1340
       && m_expected_audio_outputs[0].channel_count == 1)
×
1341
    {
1342
      auto& l = audio_outs[0]->data.channel(0);
×
1343
      auto& r = audio_outs[0]->data.channel(1);
×
1344
      r.assign(l.begin(), l.end());
×
1345
    }
×
1346
  }
×
1347
};
1348

1349
// Special case for monophonic nodes
1350
class clap_node_mono : public clap_node_base
1351
{
1352
public:
1353
  // Declared early so member function signatures (make_poly_instance) and the
1354
  // m_incoming queue declared further down can refer to it.
1355
  struct poly_plugin
1356
  {
1357
    const clap_plugin_t* plugin{};
1358
    bool activated{};
1359
    bool processing{};
1360
    operator const clap_plugin_t*() const noexcept { return plugin; }
×
1361
  };
1362

1363
  clap_node_mono(
×
1364
      const Clap::Model& proc, Clap::PluginHandle& handle, int sampleRate, int bs)
1365
      : clap_node_base{proc}
×
1366
      , m_instance{handle}
×
1367
      , m_plugin_id{proc.pluginId().toUtf8()}
×
1368
  {
×
1369
    // Pre-reserve enough capacity that the dynamic grower (which feeds
1370
    // m_poly from the audio thread via drain_incoming → push_back) never
1371
    // triggers a reallocation in normal use. 256 * sizeof(poly_plugin)
1372
    // ≈ 4 kB up front, and covers up to 256 channels of polyphony without
1373
    // any audio-thread malloc; beyond that vector will still grow, just
1374
    // with a non-RT-friendly reallocation cost each doubling.
1375
    m_poly.reserve(256);
×
1376
    m_poly.push_back({handle.plugin, false});
×
1377
    if(handle.activated)
×
1378
      (void)deactivate_plugin(handle.plugin);
×
1379
    m_poly.back().activated = activate_plugin(m_instance.plugin, sampleRate, bs);
×
1380
    // Mirror the activation state on the shared handle so that
1381
    // Clap::Model::~Model / resetExecution can drive the deactivate of
1382
    // m_poly[0] from the main thread, symmetrically with clap_node.
1383
    handle.activated = m_poly.back().activated;
×
1384

1385
    // The constructor runs on the main thread, so create_plugin/init are
1386
    // spec-correct here. If any of the extra instances fail to construct we
1387
    // tear down the ones we already built so we never leak plugin handles.
1388
    try
1389
    {
1390
      for(int i = 0; i < 7; i++)
×
1391
        add_poly_instance(sampleRate, bs);
×
1392
    }
×
1393
    catch(...)
1394
    {
1395
      destroy_extra_instances();
×
1396
      throw;
×
1397
    }
×
1398
  }
×
1399

1400
  void add_poly_instance(int sampleRate, int bs)
×
1401
  {
1402
    m_poly.push_back(make_poly_instance(sampleRate, bs, /*state*/ {}));
×
1403
  }
×
1404

1405
  // [main-thread] Build a fresh, init'd, activated polyphonic instance.
1406
  // Optionally clones state into it via clap.state (so AIDA-X & friends
1407
  // keep their loaded model / preset on each instance).
1408
  // Throws std::runtime_error on create_plugin / init failure.
1409
  poly_plugin make_poly_instance(int sampleRate, int bs, const QByteArray& state)
×
1410
  {
1411
    poly_plugin p{nullptr, false, false};
×
1412
    p.plugin = m_instance.factory->create_plugin(
×
1413
        m_instance.factory, &m_instance.host, m_plugin_id.data());
×
1414
    if(!p.plugin)
×
1415
      throw std::runtime_error("Could not create plug-in instance");
×
1416

1417
    if(!p.plugin->init(p.plugin))
×
1418
    {
1419
      p.plugin->destroy(p.plugin);
×
1420
      throw std::runtime_error("Could not init plug-in instance");
×
1421
    }
1422
    // Apply the snapshot *before* activate, matching how Model::loadPlugin
1423
    // restores state for the primary instance.
1424
    apply_clap_state(p.plugin, state);
×
1425
    p.activated = activate_plugin(p.plugin, sampleRate, bs);
×
1426
    return p;
×
1427
  }
×
1428

1429
  // [audio-thread] Called by grow_pool_tick via in_exec.
1430
  void append_poly(poly_plugin p) noexcept { m_poly.push_back(p); }
×
1431

1432
  // [audio-thread] Tell the grower we need at least `n` total instances.
1433
  // Monotonically-increasing: a brief drop to a smaller channel count
1434
  // does not cause us to shrink the pool.
1435
  void request_pool_size(std::size_t n) noexcept
×
1436
  {
1437
    auto prev = m_requested_pool.load(std::memory_order_relaxed);
×
1438
    while(n > prev
×
1439
          && !m_requested_pool.compare_exchange_weak(
×
1440
              prev, n, std::memory_order_release, std::memory_order_relaxed))
×
1441
      ;
1442
  }
×
1443

1444
  // Tear down extra polyphonic instances (skipping m_poly[0], which is
1445
  // owned by the Clap::Model). Used by the constructor's catch and by the
1446
  // destructor's queued main-thread cleanup.
1447
  void destroy_extra_instances() noexcept
×
1448
  {
1449
    for(std::size_t i = 1; i < m_poly.size(); ++i)
×
1450
    {
1451
      auto& p = m_poly[i];
×
1452
      if(!p.plugin)
×
1453
        continue;
×
1454
      if(p.processing)
×
1455
      {
1456
        p.plugin->stop_processing(p.plugin);
×
1457
        p.processing = false;
×
1458
      }
×
1459
      if(p.activated)
×
1460
      {
1461
        p.plugin->deactivate(p.plugin);
×
1462
        p.activated = false;
×
1463
      }
×
1464
      p.plugin->destroy(p.plugin);
×
1465
      p.plugin = nullptr;
×
1466
    }
×
1467
  }
×
1468

1469
  ~clap_node_mono()
×
1470
  {
×
1471
    // Stop any instance that was still processing. ~clap_node_mono runs on
1472
    // the audio thread, so stop_processing is allowed here (it is the only
1473
    // CLAP entry point we may call from there). Deactivation and destruction
1474
    // of the extra instances is deferred to the main thread below.
1475
    for(std::size_t i = 0; i < m_poly.size(); ++i)
×
1476
    {
1477
      if(m_poly[i].processing)
×
1478
      {
1479
        m_poly[i].plugin->stop_processing(m_poly[i].plugin);
×
1480
        m_poly[i].processing = false;
×
1481
      }
×
1482
    }
×
1483

1484
    // m_poly[0] is the Model's shared handle; deactivated by Model::~Model.
1485
    // Orphaned grower output is cleaned up via grow_pool_tick's weak_ptr.
1486
    QMetaObject::invokeMethod(
×
1487
        QCoreApplication::instance(),
×
1488
        [poly = std::move(m_poly)]() mutable {
×
1489
      auto destroy = [](const poly_plugin& p) {
×
1490
        if(!p.plugin)
×
1491
          return;
×
1492
        if(p.activated)
×
1493
          p.plugin->deactivate(p.plugin);
×
1494
        p.plugin->destroy(p.plugin);
×
1495
      };
×
1496
      for(std::size_t i = 1; i < poly.size(); ++i)
×
1497
        destroy(poly[i]);
×
1498
    });
×
1499
  }
×
1500

1501
  void do_process(
×
1502
      const clap_plugin_t* plug, clap_process_t& process, event_storage& input_storage,
1503
      event_storage& output_storage, int current_channel)
1504
  {
1505
    // Process audio
1506
    process.steady_time = -1;
×
1507
    process.transport = &m_current_transport;
×
1508

1509
    // Setup input events
1510
    clap_input_events evs{
×
1511
        .ctx = &input_storage,
×
1512
        .size = +[](const clap_input_events* list) -> uint32_t {
×
1513
      auto* storage = static_cast<event_storage*>(list->ctx);
×
1514
      return storage->all_events.size();
×
1515
    },
1516
        .get = +[](const clap_input_events* list,
×
1517
                   uint32_t index) -> const clap_event_header_t* {
1518
      auto* storage = static_cast<event_storage*>(list->ctx);
×
1519
      if(index < storage->all_events.size())
×
1520
        return storage->all_events[index];
×
1521
      return nullptr;
×
1522
    }};
×
1523

1524
    clap_output_events_t o_evs{
×
1525
        .ctx = &output_storage,
×
1526
        .try_push = [](const struct clap_output_events* list,
×
1527
                       const clap_event_header_t* event) -> bool {
1528
      auto* storage = static_cast<event_storage*>(list->ctx);
×
1529
      if(!event || event->space_id != CLAP_CORE_EVENT_SPACE_ID)
×
1530
        return false;
×
1531
      if(event->type == CLAP_EVENT_PARAM_VALUE
×
1532
         && event->size >= sizeof(clap_event_param_value_t))
×
1533
      {
1534
        storage->param_events.push_back(
×
1535
            *reinterpret_cast<const clap_event_param_value_t*>(event));
×
1536
        return true;
×
1537
      }
1538
      return false;
×
1539
    }};
×
1540

1541
    process.in_events = &evs;
×
1542
    process.out_events = &o_evs;
×
1543

1544
    // We track m_last_status mainly for diagnostics in the mono path. We
1545
    // don't act on CLAP_PROCESS_SLEEP for monophonic-faked plugins because
1546
    // each m_poly[i] has independent processing state and a per-channel
1547
    // skip would require per-channel status tracking + audio-input
1548
    // variation detection per channel — more bookkeeping than it's worth.
1549
    m_last_status = plug->process(plug, &process);
×
1550
    (void)current_channel;
1551
  }
×
1552

1553
  // Snapshot voice-0 params into voices 1+ so UI edits propagate (CLAP UI binds to voice 0).
1554
  void replicate_voice0_state(
×
1555
      const clap_plugin_t* plug, event_storage& input_storage) noexcept
1556
  {
1557
    auto params = this->handle->ext_params;
×
1558
    if(!params)
×
1559
      return;
×
1560
    const int param_count = params->count(plug);
×
1561
    if(param_count <= 0)
×
1562
      return;
×
1563

1564
    const int orig = input_storage.param_events.size();
×
1565
    int cur = orig;
×
1566
    input_storage.param_events.resize(orig + param_count);
×
1567
    clap_event_param_value_t* cur_p = &input_storage.param_events[cur];
×
1568
    for(auto& p : m_param_ins)
×
1569
    {
1570
      // Per-voice slice would be clobbered by voice-0's value here.
1571
      if(m_overridden_params.contains(p.id))
×
1572
        continue;
×
1573
      double current_value;
1574
      if(params->get_value(plug, p.id, &current_value))
×
1575
      {
1576
        clap_event_param_value_t& param_event = *cur_p;
×
1577
        param_event.header.size = sizeof(clap_event_param_value_t);
×
1578
        param_event.header.time = 0;
×
1579
        param_event.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
×
1580
        param_event.header.type = CLAP_EVENT_PARAM_VALUE;
×
1581
        param_event.header.flags = CLAP_EVENT_IS_LIVE;
×
1582
        param_event.param_id = p.id;
×
1583
        param_event.cookie = p.cookie;
×
1584
        param_event.note_id = -1;
×
1585
        param_event.port_index = -1;
×
1586
        param_event.channel = -1;
×
1587
        param_event.key = -1;
×
1588
        param_event.value = current_value;
×
1589
        ++cur;
×
1590
        ++cur_p;
×
1591
      }
×
1592
    }
1593

1594
    if(cur > orig)
×
1595
    {
1596
      auto it = std::upper_bound(
×
1597
          input_storage.all_events.begin(), input_storage.all_events.end(), 0,
×
1598
          [](auto& t1, auto& ev2) { return t1 < ev2->time; });
×
1599
      const auto r = std::span<clap_event_param_value_t>(
×
1600
                         input_storage.param_events.data() + orig,
×
1601
                         input_storage.param_events.data() + cur)
×
1602
                     | std::views::transform([](clap_event_param_value_t& elem) {
×
1603
        return &elem.header;
×
1604
      });
1605
      input_storage.all_events.insert(it, std::begin(r), std::end(r));
×
1606
    }
×
1607
  }
×
1608

1609
  // Must run after prepare_input_events and before the per-voice loop.
1610
  void prepare_voice_overrides(uint32_t samples) noexcept
×
1611
  {
1612
    m_voice_overrides.resize(m_poly.size());
×
1613
    for(auto& vec : m_voice_overrides)
×
1614
      vec.clear();
×
1615
    m_overridden_params.clear();
×
1616

1617
    for(std::size_t i = 0; i < parameter_ins.size(); ++i)
×
1618
    {
1619
      const auto& data = parameter_ins[i]->data.get_data();
×
1620
      if(data.empty())
×
1621
        continue;
×
1622
      const auto& param_info = m_param_ins[i];
×
1623
      for(const auto& tv : data)
×
1624
      {
1625
        if(!is_vector_value(tv.value))
×
1626
          continue;
×
1627
        m_overridden_params.insert(param_info.id);
×
1628
        const int64_t t = tv.timestamp;
×
1629
        const uint32_t time
×
1630
            = t <= 0
×
1631
                  ? 0u
1632
                  : static_cast<uint32_t>(
1633
                        std::min<int64_t>(t, samples > 0 ? samples - 1 : 0));
×
1634
        for(std::size_t v = 0; v < m_poly.size(); ++v)
×
1635
        {
1636
          auto sliced = voice_control_value(tv.value, v);
×
1637
          if(!sliced)
×
1638
            continue;
×
1639
          const double value = std::clamp(
×
1640
              *sliced, param_info.min_value, param_info.max_value);
×
1641
          clap_event_param_value_t ev{};
×
1642
          ev.header.size = sizeof(clap_event_param_value_t);
×
1643
          ev.header.time = time;
×
1644
          ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
×
1645
          ev.header.type = CLAP_EVENT_PARAM_VALUE;
×
1646
          ev.header.flags = CLAP_EVENT_IS_LIVE;
×
1647
          ev.param_id = param_info.id;
×
1648
          ev.cookie = param_info.cookie;
×
1649
          ev.note_id = -1;
×
1650
          ev.port_index = -1;
×
1651
          ev.channel = -1;
×
1652
          ev.key = -1;
×
1653
          ev.value = value;
×
1654
          m_voice_overrides[v].push_back(ev);
×
1655
        }
×
1656
      }
1657
    }
×
1658
  }
×
1659

1660
  // Compose shared base + this voice's overrides into one sorted event stream.
1661
  void build_voice_input(event_storage& dst, std::size_t voice_idx)
×
1662
  {
1663
    dst.midi_events = m_input_events.midi_events;
×
1664
    dst.midi2_events = m_input_events.midi2_events;
×
1665
    dst.note_events = m_input_events.note_events;
×
1666
    dst.param_events = m_input_events.param_events;
×
1667
    dst.sysex_events = m_input_events.sysex_events;
×
1668
    dst.sysex_data = m_input_events.sysex_data;
×
1669
    const auto& overrides = m_voice_overrides[voice_idx];
×
1670
    dst.param_events.insert(
×
1671
        dst.param_events.end(), overrides.begin(), overrides.end());
×
1672

1673
    dst.all_events.clear();
×
1674
    dst.all_events.reserve(
×
1675
        dst.midi_events.size() + dst.midi2_events.size() + dst.note_events.size()
×
1676
        + dst.param_events.size() + dst.sysex_events.size());
×
1677
    for(auto& ev : dst.midi_events)
×
1678
      dst.all_events.push_back(reinterpret_cast<clap_event_header_t*>(&ev));
×
1679
    for(auto& ev : dst.midi2_events)
×
1680
      dst.all_events.push_back(reinterpret_cast<clap_event_header_t*>(&ev));
×
1681
    for(auto& ev : dst.note_events)
×
1682
      dst.all_events.push_back(reinterpret_cast<clap_event_header_t*>(&ev));
×
1683
    for(auto& ev : dst.param_events)
×
1684
      dst.all_events.push_back(reinterpret_cast<clap_event_header_t*>(&ev));
×
1685
    for(auto& ev : dst.sysex_events)
×
1686
      dst.all_events.push_back(reinterpret_cast<clap_event_header_t*>(&ev));
×
1687
    std::sort(
×
1688
        dst.all_events.begin(), dst.all_events.end(),
×
1689
        [](const clap_event_header_t* a, const clap_event_header_t* b) {
×
1690
      return a->time < b->time;
×
1691
    });
1692
  }
×
1693

1694
  PluginHandle& m_instance;
1695
  std::vector<poly_plugin> m_poly;
1696
  std::string m_plugin_id;
1697

1698
  std::atomic<std::size_t> m_requested_pool{0};
×
1699

1700
  std::vector<std::vector<clap_event_param_value_t>> m_voice_overrides;
1701

1702
  ossia::hash_set<clap_id> m_overridden_params;
1703

1704
  event_storage m_voice_input;
1705
};
1706

1707
class clap_node_mono_32 final : public clap_node_mono
1708
{
1709
  std::vector<float> input_channel_storage;
1710
  std::vector<float> output_channel_storage;
1711

1712
public:
1713
  using clap_node_mono::clap_node_mono;
1714

1715
  void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
×
1716
  {
1717
    if(!m_instance.plugin)
×
1718
      return;
×
1719

1720
    auto [offset, samples] = e.timings(t);
×
1721
    if(samples == 0)
×
1722
      return;
×
1723

1724
    m_current_transport = make_transport(t, e);
×
1725

1726
    // Clear previous data
1727
    input_channel_storage.clear();
×
1728
    output_channel_storage.clear();
×
1729
    input_channel_storage.resize(samples);
×
1730
    output_channel_storage.resize(samples);
×
1731

NEW
1732
    prepare_input_events(offset, samples);
×
1733
    prepare_voice_overrides(samples);
×
1734

1735
    float* ins_pointer[1]{input_channel_storage.data()};
×
1736
    float* outs_pointer[1]{output_channel_storage.data()};
×
1737

1738
    // Setup audio input buffers
1739
    const auto audio_in = audio_ins[0];
×
1740
    const auto audio_out = audio_outs[0];
×
1741
    auto& in_channels = audio_in->data.get();
×
1742
    const auto upstream_channels = audio_in->data.channels();
×
1743
    if(upstream_channels == 0)
×
1744
      return;
×
1745
    request_pool_size(upstream_channels);
×
1746
    const std::size_t poly_channels
×
1747
        = std::min<std::size_t>(upstream_channels, m_poly.size());
×
1748
    // FIXME constant mode of audio inputs
1749
    if(std::all_of(
×
1750
           audio_in->data.begin(), audio_in->data.end(),
×
1751
           [](const auto& channel) { return channel.size() == 0; }))
×
1752
      return;
×
1753
    audio_out->data.set_channels(poly_channels);
×
1754
    auto& out_channels = audio_out->data.get();
×
1755
    const uint32_t buffer_size = e.bufferSize();
×
1756
    clap_audio_buffer_t in_buffer = dummy_audio_buffer();
×
1757
    clap_audio_buffer_t out_buffer = dummy_audio_buffer();
×
1758
    in_buffer.channel_count = 1;
×
1759
    in_buffer.data32 = ins_pointer;
×
1760
    out_buffer.channel_count = 1;
×
1761
    out_buffer.data32 = outs_pointer;
×
1762

1763
    for(std::size_t current_channel = 0; current_channel < poly_channels;
×
1764
        ++current_channel)
×
1765
    {
1766
      // 0. Activate plug-in if necessary
1767
      auto& cur = m_poly[current_channel];
×
1768
      if(!cur.activated)
×
1769
        return;
×
1770

1771
      if(!cur.processing)
×
1772
      {
1773
        cur.processing = start_plugin(cur.plugin);
×
1774
        if(!cur.processing)
×
1775
          return;
×
1776
      }
×
1777

1778
      auto& channel = in_channels[current_channel];
×
1779

1780
      // 1. Copy input (extend the upstream buffer to the full block size in
1781
      //    case the producer wrote fewer frames than the engine block).
1782
      {
1783
        channel.resize(std::max<std::size_t>(channel.size(), buffer_size));
×
1784
        SCORE_ASSERT(input_channel_storage.size() >= samples);
×
1785
        const double* src = channel.data() + offset;
×
1786
        float* dst = input_channel_storage.data();
×
1787
        for(uint32_t s = 0; s < samples; ++s)
×
1788
          dst[s] = static_cast<float>(src[s]);
×
1789
      }
1790

1791
      // 2. Clear output
1792
      std::fill_n(output_channel_storage.data(), samples, 0);
×
1793

1794
      // 3. Process this channel
1795
      clap_process_t process{};
×
1796
      process.frames_count = samples;
×
1797
      process.audio_inputs = &in_buffer;
×
1798
      process.audio_outputs = &out_buffer;
×
1799
      process.audio_inputs_count = 1;
×
1800
      process.audio_outputs_count = 1;
×
1801
      build_voice_input(m_voice_input, current_channel);
×
1802
      do_process(cur, process, m_voice_input, m_output_events, current_channel);
×
1803
      if(current_channel == 0)
×
1804
        replicate_voice0_state(cur, m_input_events);
×
1805

1806
      // 4. Copy float back to matching output channel. Resize to the full
1807
      //    block size first — writing samples frames starting at `offset`
1808
      //    would otherwise overflow when `offset > 0`.
1809
      {
1810
        auto& out_channel = out_channels[current_channel];
×
1811
        out_channel.resize(std::max<std::size_t>(out_channel.size(), buffer_size));
×
1812
        const float* src = output_channel_storage.data();
×
1813
        double* dst = out_channel.data() + offset;
×
1814
        for(uint32_t s = 0; s < samples; ++s)
×
1815
          dst[s] = static_cast<double>(src[s]);
×
1816
      }
1817
    }
×
1818

1819
    dispatch_param_outputs();
×
1820
  }
×
1821
};
1822

1823
class clap_node_mono_64 final : public clap_node_mono
1824
{
1825
public:
1826
  using clap_node_mono::clap_node_mono;
1827
  void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
×
1828
  {
1829
    if(!m_instance.plugin)
×
1830
      return;
×
1831

1832
    auto [offset, samples] = e.timings(t);
×
1833
    if(samples == 0)
×
1834
      return;
×
1835

1836
    m_current_transport = make_transport(t, e);
×
1837

NEW
1838
    prepare_input_events(offset, samples);
×
1839
    prepare_voice_overrides(samples);
×
1840

1841
    double* ins_pointer[1]{};
×
1842
    double* outs_pointer[1]{};
×
1843

1844
    // Setup audio input buffers
1845
    const auto audio_in = audio_ins[0];
×
1846
    const auto audio_out = audio_outs[0];
×
1847
    const auto upstream_channels = audio_in->data.channels();
×
1848
    if(upstream_channels == 0)
×
1849
      return;
×
1850
    request_pool_size(upstream_channels);
×
1851
    const std::size_t poly_channels
×
1852
        = std::min<std::size_t>(upstream_channels, m_poly.size());
×
1853
    // FIXME constant mode of audio inputs
1854
    if(std::all_of(
×
1855
           audio_in->data.begin(), audio_in->data.end(),
×
1856
           [](const auto& channel) { return channel.size() == 0; }))
×
1857
      return;
×
1858
    const uint32_t buffer_size = e.bufferSize();
×
1859
    for(auto& channel : audio_in->data.get())
×
1860
    {
1861
      channel.resize(std::max<std::size_t>(channel.size(), buffer_size));
×
1862
    }
1863
    audio_out->data.set_channels(poly_channels);
×
1864
    auto& out_channels = audio_out->data.get();
×
1865
    for(std::size_t c = 0; c < poly_channels; ++c)
×
1866
    {
1867
      out_channels[c].resize(
×
1868
          std::max<std::size_t>(out_channels[c].size(), buffer_size));
×
1869
    }
×
1870
    clap_audio_buffer_t in_buffer = dummy_audio_buffer();
×
1871
    clap_audio_buffer_t out_buffer = dummy_audio_buffer();
×
1872
    in_buffer.channel_count = 1;
×
1873
    in_buffer.data64 = ins_pointer;
×
1874
    out_buffer.channel_count = 1;
×
1875
    out_buffer.data64 = outs_pointer;
×
1876

1877
    auto& in_channels = audio_in->data.get();
×
1878
    for(std::size_t current_channel = 0; current_channel < poly_channels;
×
1879
        ++current_channel)
×
1880
    {
1881
      // 0. Activate plug-in if necessary
1882
      auto& cur = m_poly[current_channel];
×
1883
      if(!cur.activated)
×
1884
        return;
×
1885

1886
      if(!cur.processing)
×
1887
      {
1888
        cur.processing = start_plugin(cur.plugin);
×
1889
        if(!cur.processing)
×
1890
          return;
×
1891
      }
×
1892

1893
      // 1. Point the plugin at the right slice of the buffer.
1894
      //    The previous code passed channel.data()/out_channel.data() with
1895
      //    no `offset`, which made the plugin process frames 0..samples
1896
      //    regardless of where the current tick actually started.
1897
      ins_pointer[0] = const_cast<double*>(in_channels[current_channel].data())
×
1898
                       + offset;
×
1899
      auto* out_chan = out_channels[current_channel].data() + offset;
×
1900
      outs_pointer[0] = out_chan;
×
1901
      std::fill_n(out_chan, samples, 0.0);
×
1902

1903
      // 2. Process this channel
1904
      clap_process_t process{};
×
1905
      process.frames_count = samples;
×
1906
      process.audio_inputs = &in_buffer;
×
1907
      process.audio_outputs = &out_buffer;
×
1908
      process.audio_inputs_count = 1;
×
1909
      process.audio_outputs_count = 1;
×
1910
      build_voice_input(m_voice_input, current_channel);
×
1911
      do_process(cur, process, m_voice_input, m_output_events, current_channel);
×
1912
      if(current_channel == 0)
×
1913
        replicate_voice0_state(cur, m_input_events);
×
1914
    }
×
1915

1916
    dispatch_param_outputs();
×
1917
  }
×
1918
};
1919

1920
struct clap_process final : public ossia::node_process
1921
{
1922
  using ossia::node_process::node_process;
1923
  void start() override { }
×
1924
  void stop() override
×
1925
  {
1926
    auto* clap = static_cast<clap_node*>(this->node.get());
×
1927

1928
    if(clap->m_processing)
×
1929
    {
1930
      clap->stop_plugin(clap->m_instance.plugin);
×
1931
      clap->m_processing = false;
×
1932
    }
×
1933
  }
×
1934
  void pause() override { }
×
1935
  void resume() override { }
×
1936
};
1937
struct clap_mono_process final : public ossia::node_process
1938
{
1939
  using ossia::node_process::node_process;
1940
  void start() override { }
×
1941
  void stop() override
×
1942
  {
1943
    auto* clap = static_cast<clap_node_mono*>(this->node.get());
×
1944

1945
    // Stop each polyphonic instance individually. The old version always
1946
    // stopped m_instance.plugin (m_poly[0]) once per processing entry,
1947
    // leaving instances 1..N in {active, processing} on shutdown, which
1948
    // violates the CLAP state machine.
1949
    for(auto& plug : clap->m_poly)
×
1950
    {
1951
      if(plug.processing && plug.plugin)
×
1952
      {
1953
        plug.plugin->stop_processing(plug.plugin);
×
1954
        plug.processing = false;
×
1955
      }
×
1956
    }
1957
  }
×
1958
  void pause() override { }
×
1959
  void resume() override { }
×
1960
};
1961

1962
Executor::Executor(Clap::Model& proc, const Execution::Context& ctx, QObject* parent)
×
1963
    : Execution::ProcessComponent_T<Clap::Model, ossia::node_process>{
×
1964
          proc, ctx, "ClapComponent", parent}
×
1965
{
×
1966
  proc.setExecuting(true);
×
1967

1968
  const auto& h = proc.handle();
×
1969
  if(!h)
×
1970
    throw std::runtime_error("Plug-in unavailable");
×
1971

1972
  const bool monophonic = proc.midiInputs().size() == 0 && proc.audioInputs().size() == 1
×
1973
                          && proc.audioInputs()[0].channel_count == 1
×
1974
                          && proc.audioOutputs().size() == 1
×
1975
                          && proc.audioOutputs()[0].channel_count == 1;
×
1976

1977
  auto& e = *ctx.execState;
×
1978
  std::shared_ptr<clap_node_base> clap{};
×
1979
  if(monophonic)
×
1980
  {
1981
    if(proc.supports64())
×
1982
    {
1983
      qDebug() << "CLAP: clap_node_mono_64";
×
1984
      auto node = ossia::make_node<clap_node_mono_64>(
×
1985
          *ctx.execState, proc, *h, e.sampleRate, e.bufferSize);
×
1986
      clap = node;
×
1987
      this->node = node;
×
1988
      m_ossia_process = std::make_shared<clap_mono_process>(node);
×
1989
    }
×
1990
    else
1991
    {
1992
      qDebug() << "CLAP: clap_node_mono_32";
×
1993
      auto node = ossia::make_node<clap_node_mono_32>(
×
1994
          *ctx.execState, proc, *h, e.sampleRate, e.bufferSize);
×
1995
      clap = node;
×
1996
      this->node = node;
×
1997
      m_ossia_process = std::make_shared<clap_mono_process>(node);
×
1998
    }
×
1999

2000
    // Drive the polyphonic-pool grower on the main thread. The mono node
2001
    // pre-allocates 8 instances in its constructor; if the upstream channel
2002
    // count exceeds that the audio thread bumps m_requested_pool and this
2003
    // timer creates more on the main thread (CLAP requires create_plugin /
2004
    // init / activate / state.load to all run there).
2005
    constexpr std::size_t kInitialPoly = 8;
×
2006
    m_pool_sample_rate = e.sampleRate;
×
2007
    m_pool_buffer_size = e.bufferSize;
×
2008
    m_pool_pushed = kInitialPoly;
×
2009
    m_pool_max_requested = kInitialPoly;
×
2010
    m_grow_timer = new QTimer(this);
×
2011
    m_grow_timer->setInterval(50);
×
2012
    connect(m_grow_timer, &QTimer::timeout, this, [this] { grow_pool_tick(); });
×
2013
    m_grow_timer->start();
×
2014
  }
×
2015
  else
2016
  {
2017
    if(proc.supports64())
×
2018
    {
2019
      qDebug() << "CLAP: clap_node_64";
×
2020
      auto node = ossia::make_node<clap_node_64>(
×
2021
          *ctx.execState, proc, *h, e.sampleRate, e.bufferSize);
×
2022
      clap = node;
×
2023
      this->node = node;
×
2024
      m_ossia_process = std::make_shared<clap_process>(node);
×
2025
    }
×
2026
    else
2027
    {
2028
      qDebug() << "CLAP: clap_node_32";
×
2029
      auto node = ossia::make_node<clap_node_32>(
×
2030
          *ctx.execState, proc, *h, e.sampleRate, e.bufferSize);
×
2031
      clap = node;
×
2032
      this->node = node;
×
2033
      m_ossia_process = std::make_shared<clap_process>(node);
×
2034
    }
×
2035
  }
2036

2037
  SCORE_ASSERT(clap);
×
2038

2039
  // Connect control inlet changes to the executor
2040
  // Note: only reelvant for the polyphonic mode as the main mode is done on the
2041
  // main thread
2042
  std::size_t control_idx = 0;
×
2043
  for(auto* inlet : proc.inlets())
×
2044
  {
2045
    if(auto* control = qobject_cast<Process::ControlInlet*>(inlet))
×
2046
    {
2047
      auto* inl = clap->parameter_ins[control_idx];
×
2048

2049
      control->setupExecution(*inl, this);
×
2050
      connect(
×
2051
          control, &Process::ControlInlet::valueChanged, this,
×
2052
          [this, inl](const ossia::value& v) {
×
2053
        if(this->process().currentlyReadingValues)
×
2054
          return;
×
2055
        auto weak_self = std::weak_ptr{this->node};
×
2056
        in_exec([inl, val = v, node = weak_self]() mutable {
×
2057
          if(auto n = node.lock())
×
2058
          {
2059
            inl->target<ossia::value_port>()->write_value(std::move(val), 0);
×
2060
          }
×
2061
        });
×
2062
      });
×
2063
      control_idx++;
×
2064
    }
×
2065
  }
2066
}
×
2067

2068
void Executor::grow_pool_tick()
×
2069
{
2070
  auto mono = std::dynamic_pointer_cast<clap_node_mono>(this->node);
×
2071
  if(!mono)
×
2072
    return;
×
2073

2074
  // Promote brief spikes into the long-term high-water mark so we don't
2075
  // miss a 2 → 512 → 2 transient.
2076
  const auto requested
×
2077
      = mono->m_requested_pool.load(std::memory_order_acquire);
×
2078
  if(requested > m_pool_max_requested)
×
2079
    m_pool_max_requested = requested;
×
2080

2081
  if(m_pool_pushed >= m_pool_max_requested)
×
2082
    return;
×
2083

2084
  // Snapshot primary's state so new instances inherit loaded models/presets.
2085
  auto handle = this->process().handle();
×
2086
  if(!handle || !handle->plugin)
×
2087
    return;
×
2088
  const QByteArray state = snapshot_clap_state(handle->plugin);
×
2089

2090
  // Few per tick: each instance can be expensive (e.g. AIDA-X neural model load).
2091
  constexpr std::size_t kBatch = 4;
×
2092
  const std::size_t to_add
×
2093
      = std::min(kBatch, m_pool_max_requested - m_pool_pushed);
×
2094
  auto weak_node = std::weak_ptr{this->node};
×
2095
  for(std::size_t i = 0; i < to_add; ++i)
×
2096
  {
2097
    try
2098
    {
2099
      auto p
2100
          = mono->make_poly_instance(m_pool_sample_rate, m_pool_buffer_size, state);
×
2101
      // Orphaned if the node dies before in_exec fires; post destroy to main thread.
2102
      in_exec([weak_node, p]() mutable {
×
2103
        if(auto n = weak_node.lock())
×
2104
        {
2105
          static_cast<clap_node_mono*>(n.get())->append_poly(p);
×
2106
        }
×
2107
        else
2108
        {
2109
          QMetaObject::invokeMethod(QCoreApplication::instance(), [p] {
×
2110
            if(!p.plugin)
×
2111
              return;
×
2112
            if(p.activated)
×
2113
              p.plugin->deactivate(p.plugin);
×
2114
            p.plugin->destroy(p.plugin);
×
2115
          });
×
2116
        }
2117
      });
×
2118
      ++m_pool_pushed;
×
2119
    }
×
2120
    catch(const std::exception& ex)
2121
    {
2122
      qWarning() << "CLAP: failed to grow polyphonic pool:" << ex.what();
×
2123
      break;
2124
    }
×
2125
  }
×
2126
}
×
2127
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc