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