iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
IPlugCLAP.cpp
1/*
2 ==============================================================================
3
4 This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5
6 See LICENSE.txt for more info.
7
8 ==============================================================================
9*/
10
11#include <algorithm>
12#include <cstdio>
13
14#include "IPlugCLAP.h"
15#include "IPlugPluginBase.h"
16#include "plugin.hxx"
17#include "host-proxy.hxx"
18
19// TODO - respond to situations in which parameters can't be pushed (search try_push)
20// Keep in the queue or discard?? - up to us?
21
22using namespace iplug;
23
24void ClapNameCopy(char* destination, const char* source)
25{
26 strncpy(destination, source, CLAP_NAME_SIZE);
27 destination[CLAP_NAME_SIZE - 1] = 0;
28}
29
30IPlugCLAP::IPlugCLAP(const InstanceInfo& info, const Config& config)
31 : IPlugAPIBase(config, kAPICLAP)
32 , IPlugProcessor(config, kAPICLAP)
33 , ClapPluginHelper(info.mDesc, info.mHost)
34{
35 Trace(TRACELOC, "%s", config.pluginName);
36
37 int version = 0;
38
39 if (CStringHasContents(info.mHost->version))
40 {
41 // TODO - check host version string
42 int ver, rmaj, rmin;
43 sscanf(info.mHost->version, "%d.%d.%d", &ver, &rmaj, &rmin);
44 version = (ver << 16) + (rmaj << 8) + rmin;
45 }
46
47 // Create space to store audio pointers
48 int nChans = RequiredChannels();
49 mAudioIO32.Resize(nChans);
50 mAudioIO64.Resize(nChans);
51
52 SetHost(info.mHost->name, version);
53 CreateTimer();
54}
55
56uint32_t IPlugCLAP::tailGet() const noexcept
57{
58 return GetTailIsInfinite() ? std::numeric_limits<uint32_t>::max() : GetTailSize();
59}
60
61void IPlugCLAP::FlushParamsIfNeeded()
62{
63 if (!isProcessing())
64 runOnMainThread([&](){ if (!isBeingDestroyed()) GetClapHost().paramsRequestFlush(); });
65}
66
68{
69 mParamValuesToHost.PushFromArgs(ParamToHost::Type::Begin, idx, 0.0);
70 FlushParamsIfNeeded();
71}
72
73void IPlugCLAP::InformHostOfParamChange(int idx, double normalizedValue)
74{
75 const IParam* pParam = GetParam(idx);
76 const bool isDoubleType = pParam->Type() == IParam::kTypeDouble;
77 const double value = isDoubleType ? normalizedValue : pParam->FromNormalized(normalizedValue);
78
79 mParamValuesToHost.PushFromArgs(ParamToHost::Type::Value, idx, value);
80 FlushParamsIfNeeded();
81}
82
84{
85 mParamValuesToHost.PushFromArgs(ParamToHost::Type::End, idx, 0.0);
86 FlushParamsIfNeeded();
87}
88
89bool IPlugCLAP::EditorResize(int viewWidth, int viewHeight)
90{
91 if (HasUI())
92 {
93 if (viewWidth != GetEditorWidth() || viewHeight != GetEditorHeight())
94 {
95 GetClapHost().guiRequestResize(viewWidth, viewHeight);
96 }
97
98 SetEditorSize(viewWidth, viewHeight);
99 }
100
101 return true;
102}
103
104// IPlugProcessor
105void IPlugCLAP::SetTailSize(int samples)
106{
108
109 if (GetClapHost().canUseTail())
110 mTailUpdate = true;
111}
112
113void IPlugCLAP::SetLatency(int samples)
114{
116
117 if (GetClapHost().canUseLatency())
118 {
119 // If active request restart on the main thread (or update the host)
120 if (isActive())
121 {
122 mLatencyUpdate = true;
123 runOnMainThread([&](){ if (!isBeingDestroyed()) GetClapHost().requestRestart(); });
124 }
125 else
126 {
127 runOnMainThread([&](){ if (!isBeingDestroyed()) GetClapHost().latencyChanged(); });
128 }
129 }
130}
131
133{
134 mMidiToHost.Add(msg);
135 return true;
136}
137
139{
140 // TODO - don't copy the data
141 SysExData data(msg.mOffset, msg.mSize, msg.mData);
142 mSysExToHost.Add(data);
143 return true;
144}
145
146// clap_plugin
147bool IPlugCLAP::init() noexcept
148{
149 SetDefaultConfig();
150
151 return true;
152}
153
154bool IPlugCLAP::activate(double sampleRate, uint32_t minFrameCount, uint32_t maxFrameCount) noexcept
155{
156 SetBlockSize(maxFrameCount);
157 SetSampleRate(sampleRate);
158 OnActivate(true);
159 OnParamReset(kReset);
160 OnReset();
161
162 mHostHasTail = GetClapHost().canUseTail();
163 mTailCount = 0;
164
165 return true;
166}
167
168void IPlugCLAP::deactivate() noexcept
169{
170 OnActivate(false);
171
172 if (mLatencyUpdate)
173 {
174 GetClapHost().latencyChanged();
175 mLatencyUpdate = false;
176 }
177
178 // TODO - should we clear mTailUpdate here or elsewhere?
179}
180
181template <typename T>
182const T* ClapEventCast(const clap_event_header_t* event)
183{
184 return reinterpret_cast<const T*>(event);
185}
186
187template <typename T>
188bool InputIsSilent(const T* data, int nFrames)
189{
190 // For local tail processing find non-zero inputs
191 auto isZero = [](T x) { return x == T(0); };
192
193 return std::find_if_not(data, data + nFrames, isZero) == (data + nFrames);
194}
195
196clap_process_status IPlugCLAP::process(const clap_process* pProcess) noexcept
197{
198 IMidiMsg msg;
199 SysExData sysEx;
200
201 // Transport Info
202 if (pProcess->transport)
203 {
204 ITimeInfo timeInfo;
205
206 auto pTransport = pProcess->transport;
207
208 constexpr double beatFactor = static_cast<double>(CLAP_BEATTIME_FACTOR);
209 constexpr double secFactor = static_cast<double>(CLAP_SECTIME_FACTOR);
210
211 if (pTransport->flags & CLAP_TRANSPORT_HAS_TEMPO)
212 timeInfo.mTempo = pTransport->tempo;
213
214 // N.B. If there is no seconds timeline there is no way to get the sample position (the plugin one is not global)
215 if (pTransport->flags & CLAP_TRANSPORT_HAS_SECONDS_TIMELINE)
216 timeInfo.mSamplePos = (GetSampleRate() * static_cast<double>(pTransport->song_pos_seconds) / secFactor);
217
218 if (pTransport->flags & CLAP_TRANSPORT_HAS_BEATS_TIMELINE)
219 {
220 timeInfo.mPPQPos = static_cast<double>(pTransport->song_pos_beats) / beatFactor;
221 timeInfo.mLastBar = static_cast<double>(pTransport->bar_start) / beatFactor;
222 timeInfo.mCycleStart = static_cast<double>(pTransport->loop_start_beats) / beatFactor;
223 timeInfo.mCycleEnd = static_cast<double>(pTransport->loop_end_beats) / beatFactor;
224 }
225
226 if (pTransport->flags & CLAP_TRANSPORT_HAS_TIME_SIGNATURE)
227 {
228 timeInfo.mNumerator = static_cast<int>(pTransport->tsig_num);
229 timeInfo.mDenominator = static_cast<int>(pTransport->tsig_denom);
230 }
231
232 timeInfo.mTransportIsRunning = pTransport->flags & CLAP_TRANSPORT_IS_PLAYING;
233 timeInfo.mTransportLoopEnabled = pTransport->flags & CLAP_TRANSPORT_IS_LOOP_ACTIVE;
234
235 SetTimeInfo(timeInfo);
236 }
237
238 // Input Events
239 ProcessInputEvents(pProcess->in_events);
240
241 while (mMidiMsgsFromEditor.Pop(msg))
242 {
243 ProcessMidiMsg(msg);
244 }
245
246 while (mSysExDataFromEditor.Pop(sysEx))
247 {
248 SendSysEx(ISysEx(sysEx.mOffset, sysEx.mData, sysEx.mSize));
249 }
250
251 // Do Audio Processing!
252 int nIns = 0;
253 int nOuts = 0;
254 int nFrames = pProcess->frames_count;
255
256 // Local tail handling
257 bool localTail = !mHostHasTail && !GetTailIsInfinite() && GetTailSize();
258 bool insQuiet = true;
259
260 // Sum IO channels
261 for (uint32_t i = 0; i < pProcess->audio_inputs_count; i++)
262 nIns += static_cast<int>(pProcess->audio_inputs[i].channel_count);
263
264 for (uint32_t i = 0; i < pProcess->audio_outputs_count; i++)
265 nOuts += static_cast<int>(pProcess->audio_outputs[i].channel_count);
266
267 // Check the format
268 bool format64 = false;
269
270 if (pProcess->audio_inputs_count && pProcess->audio_inputs && pProcess->audio_inputs[0].channel_count)
271 format64 = pProcess->audio_inputs[0].data64;
272 else if (pProcess->audio_outputs_count && pProcess->audio_outputs && pProcess->audio_outputs[0].channel_count)
273 format64 = pProcess->audio_outputs[0].data64;
274
275 // Assert that all formats match
276#ifndef NDEBUG
277 for (uint32_t i = 0; i < pProcess->audio_inputs_count; i++)
278 {
279 auto bus = pProcess->audio_inputs[i];
280 assert(!bus.channel_count || format64 == static_cast<bool>(bus.data64));
281 }
282
283 for (uint32_t i = 0; i < pProcess->audio_outputs_count; i++)
284 {
285 auto bus = pProcess->audio_outputs[i];
286 assert(!bus.channel_count || format64 == static_cast<bool>(bus.data64));
287 }
288#endif
289
290 SetChannelConnections(ERoute::kInput, 0, MaxNChannels(ERoute::kInput), false);
291 SetChannelConnections(ERoute::kInput, 0, nIns, true);
292
293 if (nIns > 0)
294 {
295 // Copy and attach buffer pointers
296 if (format64)
297 {
298 for (uint32_t i = 0, k = 0; i < pProcess->audio_inputs_count; i++)
299 {
300 auto bus = pProcess->audio_inputs[i];
301
302 for (uint32_t j = 0; j < bus.channel_count; j++, k++)
303 {
304 mAudioIO64.Get()[k] = bus.data64[j];
305
306 // For local tail processing check for non-zero inputs
307
308 if (localTail && insQuiet)
309 insQuiet = InputIsSilent(bus.data64[j], nFrames);
310 }
311 }
312
313 AttachBuffers(ERoute::kInput, 0, nIns, mAudioIO64.Get(), nFrames);
314 }
315 else
316 {
317 for (uint32_t i = 0, k = 0; i < pProcess->audio_inputs_count; i++)
318 {
319 auto bus = pProcess->audio_inputs[i];
320
321 for (uint32_t j = 0; j < bus.channel_count; j++, k++)
322 {
323 mAudioIO32.Get()[k] = bus.data32[j];
324
325 // For local tail processing check for non-zero inputs
326 if (localTail && insQuiet)
327 insQuiet = InputIsSilent(bus.data32[j], nFrames);
328 }
329 }
330
331 AttachBuffers(ERoute::kInput, 0, nIns, mAudioIO32.Get(), nFrames);
332 }
333 }
334
335 SetChannelConnections(ERoute::kOutput, 0, MaxNChannels(ERoute::kOutput), false);
336 SetChannelConnections(ERoute::kOutput, 0, nOuts, true);
337
338 if (nOuts > 0)
339 {
340 // Copy and attach buffer pointers
341 if (format64)
342 {
343 for (uint32_t i = 0, k = 0; i < pProcess->audio_outputs_count; i++)
344 {
345 auto bus = pProcess->audio_outputs[i];
346 for (uint32_t j = 0; j < bus.channel_count; j++, k++)
347 mAudioIO64.Get()[k] = bus.data64[j];
348 }
349
350 AttachBuffers(ERoute::kOutput, 0, nOuts, mAudioIO64.Get(), nFrames);
351 }
352 else
353 {
354 for (uint32_t i = 0, k = 0; i < pProcess->audio_outputs_count; i++)
355 {
356 auto bus = pProcess->audio_outputs[i];
357 for (uint32_t j = 0; j < bus.channel_count; j++, k++)
358 mAudioIO32.Get()[k] = bus.data32[j];
359 }
360
361 AttachBuffers(ERoute::kOutput, 0, nOuts, mAudioIO32.Get(), nFrames);
362 }
363 }
364
365 if (format64)
366 ProcessBuffers(0.0, nFrames);
367 else
368 ProcessBuffers(0.f, nFrames);
369
370 // Send Events Out (Parameters and MIDI)
371 ProcessOutputEvents(pProcess->out_events, nFrames);
372
373 // Update tail if relevant
374 if (mTailUpdate)
375 {
376 GetClapHost().tailChanged();
377 mTailUpdate = false;
378 }
379
380 // Local tail handling
381 if (mHostHasTail)
382 return CLAP_PROCESS_TAIL;
383
384#if PLUG_TYPE == 0 // Only implement local tail for effects
385 // No tail
386 if (!GetTailSize())
387 return CLAP_PROCESS_CONTINUE_IF_NOT_QUIET;
388
389 // Infinite tail
390 if (GetTailIsInfinite())
391 return CLAP_PROCESS_CONTINUE;
392
393 // Finite tail
394 mTailCount = insQuiet ? std::min(mTailCount + nFrames, GetTailSize()) : 0;
395
396 return mTailCount < GetTailSize() ? CLAP_PROCESS_CONTINUE : CLAP_PROCESS_SLEEP;
397
398#else
399 return CLAP_PROCESS_CONTINUE;
400#endif
401}
402
403// clap_plugin_render
404bool IPlugCLAP::renderSetMode(clap_plugin_render_mode mode) noexcept
405{
406 SetRenderingOffline(mode == CLAP_RENDER_OFFLINE);
407 return true;
408}
409
410// clap_plugin_state
411bool IPlugCLAP::stateSave(const clap_ostream* pStream) noexcept
412{
413 IByteChunk chunk;
414
415 if (!SerializeState(chunk))
416 return false;
417
418 return pStream->write(pStream, chunk.GetData(), chunk.Size()) == chunk.Size();
419}
420
421bool IPlugCLAP::stateLoad(const clap_istream* pStream) noexcept
422{
423 constexpr int bytesPerBlock = 256;
424 char buffer[bytesPerBlock];
425 int64_t bytesRead = 0;
426
427 IByteChunk chunk;
428
429 while ((bytesRead = pStream->read(pStream, buffer, bytesPerBlock)) > 0)
430 chunk.PutBytes(buffer, static_cast<int>(bytesRead));
431
432 if (bytesRead != 0)
433 return false;
434
435 bool restoredOK = UnserializeState(chunk, 0) >= 0;
436
437 if (restoredOK)
438 OnRestoreState();
439
440 return restoredOK;
441}
442
443// clap_plugin_params
444bool IPlugCLAP::paramsInfo(uint32_t paramIdx, clap_param_info* pInfo) const noexcept
445{
446 assert(MAX_PARAM_NAME_LEN <= CLAP_NAME_SIZE && "iPlug parameter name size exceeds CLAP maximum");
447 assert(MAX_PARAM_GROUP_LEN <= CLAP_PATH_SIZE && "iPlug group name size exceeds CLAP maximum");
448
449 const IParam* pParam = GetParam(paramIdx);
450 const bool isDoubleType = pParam->Type() == IParam::kTypeDouble;
451
452 clap_param_info_flags flags = CLAP_PARAM_REQUIRES_PROCESS; // TO DO - check this with Alex B
453
454 if (!isDoubleType)
455 flags |= CLAP_PARAM_IS_STEPPED;
456 if (pParam->GetCanAutomate())
457 flags |= CLAP_PARAM_IS_AUTOMATABLE;
458
459 pInfo->id = paramIdx;
460 pInfo->flags = flags;
461 pInfo->cookie = nullptr;
462
463 strcpy(pInfo->name, pParam->GetName());
464 strcpy(pInfo->module, pParam->GetGroup());
465
466 // Values
467 pInfo->min_value = isDoubleType ? 0.0 : pParam->GetMin();
468 pInfo->max_value = isDoubleType ? 1.0 : pParam->GetMax();
469 pInfo->default_value = pParam->GetDefault(isDoubleType);
470
471 return true;
472}
473
474bool IPlugCLAP::paramsValue(clap_id paramIdx, double* pValue) noexcept
475{
476 const IParam* pParam = GetParam(paramIdx);
477 const bool isDoubleType = pParam->Type() == IParam::kTypeDouble;
478 *pValue = isDoubleType ? pParam->GetNormalized() : pParam->Value();
479 return true;
480}
481
482bool IPlugCLAP::paramsValueToText(clap_id paramIdx, double value, char* display, uint32_t size) noexcept
483{
484 const IParam* pParam = GetParam(paramIdx);
485 const bool isDoubleType = pParam->Type() == IParam::kTypeDouble;
486
487 WDL_String str;
488
489 pParam->GetDisplay(value, isDoubleType, str);
490
491 // Add Label
492 if (CStringHasContents(pParam->GetLabel()))
493 {
494 str.Append(" ");
495 str.Append(pParam->GetLabel());
496 }
497
498 if (size < str.GetLength())
499 return false;
500
501 strcpy(display, str.Get());
502 return true;
503}
504
505bool IPlugCLAP::paramsTextToValue(clap_id paramIdx, const char* display, double* pValue) noexcept
506{
507 const IParam* pParam = GetParam(paramIdx);
508 const bool isDoubleType = pParam->Type() == IParam::kTypeDouble;
509 const double paramValue = pParam->StringToValue(display);
510
511 *pValue = isDoubleType ? pParam->ToNormalized(paramValue) : paramValue;
512 return true;
513}
514
515void IPlugCLAP::paramsFlush(const clap_input_events* pInputParamChanges, const clap_output_events* pOutputParamChanges) noexcept
516{
517 ProcessInputEvents(pInputParamChanges);
518 ProcessOutputParams(pOutputParamChanges);
519}
520
521void IPlugCLAP::ProcessInputEvents(const clap_input_events* pInputEvents) noexcept
522{
523 IMidiMsg msg;
524
525 if (pInputEvents)
526 {
527 for (int i = 0; i < pInputEvents->size(pInputEvents); i++)
528 {
529 auto pEvent = pInputEvents->get(pInputEvents, i);
530
531 if (pEvent->space_id != CLAP_CORE_EVENT_SPACE_ID)
532 continue;
533
534 switch (pEvent->type)
535 {
536 case CLAP_EVENT_NOTE_ON:
537 {
538 // N.B. velocity stored 0-1
539 auto pNote = ClapEventCast<clap_event_note>(pEvent);
540 auto velocity = static_cast<int>(std::round(pNote->velocity * 127.0));
541 msg.MakeNoteOnMsg(pNote->key, velocity, pEvent->time, pNote->channel);
542 ProcessMidiMsg(msg);
543 mMidiMsgsFromProcessor.Push(msg);
544 break;
545 }
546
547 case CLAP_EVENT_NOTE_OFF:
548 {
549 auto pNote = ClapEventCast<clap_event_note>(pEvent);
550 msg.MakeNoteOffMsg(pNote->key, pEvent->time, pNote->channel);
551 ProcessMidiMsg(msg);
552 mMidiMsgsFromProcessor.Push(msg);
553 break;
554 }
555
556 case CLAP_EVENT_MIDI:
557 {
558 auto pMidiEvent = ClapEventCast<clap_event_midi>(pEvent);
559 msg = IMidiMsg(pEvent->time, pMidiEvent->data[0], pMidiEvent->data[1], pMidiEvent->data[2]);
560 ProcessMidiMsg(msg);
561 mMidiMsgsFromProcessor.Push(msg);
562 break;
563 }
564
565 case CLAP_EVENT_MIDI_SYSEX:
566 {
567 auto pSysexEvent = ClapEventCast<clap_event_midi_sysex>(pEvent);
568 ISysEx sysEx(pEvent->time, pSysexEvent->buffer, pSysexEvent->size);
569 ProcessSysEx(sysEx);
570 mSysExDataFromProcessor.PushFromArgs(sysEx.mOffset, sysEx.mSize, sysEx.mData);
571 break;
572 }
573
574 case CLAP_EVENT_PARAM_VALUE:
575 {
576 auto pParamValue = ClapEventCast<clap_event_param_value>(pEvent);
577
578 int paramIdx = pParamValue->param_id;
579 double value = pParamValue->value;
580
581 IParam* pParam = GetParam(paramIdx);
582 const bool isDoubleType = pParam->Type() == IParam::kTypeDouble;
583
584 if (isDoubleType)
585 pParam->SetNormalized(value);
586 else
587 pParam->Set(value);
588
589 SendParameterValueFromAPI(paramIdx, value, isDoubleType);
590 OnParamChange(paramIdx, EParamSource::kHost, pEvent->time);
591 break;
592 }
593
594 default:
595 break;
596 }
597 }
598 }
599}
600
601void IPlugCLAP::ProcessOutputParams(const clap_output_events* pOutputParamChanges) noexcept
602{
603 ParamToHost change;
604
605 while (mParamValuesToHost.Pop(change))
606 {
607 // Construct output stream
608 bool isValue = change.type() == CLAP_EVENT_PARAM_VALUE;
609
610 clap_event_header_t header;
611
612 // N.B. - paramaters output here almost certainly come from the UI
613 // They cannot be set with a sample offset (this is a limitation of the current IPlug2 API)
614
615 header.size = isValue ? sizeof(clap_event_param_value) : sizeof(clap_event_param_gesture);
616 header.time = 0;
617 header.space_id = CLAP_CORE_EVENT_SPACE_ID;
618 header.type = change.type();
619 header.flags = 0;
620
621 if (isValue)
622 {
623 clap_event_param_value event { header, change.idx(), nullptr, -1, -1, -1, -1, change.value() };
624 pOutputParamChanges->try_push(pOutputParamChanges, &event.header);
625 }
626 else
627 {
628 clap_event_param_gesture event { header, change.idx() };
629 pOutputParamChanges->try_push(pOutputParamChanges, &event.header);
630 }
631 }
632}
633
634void IPlugCLAP::ProcessOutputEvents(const clap_output_events* pOutputEvents, int nFrames) noexcept
635{
636 // N.B. Midi events and sysEx events are ordered by the respective queues
637 // Here we ensure correct ordering between the two queues
638 // Parameters can only be sent at the start of each block so are processed first
639
640 ProcessOutputParams(pOutputEvents);
641
642 if (pOutputEvents)
643 {
644 clap_event_header_t header;
645
646 while (mMidiToHost.ToDo() || mSysExToHost.ToDo())
647 {
648 int midiMsgOffset = nFrames;
649 int sysExOffset = nFrames;
650
651 // Look at the next two items to ensure correct ordering
652 if (mMidiToHost.ToDo())
653 midiMsgOffset = mMidiToHost.Peek().mOffset;
654
655 if (mSysExToHost.ToDo())
656 sysExOffset = mSysExToHost.Peek().mOffset;
657
658 // Don't move beyond the current frame
659 if (std::min(midiMsgOffset, sysExOffset) >= nFrames)
660 break;
661
662 if (sysExOffset <= midiMsgOffset)
663 {
664 auto data = mSysExToHost.Peek();
665
666 uint32_t dataSize = static_cast<uint32_t>(data.mSize);
667
668 header.size = sizeof(clap_event_midi_sysex);
669 header.time = data.mOffset;
670 header.space_id = CLAP_CORE_EVENT_SPACE_ID;
671 header.type = CLAP_EVENT_MIDI_SYSEX;
672 header.flags = 0;
673
674 clap_event_midi_sysex sysex_event { header, 0, data.mData, dataSize };
675
676 pOutputEvents->try_push(pOutputEvents, &sysex_event.header);
677
678 mSysExToHost.Remove();
679 }
680 else
681 {
682 auto msg = mMidiToHost.Peek();
683 auto status = msg.mStatus;
684
685 // Construct output stream
686 header.size = sizeof(clap_event_param_value);
687 header.time = msg.mOffset;
688 header.space_id = CLAP_CORE_EVENT_SPACE_ID;
689 header.type = CLAP_EVENT_MIDI;
690 header.flags = 0;
691
692 if (msg.StatusMsg() == IMidiMsg::kNoteOn)
693 header.type = CLAP_EVENT_NOTE_ON;
694
695 if (msg.StatusMsg() == IMidiMsg::kNoteOff)
696 header.type = CLAP_EVENT_NOTE_OFF;
697
698 if (header.type == CLAP_EVENT_NOTE_ON || header.type == CLAP_EVENT_NOTE_OFF)
699 {
700 int16_t channel = static_cast<int16_t>(msg.Channel());
701 clap_event_note note_event { header, -1, 0, channel, msg.mData1, static_cast<double>(msg.mData2) / 127.0};
702 pOutputEvents->try_push(pOutputEvents, &note_event.header);
703 }
704 else
705 {
706 clap_event_midi midi_event { header, 0, { status, msg.mData1, msg.mData2 } };
707 pOutputEvents->try_push(pOutputEvents, &midi_event.header);
708 }
709
710 mMidiToHost.Remove();
711 }
712 }
713
714 mMidiToHost.Flush(nFrames);
715 mSysExToHost.Flush(nFrames);
716 }
717}
718
719const char* ClapPortType(uint32_t nChans)
720{
721 // TODO - add support for surround/ambisonics or allow user setting?
722 return nChans == 2 ? CLAP_PORT_STEREO : (nChans == 1 ? CLAP_PORT_MONO : nullptr);
723}
724
725bool IPlugCLAP::implementsAudioPorts() const noexcept
726{
727 return MaxNBuses(ERoute::kInput) || MaxNBuses(ERoute::kOutput);
728}
729
730uint32_t IPlugCLAP::audioPortsCount(bool isInput) const noexcept
731{
732 return NBuses(isInput ? ERoute::kInput : ERoute::kOutput);
733}
734
735bool IPlugCLAP::audioPortsInfo(uint32_t index, bool isInput, clap_audio_port_info* pInfo) const noexcept
736{
737 // TODO - should we use in place pairs?
738 WDL_String busName;
739
740 const auto direction = isInput ? ERoute::kInput : ERoute::kOutput;
741 const auto nBuses = NBuses(direction);
742 const auto nChans = NChannels(direction, index);
743
744 GetBusName(direction, index, nBuses, busName);
745
746 constexpr uint32_t bitFlags = CLAP_AUDIO_PORT_SUPPORTS_64BITS
747 | CLAP_AUDIO_PORT_PREFERS_64BITS
748 | CLAP_AUDIO_PORT_REQUIRES_COMMON_SAMPLE_SIZE;
749
750 pInfo->id = index;
751 ClapNameCopy(pInfo->name, busName.Get());
752 pInfo->flags = !index ? bitFlags | CLAP_AUDIO_PORT_IS_MAIN : bitFlags;
753 pInfo->channel_count = nChans;
754 pInfo->port_type = ClapPortType(pInfo->channel_count);
755 pInfo->in_place_pair = CLAP_INVALID_ID;
756 return true;
757}
758
759bool IPlugCLAP::implementsAudioPortsConfig() const noexcept
760{
761 return audioPortsConfigCount();
762}
763
764uint32_t IPlugCLAP::audioPortsConfigCount() const noexcept
765{
766 return static_cast<uint32_t>(NIOConfigs());
767}
768
769bool IPlugCLAP::audioPortsGetConfig(uint32_t index, clap_audio_ports_config* pConfig) const noexcept
770{
771 if (index >= audioPortsConfigCount())
772 return false;
773
774 WDL_String configName;
775
776 // TODO - review naming or add option for names...
777 auto getNChans = [&](ERoute direction, int bus)
778 {
779 return static_cast<uint32_t>(NChannels(direction, static_cast<uint32_t>(bus), index));
780 };
781
782 auto getDirectionName = [&](ERoute direction)
783 {
784 // N.B. Configs in iPlug2 currently have no names so we reconstruct the strings...
785 configName.AppendFormatted(CLAP_NAME_SIZE, "%d", getNChans(direction, 0));
786
787 for (int i = 0; i < NBuses(direction, index); i++)
788 configName.AppendFormatted(CLAP_NAME_SIZE, ".%d", getNChans(direction, i));
789 };
790
791 getDirectionName(kInput);
792 configName.Append("-");
793 getDirectionName(kOutput);
794
795 pConfig->id = index;
796 ClapNameCopy(pConfig->name, configName.Get());
797
798 pConfig->input_port_count = static_cast<uint32_t>(NBuses(kInput, index));
799 pConfig->output_port_count = static_cast<uint32_t>(NBuses(kOutput, index));
800
801 pConfig->has_main_input = pConfig->input_port_count > 1;
802 pConfig->main_input_channel_count = pConfig->has_main_input ? getNChans(kInput, 0) : 0;
803 pConfig->main_input_port_type = ClapPortType(pConfig->main_input_channel_count);
804
805 pConfig->has_main_output = pConfig->output_port_count > 1;
806 pConfig->main_output_channel_count = pConfig->has_main_input ? getNChans(kOutput, 0) : 0;
807 pConfig->main_output_port_type = ClapPortType(pConfig->main_output_channel_count);
808
809 return true;
810}
811
812bool IPlugCLAP::audioPortsSetConfig(clap_id configIdx) noexcept
813{
814 if (configIdx >= audioPortsConfigCount())
815 return false;
816
817 mConfigIdx = static_cast<int>(configIdx);
818
819 return true;
820}
821
822uint32_t IPlugCLAP::notePortsCount(bool isInput) const noexcept
823{
824 if (isInput)
825 return PLUG_DOES_MIDI_IN ? 1 : 0;
826 else
827 return PLUG_DOES_MIDI_OUT ? 1 : 0;
828}
829
830bool IPlugCLAP::notePortsInfo(uint32_t index, bool isInput, clap_note_port_info* pInfo) const noexcept
831{
832 if (isInput)
833 {
834 pInfo->id = index;
835 pInfo->supported_dialects = CLAP_NOTE_DIALECT_MIDI;
836 pInfo->preferred_dialect = CLAP_NOTE_DIALECT_MIDI;
837 ClapNameCopy(pInfo->name, "MIDI Input");
838 }
839 else
840 {
841 pInfo->id = index;
842 pInfo->supported_dialects = CLAP_NOTE_DIALECT_MIDI;
843 pInfo->preferred_dialect = CLAP_NOTE_DIALECT_MIDI;
844 ClapNameCopy(pInfo->name, "MIDI Output");
845 }
846 return true;
847}
848
849// clap_plugin_gui
850bool IPlugCLAP::guiIsApiSupported(const char* api, bool isFloating) noexcept
851{
852#if defined OS_MAC
853 return !isFloating && !strcmp(api, CLAP_WINDOW_API_COCOA);
854#elif defined OS_WIN
855 return !isFloating && !strcmp(api, CLAP_WINDOW_API_WIN32);
856#else
857#error Not Implemented!
858#endif
859}
860
861bool IPlugCLAP::guiSetParent(const clap_window* pWindow) noexcept
862{
863#if defined OS_MAC
864 return GUIWindowAttach(pWindow->cocoa);
865#elif defined OS_WIN
866 return GUIWindowAttach(pWindow->win32);
867#else
868#error Not Implemented!
869#endif
870}
871
872bool IPlugCLAP::implementsGui() const noexcept
873{
874 return HasUI();
875}
876
877bool IPlugCLAP::guiCreate(const char* api, bool isFloating) noexcept
878{
879 return HasUI();
880}
881
882void IPlugCLAP::guiDestroy() noexcept
883{
884 CloseWindow();
885 mGUIOpen = false;
886 mWindow = nullptr;
887}
888
889bool IPlugCLAP::guiShow() noexcept
890{
891 if (HasUI() && !mGUIOpen)
892 {
893 OpenWindow(mWindow);
894 return true;
895 }
896 else
897 {
898 return false;
899 }
900}
901
902bool IPlugCLAP::guiHide() noexcept
903{
904 if (HasUI())
905 {
906 CloseWindow();
907 mGUIOpen = false;
908 return true;
909 }
910 else
911 {
912 return false;
913 }
914}
915
916bool IPlugCLAP::guiCanResize() const noexcept
917{
918 return HasUI() && GetHostResizeEnabled();
919}
920
921bool IPlugCLAP::guiSetScale(double scale) noexcept
922{
923 if (HasUI())
924 {
925 SetScreenScale(static_cast<float>(scale));
926 return true;
927 }
928 else
929 {
930 return false;
931 }
932}
933
934bool IPlugCLAP::guiGetSize(uint32_t* pWidth, uint32_t* pHeight) noexcept
935{
936 TRACE
937
938 if (HasUI())
939 {
940 *pWidth = GetEditorWidth();
941 *pHeight = GetEditorHeight();
942
943 return true;
944 }
945 else
946 {
947 return false;
948 }
949}
950
951bool IPlugCLAP::GUIWindowAttach(void* pWindow) noexcept
952{
953 if (HasUI())
954 {
955 OpenWindow(pWindow);
956 mWindow = pWindow;
957 mGUIOpen = true;
958 return true;
959 }
960 else
961 {
962 return false;
963 }
964}
965
966bool IPlugCLAP::guiAdjustSize(uint32_t* pWidth, uint32_t* pHeight) noexcept
967{
968 Trace(TRACELOC, "width:%i height:%i\n", *pWidth, *pHeight);
969
970 if (HasUI())
971 {
972 int w = *pWidth;
973 int h = *pHeight;
974 ConstrainEditorResize(w, h);
975 *pWidth = w;
976 *pHeight = h;
977
978 return true;
979 }
980 else
981 {
982 return false;
983 }
984}
985
986bool IPlugCLAP::guiSetSize(uint32_t width, uint32_t height) noexcept
987{
988 Trace(TRACELOC, "width:%i height:%i\n", width, height);
989
990 if (HasUI())
991 {
992 OnParentWindowResize(width, height);
993 return true;
994 }
995 else
996 {
997 return false;
998 }
999}
1000
1001// TODO - wildcards (return as -1 chans...)
1002void IPlugCLAP::SetDefaultConfig()
1003{
1004 auto isMatch = [&](int idx, int chans)
1005 {
1006 if (NBuses(ERoute::kOutput, idx) >= 1 && NChannels(ERoute::kOutput, 0, idx) == chans)
1007 {
1008 int numBuses = NBuses(ERoute::kInput, idx);
1009
1010 // Instruments are allowed to match with no inputs
1011 if (IsInstrument() && (numBuses == 0 || NChannels(ERoute::kInput, 0, idx) == 0))
1012 return true;
1013
1014 // Otherwise IO must match
1015 return numBuses >= 1 && NChannels(ERoute::kInput, 0, idx) == chans;
1016 }
1017
1018 return false;
1019 };
1020
1021 auto testMatches = [&](int chans)
1022 {
1023 bool matched = false;
1024 int configNBusesI = 0;
1025 int configNBusesO = 0;
1026
1027 for (int i = 0; i < static_cast<int>(audioPortsConfigCount()); i++)
1028 {
1029 if (isMatch(i, chans))
1030 {
1031 const int nBusesI = NBuses(ERoute::kInput, i);
1032 const int nBusesO = NBuses(ERoute::kOutput, i);
1033
1034 const bool preferInput = nBusesO < configNBusesI;
1035 const bool preferOutput = nBusesI < configNBusesO;
1036
1037 if (!matched || preferOutput || (nBusesO == configNBusesO && preferInput))
1038 {
1039 matched = true;
1040 mConfigIdx = i;
1041 configNBusesI = NBuses(ERoute::kInput, i);
1042 configNBusesO = NBuses(ERoute::kOutput, i);
1043 }
1044 }
1045 }
1046
1047 return matched;
1048 };
1049
1050 mConfigIdx = 0;
1051
1052 // If there is track info available try to match
1053 if (GetClapHost().canUseTrackInfo())
1054 {
1055 clap_track_info info;
1056 GetClapHost().trackInfoGet(&info);
1057
1058 if (testMatches(info.audio_channel_count) || info.audio_channel_count == 2)
1059 return;
1060 }
1061
1062 // Default to stereo if nothing else has succeeded
1063 testMatches(2);
1064}
1065
1066int IPlugCLAP::RequiredChannels() const
1067{
1068 return std::max(MaxNChannels(kInput), MaxNChannels(kOutput));
1069}
1070
1071uint32_t IPlugCLAP::NBuses(ERoute direction, int configIdx) const
1072{
1073 return GetIOConfig(configIdx)->NBuses(direction);
1074}
1075
1076uint32_t IPlugCLAP::NChannels(ERoute direction, uint32_t bus, int configIdx) const
1077{
1078 return GetIOConfig(configIdx)->NChansOnBusSAFE(direction, static_cast<int>(bus));
1079}
1080
1081uint32_t IPlugCLAP::NBuses(ERoute direction) const
1082{
1083 return NBuses(direction, mConfigIdx);
1084}
1085
1086uint32_t IPlugCLAP::NChannels(ERoute direction, uint32_t bus) const
1087{
1088 return NChannels(direction, bus, mConfigIdx);
1089}
Manages a block of memory, for plug-in settings store/recall.
Definition: IPlugStructs.h:112
int PutBytes(const void *pSrc, int nBytesToCopy)
Copies data into the chunk, placing it at the end, resizing if necessary.
Definition: IPlugStructs.h:147
uint8_t * GetData()
Gets a ptr to the chunk data.
Definition: IPlugStructs.h:242
int Size() const
Returns the current size of the chunk.
Definition: IPlugStructs.h:221
IPlug's parameter class.
double GetDefault(bool normalized=false) const
Returns the parameter's default value.
EParamType Type() const
Get the parameter's type.
double ToNormalized(double nonNormalizedValue) const
Convert a real value to normalized value for this parameter.
bool GetCanAutomate() const
void GetDisplay(WDL_String &display, bool withDisplayText=true) const
Get the current textual display for the current parameter value.
double GetMin() const
Returns the parameter's minimum value.
void Set(double value)
Sets the parameter value.
const char * GetLabel() const
Returns the parameter's label.
double StringToValue(const char *str) const
Convert a textual representation of the parameter value to a double (real value)
void SetNormalized(double normalizedValue)
Sets the parameter value from a normalized range (usually coming from the linked IControl)
const char * GetName() const
Returns the parameter's name.
double FromNormalized(double normalizedValue) const
Convert a normalized value to real value for this parameter.
double GetNormalized() const
Returns the parameter's normalized value.
const char * GetGroup() const
Returns the parameter's group.
double Value() const
Gets a readable value of the parameter.
double GetMax() const
Returns the parameter's maximum value.
The base class of an IPlug plug-in, which interacts with the different plug-in APIs.
Definition: IPlugAPIBase.h:43
bool SendSysEx(const ISysEx &msg) override
Send a single MIDI System Exclusive (SysEx) message // TODO: info about what thread should this be ca...
Definition: IPlugCLAP.cpp:138
void InformHostOfParamChange(int idx, double normalizedValue) override
Implemented by the API class, called by the UI via SetParameterValue() with the value of a parameter ...
Definition: IPlugCLAP.cpp:73
void SetTailSize(int tailSize) override
Call this method if you need to update the tail size at runtime, for example if the decay time of you...
Definition: IPlugCLAP.cpp:105
void SetLatency(int samples) override
Call this if the latency of your plug-in changes after initialization (perhaps from OnReset() ) This ...
Definition: IPlugCLAP.cpp:113
bool EditorResize(int viewWidth, int viewHeight) override
Implementations call into the APIs resize hooks returns a bool to indicate whether the DAW or plugin ...
Definition: IPlugCLAP.cpp:89
void BeginInformHostOfParamChange(int idx) override
Implemented by the API class, called by the UI (or by a delegate) at the beginning of a parameter cha...
Definition: IPlugCLAP.cpp:67
bool SendMidiMsg(const IMidiMsg &msg) override
Send a single MIDI message // TODO: info about what thread should this be called on or not called on!
Definition: IPlugCLAP.cpp:132
void EndInformHostOfParamChange(int idx) override
Implemented by the API class, called by the UI (or by a delegate) at the end of a parameter change ge...
Definition: IPlugCLAP.cpp:83
The base class for IPlug Audio Processing.
bool GetTailIsInfinite() const
const IOConfig * GetIOConfig(int idx) const
int GetTailSize() const
virtual void SetTailSize(int tailSize)
Call this method if you need to update the tail size at runtime, for example if the decay time of you...
virtual void SetLatency(int latency)
Call this if the latency of your plug-in changes after initialization (perhaps from OnReset() ) This ...
bool IsInstrument() const
int MaxNBuses(ERoute direction, int *pConfigIdxWithTheMostBuses=nullptr) const
Used to determine the maximum number of input or output buses based on what was specified in the chan...
int NIOConfigs() const
int MaxNChannels(ERoute direction) const
virtual void OnActivate(bool active)
Override OnActivate() which should be called by the API class when a plug-in is "switched on" by the ...
bool PushFromArgs(Args ...args)
Definition: IPlugQueue.h:91
bool HasUI() const
bool GetHostResizeEnabled() const
ERoute
Used to identify whether a bus/channel connection is an input or an output.
Encapsulates information about the host transport state.
Definition: IPlugStructs.h:585
Encapsulates a MIDI message and provides helper functions.
Definition: IPlugMidi.h:31
void MakeNoteOnMsg(int noteNumber, int velocity, int offset, int channel=0)
Make a Note On message.
Definition: IPlugMidi.h:145
int Channel() const
Gets the channel of a MIDI message.
Definition: IPlugMidi.h:236
void MakeNoteOffMsg(int noteNumber, int offset, int channel=0)
Make a Note Off message.
Definition: IPlugMidi.h:158
EStatusMsg StatusMsg() const
Gets the MIDI Status message.
Definition: IPlugMidi.h:243
int NChansOnBusSAFE(ERoute direction, int index) const
Definition: IPlugStructs.h:536
int NBuses(ERoute direction) const
Definition: IPlugStructs.h:549
A struct for dealing with SysEx messages.
Definition: IPlugMidi.h:539
This structure is used when queueing Sysex messages.
Definition: IPlugStructs.h:45