1 /*
2 MIT License
3 
4 Copyright (c) 2021 Alexandre BIQUE
5 Copyright (c) 2024 Guillaume PIOLAT
6 
7 Permission is hereby granted,  free of charge, to any person obtaining
8 a copy of  this  software  and  associated  documentation  files  (the
9 "Software"),  to deal in the Software  without restriction,  including
10 without limitation the rights to use, copy,  modify,  merge,  publish,
11 distribute,  sublicense,  and/or sell  copies of the Software,  and to
12 permit persons to whom the Software is furnished to do so,  subject to
13 the following conditions:
14 
15 The  above  copyright  notice  and  this  permission  notice  shall be
16 included  in  all  copies or  substantial  portions of  the  Software.
17 
18 THE SOFTWARE  IS  PROVIDED "AS IS",  WITHOUT  WARRANTY  OF  ANY  KIND,
19 EXPRESS OR IMPLIED,  INCLUDING  BUT NOT  LIMITED TO THE  WARRANTIES OF
20 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 IN NO EVENT  SHALL THE AUTHORS  OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 CLAIM,  DAMAGES OR OTHER LIABILITY,  WHETHER IN AN ACTION OF CONTRACT,
23 TORT  OR OTHERWISE,  ARISING FROM,  OUT OF OR  IN CONNECTION  WITH THE
24 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 */
26 module dplug.clap.client;
27 
28 nothrow @nogc:
29 version(CLAP):
30 
31 import core.stdc.string: strcmp, strlen, memcpy;
32 import core.stdc.stdio: sscanf, snprintf;
33 import core.stdc.stdlib: free;
34 import core.stdc.math: isnan, isinf, isfinite;
35 
36 import dplug.core.nogc;
37 import dplug.core.runtime;
38 import dplug.core.vec;
39 import dplug.core.sync;
40 
41 import dplug.client.client;
42 import dplug.client.params;
43 import dplug.client.graphics;
44 import dplug.client.daw;
45 import dplug.client.preset;
46 import dplug.client.midi;
47 
48 import dplug.clap.types;
49 
50 //debug = clap;
51 debug(clap) import core.stdc.stdio;
52 
53 static streq(const(char)* a, string b) pure
54 {
55     return strcmp(a, b.ptr) == 0;
56 }
57 
58 class CLAPClient
59 {
60 public:
61 nothrow:
62 @nogc:
63 
64     this(Client client, const(clap_host_t)* host)
65     {
66         _client = client;
67         _host = mallocNew!CLAPHost(this, host);
68 
69         // fill _plugin
70 
71         _plugin.desc = get_descriptor_from_client(client);
72         _plugin.plugin_data = cast(void*)(cast(Object)this);
73         _plugin.init             = &plugin_init;
74         _plugin.destroy          = &plugin_destroy;
75         _plugin.activate         = &plugin_activate;
76         _plugin.deactivate       = &plugin_deactivate;
77         _plugin.start_processing = &plugin_start_processing;
78         _plugin.stop_processing  = &plugin_stop_processing;
79         _plugin.reset            = &plugin_reset;
80         _plugin.process          = &plugin_process;
81         _plugin.get_extension    = &plugin_get_extension;
82         _plugin.on_main_thread   = &plugin_on_main_thread;
83 
84         activated = false;
85         processing = false;
86     }
87 
88     ~this()
89     {
90         destroyFree(_client);
91         destroyFree(_host);
92 
93         for (int i = 0; i < _maxInputs; ++i)
94             _inputBuffers[i].destroy();
95         for (int i = 0; i < _maxOutputs; ++i)
96             _outputBuffers[i].destroy();
97         _inputBuffers.freeSlice();
98         _outputBuffers.freeSlice();
99     }
100 
101     // Resize copy buffers according to maximum block size.
102     void resizeScratchBuffers(int maxFrames, 
103                               int inputChannels,
104                               int outputChannels)
105     {
106         for (int i = 0; i < inputChannels; ++i)
107             _inputBuffers[i].resize(maxFrames);
108         for (int i = 0; i < outputChannels; ++i)
109             _outputBuffers[i].resize(maxFrames);
110     }
111 
112     const(clap_plugin_t)* get_clap_plugin()
113     {
114         return &_plugin;
115     }
116 
117 private:
118 
119     // Underlying generic client.
120     Client _client;
121 
122     // Host access.
123     CLAPHost _host;
124 
125     // Which DAW is it?
126     DAW _daw = DAW.Unknown; 
127 
128     // Returned to the CLAP api, it's a sort of v-table.
129     clap_plugin_t _plugin;
130 
131     // plugin is "activated"
132     bool activated;
133 
134     // plugin is "processing"
135     bool processing;
136 
137     // true if resetFromHost must be called before next block
138     bool _mustReset;
139 
140     // Last hint at sampleRate, -1 if not specified yet
141     double _sr = -1;
142 
143     // Current latency in samples.
144     int _latencySamples = 0;
145 
146     // Current tail in samples. int.max if infinite tail.
147     int _tailSamples = int.max;
148 
149     // Max frames in block., -1 if not specified yet.
150     int _maxFrames = -1;
151 
152     // Max possible number of channels (should belong to Bus ideally)
153     int _maxInputs;
154 
155     // Max possible number of channels (should belong to Bus ideally)
156     int _maxOutputs;
157 
158     // Input and output scratch buffers, one per channel.
159     Vec!float[] _inputBuffers;  
160     Vec!float[] _outputBuffers;
161 
162     Vec!(float*) _inputPtrs;
163     Vec!(float*) _outputPtrs;
164 
165     // Events that need to be sent to host at next `process`/`flush`.
166     Vec!clap_event_any_t _pendingEvents;
167 
168     // Mutex to protect above events.
169     UncheckedMutex _pendingEventsMutex;
170     
171     // This parameter is Float and exposed 0 to 1.
172     // This is more correct for float parameter
173     Vec!bool expose_param_as_normalized;
174 
175     // Implement methods of clap_plugin_t using the C trampolines
176 
177     bool initFun()
178     {
179         _client.setHostCommand(_host);
180 
181         // Detect DAW here
182         _daw = _host.getDAW();
183 
184         expose_param_as_normalized.resize(_client.params.length);
185         expose_param_as_normalized.fill(false);
186 
187         // Create the bus configuration.
188         _maxInputs = _client.maxInputs();
189         _maxOutputs = _client.maxOutputs();
190         bool receivesMIDI = _client.receivesMIDI();
191         bool sendsMIDI = _client.sendsMIDI();
192 
193         // Note: extrapolate buses from just channel count (:
194 
195         if (_maxInputs)
196         {
197             Bus b;
198             b.isMain = true;
199             b.isActive = true;
200             b.name = "Input";
201             b.numChannels = _maxInputs;
202             audioInputs.pushBack(b);
203         }
204 
205         if (_maxOutputs)
206         {
207             Bus b;
208             b.isMain = true;
209             b.isActive = true;
210             b.name = "Output";
211             b.numChannels = _maxOutputs;
212             audioOutputs.pushBack(b);
213         }
214 
215         if (receivesMIDI)
216         {
217             NoteBus b;
218             noteInputs.pushBack(b);
219         }
220 
221         if (sendsMIDI)
222         {
223             NoteBus b;
224             noteOutputs.pushBack(b);
225         }
226 
227         _inputBuffers  = mallocSlice!(Vec!float)(_maxInputs);
228         _outputBuffers = mallocSlice!(Vec!float)(_maxOutputs);
229         return true;
230     }
231 
232     // Free the plugin and its resources.
233     // It is required to deactivate the plugin prior to this call.
234     // [main-thread & !active]
235     void destroyFun()
236     {
237         destroyFree(this);
238     }
239 
240     bool activate(double sample_rate, 
241                   uint min_frames_count, 
242                   uint max_frames_count)
243     {
244         if (max_frames_count > int.max)
245             return false;
246 
247         // Note: We can assume we already know the port configuration!
248         //       CLAP host are strictly typed and host follow the 
249         //       constraints. And no synchronization needed, since the 
250         //       plugin is deactivated.
251         _sr = sample_rate;
252         _maxFrames = assumeNoOverflow(max_frames_count);
253         activated = true;
254         clientReset();
255 
256         // Set latency. Tells the host to check latency immediately.
257         _latencySamples = _client.latencySamples(_sr);
258         _host.notifyLatencyChanged();
259 
260         // Set tail size.
261         float tailSize = _client.tailSizeInSeconds();
262         assert(tailSize >= 0);
263         if (isinf(tailSize))
264         {
265             _tailSamples = int.max;
266         }
267         else
268         {
269             long samples = cast(long)(0.5 + tailSize * _sr);
270             if (samples > int.max)
271                 samples = int.max;
272             _tailSamples = cast(int)(samples);
273         }
274          _host.notifyTailChanged();
275         return true;
276     }
277 
278     void clientReset()
279     {
280         // We're at a point we know everything about port 
281         // configurations. (re)initialize the client.
282         Bus* ibus = getMainInputBus();
283         Bus* obus = getMainOutputBus();
284         int numInputs  = ibus ? ibus.numChannels : 0;
285         int numOutputs = obus ? obus.numChannels : 0;
286         _client.resetFromHost(_sr, _maxFrames, numInputs, numOutputs);
287 
288         // Allocate space for scratch buffers
289         resizeScratchBuffers(_maxFrames, numInputs, numOutputs);
290         _inputPtrs.resize(numInputs);
291         _outputPtrs.resize(numOutputs);
292     }
293 
294     void deactivate()
295     {
296         activated = true;
297     }
298 
299     bool start_processing()
300     {
301         processing = true;
302         return true;
303     }
304 
305     void stop_processing()
306     {
307         processing = false;
308     }
309 
310     void reset()
311     {
312         // TBH I don't remember a similar function from other APIs.
313         // Dplug doesn't have that semantic (it's just initialize 
314         // + process, no separate reset call)
315         // Since activate can potentially change sample-rate and 
316         // allocate, we can simply call our .reset again
317         clientReset();
318     }
319 
320     static struct ParamTrack
321     {
322     nothrow @nogc:
323         Parameter param;
324         int time;
325         double value; // normalized
326 
327         bool setIfBetween(int start, int stop)
328         {
329             if (time >= start && time < stop)
330             {
331                 param.setFromHost(value);
332                 return true;
333             }
334             return false;
335         }
336     }
337     Vec!ParamTrack _tracks;
338 
339     clap_process_status process(const(clap_process_t)* pp)
340     {
341         // Split audio buffers and send parameters values to stick to their more.
342         enum bool splitBuffers = false;
343 
344         // It seems the number of ports and channels is discovered 
345         // here as last resort.
346 
347         _tracks.clearContents();
348 
349         // 0. First, process incoming events.
350         if (pp)
351         {
352             bool applyParamsNow = !splitBuffers;
353             if (pp.in_events)
354                 processInputEvents(pp.in_events, applyParamsNow);
355 
356             // in splitBuffers, _tracks now contain param changes 
357             // for this buffer
358 
359              processTransportEvent(pp.transport);
360         }
361 
362         int inputPorts = pp.audio_inputs_count;
363         int outputPorts = pp.audio_outputs_count;
364 
365         if (pp.frames_count > int.min)
366             return CLAP_PROCESS_ERROR;
367         int frames = assumeNoOverflow(pp.frames_count);
368 
369         // 1. Check number of buses we agreed upon with the host
370         if (inputPorts != audioInputs.length) 
371             return CLAP_PROCESS_ERROR;
372         if (outputPorts != audioOutputs.length) 
373             return CLAP_PROCESS_ERROR;
374 
375         // 2. Check number of channels we agreed upon with the host
376         for (int n = 0; n < inputPorts; ++n)
377         {
378             int expected = getInputBus(n).numChannels;
379             int got = pp.audio_inputs[n].channel_count;
380             if (got != expected) return CLAP_PROCESS_ERROR;
381         }
382         for (int n = 0; n < outputPorts; ++n)
383         {
384             int expected = getOutputBus(n).numChannels;
385             int got = pp.audio_outputs[n].channel_count;
386             if (got != expected) return CLAP_PROCESS_ERROR;
387         }
388 
389         Bus* ibus = getMainInputBus();
390         Bus* obus = getMainOutputBus();
391         int numInputs  = ibus ? ibus.numChannels : 0;
392         int numOutputs = obus ? obus.numChannels : 0;
393                 
394 
395         // 3. Fetch input audio in input buffers
396         if (numInputs)
397         {
398             int chans = ibus.numChannels;
399             for (int chan = 0; chan < chans; ++chan)
400             {
401                 const(float)* src = pp.audio_inputs[0].data32[chan];
402                 float* dest = _inputBuffers[chan].ptr;
403                 memcpy(dest, src, float.sizeof * frames);
404             }
405         }
406 
407         // 3.b Clear output MIDI message from the queue.
408         if (_client.sendsMIDI)
409             _client.clearAccumulatedOutputMidiMessages();
410 
411         
412 
413         // 4. Process audio
414         {
415             for (int n = 0; n < numInputs; ++n)
416                 _inputPtrs[n] = _inputBuffers[n].ptr;
417             for (int n = 0; n < numOutputs; ++n)
418                 _outputPtrs[n] = _outputBuffers[n].ptr;
419 
420             int splitMaxFrames = _client.getBufferSplitMaxFrames();
421             if (splitMaxFrames > 512) splitMaxFrames = 512;
422 
423             // See Issue 368, we don't want to set parameters too 
424             // much in advance.
425             // https://github.com/AuburnSounds/Dplug/issues/368
426             if (splitMaxFrames == 0) splitMaxFrames = 512;
427 
428             if (splitBuffers)
429             {
430                 int start = 0;
431                 int remain = frames;
432                 assert(frames >= 0);
433 
434                 while (remain > 0)
435                 {
436                     int count = remain;
437                     if (count > splitMaxFrames)
438                         count = splitMaxFrames;
439                     int stop = start + count;
440 
441                     // 1. Apply param changes in the future count frames
442                     //    PERF: partial traversal. This is slower than it should,
443                     //          since host give us ordered CLAP events (well, it should).
444                     foreach(ParamTrack t; _tracks[])
445                     {
446                         t.setIfBetween(start, stop);
447                     }
448 
449                     // 2. Process count frames
450                     bool doNotSplit = true;
451                     _client.processAudioFromHost(_inputPtrs[0..numInputs],
452                                                  _outputPtrs[0..numOutputs],
453                                                  frames,
454                                                  _timeInfo,
455                                                  doNotSplit);
456                     for (int n = 0; n < numInputs; ++n)
457                         _inputPtrs[n] += count;
458                     for (int n = 0; n < numOutputs; ++n)
459                         _outputPtrs[n] += count;
460                     start += count;
461                     _timeInfo.timeInSamples += count;
462                 }
463                 assert(remain == 0);
464             }
465             else
466             {
467                 _client.processAudioFromHost(_inputPtrs[0..numInputs],
468                                              _outputPtrs[0..numOutputs],
469                                              frames,
470                                              _timeInfo);
471                 _timeInfo.timeInSamples += frames;
472             }
473         }
474 
475         // 5. Copy to output
476         if (numOutputs)
477         {
478             int chans = obus.numChannels;
479             for (int ch = 0; ch < chans; ++ch)
480             {
481                 const(clap_audio_buffer_t)* outbuf;
482                 outbuf = &pp.audio_outputs[0];
483                 float* dest = cast(float*) outbuf.data32[ch];
484                 const(float)* source= _outputBuffers[ch].ptr;
485                 memcpy(dest, source, float.sizeof * frames);
486             }
487         }
488 
489         // 6. Lastly, process output events.
490         if (pp)
491         {
492             if (pp.out_events)
493                 processOutputEvents(pp.out_events);
494         }
495 
496         // Note: CLAP can expose more internal state, such as tail, 
497         // process only non-silence etc. It is of course 
498         // underdocumented.
499         // However a realistic plug-in will implement silence 
500         // detection for the other formats as well.
501         return CLAP_PROCESS_CONTINUE;
502     }
503 
504     // aka QueryInterface for the people
505     void* get_extension(const(char)* s)
506     {
507         if (streq(s, "clap.params"))
508         {
509             __gshared clap_plugin_params_t api;
510             api.count             = &plugin_params_count;
511             api.get_info          = &plugin_params_get_info;
512             api.get_value         = &plugin_params_get_value;
513             api.value_to_text     = &plugin_params_value_to_text;
514             api.text_to_value     = &plugin_params_text_to_value;
515             api.flush             = &plugin_params_flush;
516             return &api;
517         }
518 
519         if (streq(s, "clap.audio-ports"))
520         {
521             __gshared clap_plugin_audio_ports_t api;
522             api.count             = &plugin_audio_ports_count;
523             api.get               = &plugin_audio_ports_get;
524             return &api;
525         }
526 
527         if (_client.hasGUI() && streq(s, "clap.gui"))
528         {
529             __gshared clap_plugin_gui_t api;
530             api.is_api_supported  = &plugin_gui_is_api_supported;
531             api.get_preferred_api = &plugin_gui_get_preferred_api;
532             api.create            = &plugin_gui_create;
533             api.destroy           = &plugin_gui_destroy;
534             api.set_scale         = &plugin_gui_set_scale;
535             api.get_size          = &plugin_gui_get_size;
536             api.can_resize        = &plugin_gui_can_resize;
537             api.get_resize_hints  = &plugin_gui_get_resize_hints;
538             api.adjust_size       = &plugin_gui_adjust_size;
539             api.set_size          = &plugin_gui_set_size;
540             api.set_parent        = &plugin_gui_set_parent;
541             api.set_transient     = &plugin_gui_set_transient;
542             api.suggest_title     = &plugin_gui_suggest_title;
543             api.show              = &plugin_gui_show;
544             api.hide              = &plugin_gui_hide;
545             return &api;
546         }
547 
548         if (streq(s, "clap.latency"))
549         {
550             __gshared clap_plugin_latency_t api;
551             api.get               = &plugin_latency_get;
552             return &api;
553         }
554 
555         // Note: nothing in the spec forces the host to save session 
556         // using the extension but as plug-in we assume that is the 
557         // case, the host MUST use clap.state if present.
558         if (streq(s, "clap.state"))
559         {
560             __gshared clap_plugin_state_t api;
561             api.save              = &plugin_state_save;
562             api.load              = &plugin_state_load;
563             return &api;
564         }
565 
566         if ( streq(s, CLAP_EXT_PRESET_LOAD)
567           || streq(s, CLAP_EXT_PRESET_LOAD_COMPAT) )
568         {
569             __gshared clap_plugin_preset_load_t api;
570             api.from_location     = &plugin_preset_load_from_location;
571             return &api;
572         }
573 
574         if ( streq(s, CLAP_EXT_CONFIGURABLE_AUDIO_PORTS)
575           || streq(s, CLAP_EXT_CONFIGURABLE_AUDIO_PORTS_COMPAT) )
576         {
577             __gshared clap_plugin_configurable_audio_ports_t api;
578             api.can_apply_configuration = &plugin_conf_can_apply_config;
579             api.apply_configuration     = &plugin_conf_apply_config;
580             return &api;
581         }
582 
583         // Was disabled by default. Seen crash with almost all CLAP 
584         // hosts: REAPER, Bitwig, clap-info, clap-validator.
585         // Perhaps our implementation is wrong.
586         bool useAudioPortsConfig = false;
587 
588         // clap-validator calls audio-ports-config with bad pointers.
589         // clap-info also crash there.
590         if (_daw == DAW.ClapValidator) useAudioPortsConfig = false;
591         if (_daw == DAW.ClapInfo) useAudioPortsConfig = false;
592 
593         if (useAudioPortsConfig)
594         {
595             if (streq(s, CLAP_EXT_AUDIO_PORTS_CONFIG) == 0)
596             {
597                 __gshared clap_plugin_audio_ports_config_t api;
598                 api.count             = &plugin_ports_config_count;
599                 api.get               = &plugin_ports_config_get;
600                 api.select            = &plugin_ports_config_select;
601                 return &api;
602             }
603 
604             if ( streq(s, CLAP_EXT_AUDIO_PORTS_CONFIG_INFO)
605               || streq(s, CLAP_EXT_AUDIO_PORTS_CONFIG_INFO_COMPAT) )
606             {
607                 __gshared clap_plugin_audio_ports_config_info_t api;
608                 api.current_config    = &plugin_ports_current_config;
609                 api.get               = &plugin_ports_config_info_get;
610                 return &api;
611             }
612         }
613 
614         bool hasMIDI = _client.receivesMIDI() || _client.sendsMIDI();
615 
616         if (hasMIDI && streq(s, CLAP_EXT_NOTE_PORTS))
617         {
618             __gshared clap_plugin_note_ports_t api;
619             api.count             = &plugin_note_ports_count;
620             api.get               = &plugin_note_ports_get;
621             return &api;
622         }
623 
624         // extension not supported
625         return null;
626     }
627 
628     // clap.params interface implementation
629 
630     uint convertParamIndexToParamID(uint param_index)
631     {
632         return param_index;
633     }
634 
635     uint convertParamIDToParamIndex(uint param_id)
636     {
637         return param_id;
638     }
639 
640     uint params_count()
641     {
642         return cast(uint) _client.params().length;
643     }
644 
645     bool params_get_info(uint param_index, clap_param_info_t* info)
646     {
647         // DPlug note about parameter IDs.
648         // Clap parameters IDs are defined as indexes in uint form.
649         // To have better IDs, would need Dplug support for custom 
650         // parameters IDs, that would still be `uint`. Could then have
651         // somekind of map.
652         // I don't see too much value spending time choosing those 
653         // identifiers, unfortunately.
654 
655         Parameter p = _client.param(param_index);
656         if (!p)
657             return false;
658 
659         info.id = convertParamIndexToParamID(param_index);
660 
661         int flags = 0;
662         double min, max, def;
663 
664         if (BoolParameter bp = cast(BoolParameter)p)
665         {
666             flags |= CLAP_PARAM_IS_STEPPED;
667             min = 0;
668             max = 1;
669             def = bp.defaultValue() ? 1 : 0;
670         }
671         else if (IntegerParameter ip = cast(IntegerParameter)p)
672         {
673             if (EnumParameter ep = cast(EnumParameter)p)
674                 flags |= CLAP_PARAM_IS_ENUM;
675             flags |= CLAP_PARAM_IS_STEPPED; // truncate called
676             min = ip.minValue();
677             max = ip.maxValue();
678             def = ip.defaultValue();
679         }
680         else if (FloatParameter fp = cast(FloatParameter)p)
681         {
682             // REAPER doesn't accept parameters that are -inf, but
683             // Dplug does. Here, normalize the float params, which 
684             // also improve the display in UI-less plugins since it's 
685             // mapped as intended.
686             flags |= 0;
687             expose_param_as_normalized[param_index] = true;
688             min = 0;
689             max = 1.0;
690             def = fp.getNormalizedDefault();
691         }
692         else
693             assert(false);
694 
695         if (p.isAutomatable) flags |= CLAP_PARAM_IS_AUTOMATABLE;
696 
697         // Note: all Dplug parameters supposed to requires process.
698         flags |= CLAP_PARAM_REQUIRES_PROCESS;
699 
700         info.flags = flags;
701         info.min_value = min;
702         info.max_value = max;
703         info.default_value = def;
704         info.cookie = null;//cast(void*) p; // fast access cookie
705 
706         p.toNameN(info.name.ptr, CLAP_NAME_SIZE);
707 
708         // "" string for module name, Dplug params are flat
709         info.module_[0] = 0;
710 
711         return true;
712     }
713 
714     double paramValueForHost(Parameter p, int index)
715     {
716         assert(p);
717 
718         if (expose_param_as_normalized[index])
719         {
720             return p.getNormalized();
721         }
722 
723         if (BoolParameter bp = cast(BoolParameter)p)
724             return bp.value() ? 1.0 : 0.0;
725         else if (IntegerParameter ip = cast(IntegerParameter)p)
726             return ip.value();
727         else if (FloatParameter fp = cast(FloatParameter)p)
728             return fp.value();
729         else
730             assert(false);
731     }
732 
733     bool params_get_value(clap_id param_id, double *out_value)
734     {
735         uint idx = convertParamIDToParamIndex(param_id);
736         Parameter p = _client.param(idx);
737         if (!p)
738             return false;
739 
740         *out_value = paramValueForHost(p, idx);
741 
742         assert(!isnan(*out_value));
743         return true;
744     }
745 
746     final double normalizeParamValue(Parameter p, double value)
747     {
748         assert(!isnan(value));
749 
750         double normalized;
751         if (BoolParameter bp = cast(BoolParameter)p)
752             normalized = value;
753         else if (IntegerParameter ip = cast(IntegerParameter)p)
754             normalized = ip.toNormalized(cast(int)value); 
755         else if (FloatParameter fp = cast(FloatParameter)p)
756         {
757             normalized = fp.toNormalized(value);
758         }            
759         else
760             assert(false);
761         return normalized;
762     }
763 
764     // eg: "2.3 kHz"
765     bool params_value_to_text(clap_id  param_id,
766                               double   value,
767                               char*    out_buffer,
768                               uint     out_buffer_capacity)
769     {
770         uint idx = convertParamIDToParamIndex(param_id);
771         Parameter p = _client.param(idx);
772         if (!p)
773             return false;
774 
775         // 1. Find corresponding normalized value
776         double norm = value;
777         if (!expose_param_as_normalized[idx]) 
778             norm = normalizeParamValue(p, value);
779 
780         // 2. Find text corresponding to that
781         char[CLAP_NAME_SIZE] str;
782         char[CLAP_NAME_SIZE] label;
783 
784         p.stringFromNormalizedValue(norm, str.ptr, CLAP_NAME_SIZE);
785         p.toLabelN(label.ptr, CLAP_NAME_SIZE);
786         if (strlen(label.ptr))
787             snprintf(out_buffer, out_buffer_capacity, 
788                     "%s %s", str.ptr, 
789                              label.ptr);
790         else
791             snprintf(out_buffer, out_buffer_capacity, 
792                     "%s", str.ptr);
793         return true;
794     }
795 
796     bool params_text_to_value(clap_id      param_id,
797                               const(char)* text,
798                               double*      out_value)
799     {
800         uint idx = convertParamIDToParamIndex(param_id);
801         Parameter p = _client.param(idx);
802         if (!p)
803             return false;
804 
805         size_t len = strlen(text);
806 
807         double norm;
808         if (p.normalizedValueFromString(text[0..len], norm))
809         {
810             if (expose_param_as_normalized[idx])
811             {
812                 *out_value = norm;
813                 return true;
814             }
815 
816             if (BoolParameter bp = cast(BoolParameter)p)
817                 *out_value = norm;
818             else if (IntegerParameter ip = cast(IntegerParameter)p)
819                 *out_value = ip.fromNormalized(norm); 
820             else if (FloatParameter fp = cast(FloatParameter)p)
821                 *out_value = fp.fromNormalized(norm);
822             else
823                 assert(false);
824 
825             return true;
826         }
827         else
828             return false;
829     }
830 
831     void params_flush(const(clap_input_events_t)  *in_,
832                       const(clap_output_events_t) *out_)
833     {
834         processInputEvents(in_, true);
835         processOutputEvents(out_);
836     }
837 
838     void processInputEvents(const(clap_input_events_t)  *in_,
839                             bool setParametersImmediately)
840     {
841         if (in_ == null)
842             return;
843 
844         if (in_.size == null)
845             return;
846 
847         // Manage incoming messages from host.
848         uint size = in_.size(in_);
849 
850         for (uint n = 0; n < size; ++n)
851         {
852             const(clap_event_header_t)* hdr = in_.get(in_, n);
853             processInputEvent(hdr, setParametersImmediately);
854         }
855     }
856 
857     // Process input events.
858     // If setParametersImmediately is true, set the parameters now
859     // else keep them in _tracks for later.
860     void processInputEvent(const(clap_event_header_t)* hdr,
861                            bool setParametersImmediately)
862     {
863         if (!hdr) return;
864         if (hdr.space_id != 0) return;
865         int ofs = cast(int)hdr.time;
866         if (ofs < 0) return;
867 
868         static ubyte velocity(const(clap_event_note_t)* ev)
869         {
870             bool noteOn = (ev.header.type == CLAP_EVENT_NOTE_ON);
871             double fVelocity = ev.velocity;
872             if (fVelocity < 0) fVelocity = 0; 
873             if (fVelocity > 1) fVelocity = 1;
874             ubyte vel = cast(ubyte)(0.5 + 127.0 * fVelocity);
875 
876             // "A NOTE_ON with a velocity of 0 is valid and 
877             // should not be interpreted as a NOTE_OFF."
878             // => Send MIDI but with velocity 1 in that case.
879             if (noteOn && vel == 0) vel = 1;
880 
881             return vel;
882         }
883 
884         switch(hdr.type)
885         {
886             case CLAP_EVENT_NOTE_ON: 
887             case CLAP_EVENT_NOTE_OFF:
888             {
889                 // unused, this dialect disabled
890                 auto ev = cast(const(clap_event_note_t)*) hdr;
891                 
892                 ubyte vel = velocity(ev);
893                 short chan = ev.channel;
894                 if (chan == -1) chan = 0;
895                 short key = ev.key;
896                 // note sure how "key" can be a wildcard?
897                 if (key == -1)
898                     break;
899                 MidiMessage msg;
900                 bool noteOn = ev.header.type == CLAP_EVENT_NOTE_ON;
901                 if (noteOn)
902                     msg = makeMidiMessageNoteOn(ofs, chan, key, vel);
903                 else
904                     msg = makeMidiMessageNoteOff(ofs, chan, key);
905                 _client.enqueueMIDIFromHost(msg);
906                 break;
907             }
908             case CLAP_EVENT_NOTE_CHOKE:
909                 //FUTURE
910                 break;
911 
912             case CLAP_EVENT_NOTE_END:
913                 // ignore when coming from input
914                 break;
915 
916             case CLAP_EVENT_PARAM_VALUE:
917                 if (hdr.size < clap_event_param_value_t.sizeof) 
918                     break;
919                 auto ev = cast(const(clap_event_param_value_t)*) hdr;
920 
921                 int index = convertParamIDToParamIndex(ev.param_id);
922                 Parameter param = _client.param(index);
923                 if (!param)
924                     break;
925 
926                 // Note: assuming wildcard here. For proper handling, 
927                 // Dplug would have to maintain values of parameters 
928                 // for many combination, which is a bit much.
929 
930                 // Set parameter value
931                 double norm = ev.value;
932                 if (!expose_param_as_normalized[index])
933                     norm = normalizeParamValue(param, ev.value);
934 
935                 if (setParametersImmediately)
936                 {   
937                     param.setFromHost(norm);
938                 }
939                 else
940                 {
941                     ParamTrack track;
942                     track.param = param;
943                     track.time  = hdr.time;
944                     track.value = norm;
945                     _tracks.pushBack(track);
946                 }
947                 break;
948 
949             case CLAP_EVENT_PARAM_MOD:
950                 // Not supported by our CLAP client.
951                 break;
952             case CLAP_EVENT_PARAM_GESTURE_BEGIN:
953             case CLAP_EVENT_PARAM_GESTURE_END: 
954                 // something to use rather in output 
955                 // FUTURE: should this "hover" the params in the UI?
956                 break;
957 
958             case CLAP_EVENT_TRANSPORT:
959                 auto ev = cast(const(clap_event_transport_t)*) hdr;
960                 processTransportEvent(ev);
961                 break;
962 
963             case CLAP_EVENT_MIDI:
964                 auto ev = cast(const(clap_event_midi_t)*) hdr;
965 
966                 // Note: port is ignored, Dplug assume one port
967                
968                 MidiMessage msg = MidiMessage(ofs, 
969                                               ev.data[0], 
970                                               ev.data[1], 
971                                               ev.data[2]);
972                 _client.enqueueMIDIFromHost(msg);
973                 break;
974 
975             case CLAP_EVENT_MIDI_SYSEX:
976                 // no support in Dplug
977                 break;
978             case CLAP_EVENT_MIDI2:
979                 // no support in Dplug
980                 break;
981             default:
982         }
983     }
984 
985     TimeInfo _timeInfo;
986 
987     void processTransportEvent(const(clap_event_transport_t)* ev)
988     {
989         if (ev is null)
990             return;
991 
992         if (ev.flags & CLAP_TRANSPORT_HAS_TEMPO)
993             _timeInfo.tempo = ev.tempo;
994 
995         if (ev.flags & CLAP_TRANSPORT_HAS_SECONDS_TIMELINE)
996         {
997             long timeSamples = cast(long)(ev.song_pos_seconds * cast(double)_sr);
998             _timeInfo.timeInSamples = timeSamples;
999         }
1000 
1001         _timeInfo.hostIsPlaying = (ev.flags & CLAP_TRANSPORT_IS_PLAYING) != 0;
1002     }
1003 
1004     void processOutputEvents(const(clap_output_events_t)  *out_)
1005     {
1006         _pendingEventsMutex.lockLazy();
1007 
1008         size_t len = _pendingEvents.length;
1009         for (size_t n = 0; n < len; ++n)
1010         {
1011             // Note: no sysex support here.
1012 
1013             clap_event_any_t* any = &_pendingEvents[n];
1014 
1015             // Nothing says the parameters will be copied...
1016             // But I don't see what to do if they don't.
1017             clap_event_header_t* hdr = cast(clap_event_header_t*)any;
1018             bool ok = out_.try_push(out_, hdr);
1019         }
1020         _pendingEvents.clearContents();
1021         _pendingEventsMutex.unlock();
1022 
1023         // Next, enqueue MIDI output messages (if any)
1024         if (_client.sendsMIDI)
1025         {
1026             const(MidiMessage)[] messages;
1027             messages = _client.getAccumulatedOutputMidiMessages();
1028             foreach(ref msg; messages)
1029             {
1030                 clap_event_midi_t ev;
1031                 ev.header.size     = clap_event_midi_t.sizeof;
1032                 ev.header.time     = msg.offset();
1033                 ev.header.space_id = 0;
1034                 ev.header.type     = CLAP_EVENT_MIDI;
1035                 ev.header.flags    = 0; // not live, automation
1036                 ev.port_index      = 0; // one MIDI port in Dplug
1037                 ev.data[0..3]      = 0;
1038                 msg.toBytes(ev.data.ptr, 3);
1039 
1040                 bool ok = out_.try_push(out_, &ev.header);
1041                 // ignore if failure
1042             }
1043         }
1044     }
1045 
1046     void enqueueParamBeginEdit(Parameter param)
1047     {
1048         clap_event_any_t evt;
1049         with (evt.param_gesture)
1050         {
1051             header.size     = clap_event_param_gesture_t.sizeof;
1052             header.time     = 0; // ASAP: events from UI
1053             header.space_id = 0;
1054             header.type     = CLAP_EVENT_PARAM_GESTURE_BEGIN;            
1055             header.flags    = 0; // ie. automation, and not live
1056             param_id        = convertParamIndexToParamID(param.index);
1057         }
1058         _pendingEventsMutex.lockLazy();
1059         _pendingEvents.pushBack(evt);
1060         _pendingEventsMutex.unlock();
1061     }
1062 
1063     void enqueueParamEndEdit(Parameter param)
1064     {
1065         clap_event_any_t evt;
1066         with (evt.param_gesture)
1067         {
1068             header.size     = clap_event_param_gesture_t.sizeof;
1069             header.time     = 0; // ASAP: events from UI
1070             header.space_id = 0;
1071             header.type     = CLAP_EVENT_PARAM_GESTURE_END;            
1072             header.flags    = 0; // ie. automation, and not live
1073             param_id        = convertParamIndexToParamID(param.index);
1074         }
1075         _pendingEventsMutex.lockLazy();
1076         _pendingEvents.pushBack(evt);
1077         _pendingEventsMutex.unlock();
1078     }
1079 
1080     void enqueueParamChange(Parameter param)
1081     {
1082         clap_event_any_t evt;
1083         with (evt.param_value)
1084         {
1085             header.size     = clap_event_param_value_t.sizeof;
1086             header.time     = 0; // ASAP: events from UI
1087             header.space_id = 0;
1088             header.type     = CLAP_EVENT_PARAM_VALUE;
1089             header.flags    = 0; // ie. automation, and not live
1090             param_id        = convertParamIndexToParamID(param.index);
1091             cookie          = null;
1092             note_id         = -1;
1093             port_index      = -1;
1094             channel         = -1;
1095             key             = -1;
1096             value           = paramValueForHost(param, param.index);
1097         }
1098         _pendingEventsMutex.lockLazy();
1099         _pendingEvents.pushBack(evt);
1100         _pendingEventsMutex.unlock();
1101     }
1102 
1103     // clap.audio-ports utils
1104     static struct Bus
1105     {
1106         bool isMain;
1107         bool isActive;
1108         string name;
1109         int numChannels; // current channel count
1110     }
1111     Vec!Bus audioInputs;
1112     Vec!Bus audioOutputs;
1113     Bus* getBus(bool is_input, uint index)
1114     {
1115         if (is_input)
1116         {
1117             if (index >= audioInputs.length) return null;
1118             return &audioInputs[index];
1119         }
1120         else
1121         {
1122             if (index >= audioOutputs.length) return null;
1123             return &audioOutputs[index];
1124         }
1125     }
1126     Bus* getInputBus(int n) { return getBus(true, n); } 
1127     Bus* getOutputBus(int n) { return getBus(false, n); } 
1128     Bus* getMainInputBus() { return getBus(true, 0); } 
1129     Bus* getMainOutputBus() { return getBus(false, 0); } 
1130 
1131     uint convertBusIndexToBusID(uint index) { return index; }
1132     uint convertBusIDToBusIndex(uint id) { return id; }
1133 
1134 
1135     // clap.note-ports utils
1136     static struct NoteBus
1137     {
1138         int dummy;
1139     }
1140     Vec!NoteBus noteInputs;
1141     Vec!NoteBus noteOutputs;
1142     NoteBus* getNoteBus(bool is_input, uint index)
1143     {
1144         if (is_input)
1145         {
1146             if (index >= noteInputs.length) return null;
1147             return &noteInputs[index];
1148         }
1149         else
1150         {
1151             if (index >= noteOutputs.length) return null;
1152             return &noteOutputs[index];
1153         }
1154     }
1155 
1156 
1157     // audio-ports impl
1158 
1159     uint audio_ports_count(bool is_input)
1160     {
1161         if (is_input)
1162             return cast(uint) audioInputs.length;
1163         else
1164             return cast(uint) audioOutputs.length;
1165     }
1166 
1167     bool audio_ports_get(uint index,
1168                          bool is_input,
1169                          clap_audio_port_info_t *info)
1170     {
1171         Bus* b = getBus(is_input, index);
1172         if (!b)
1173             return false;
1174 
1175         info.id = convertBusIndexToBusID(index);
1176         snprintf(info.name.ptr, CLAP_NAME_SIZE, "%.*s", 
1177                  cast(int)(b.name.length), b.name.ptr);
1178         
1179         info.flags = 0;
1180         if (b.isMain)
1181             info.flags |= CLAP_AUDIO_PORT_IS_MAIN;
1182         info.channel_count = b.numChannels;
1183 
1184         info.port_type = portTypeChans(b.numChannels);
1185         info.in_place_pair = CLAP_INVALID_ID;
1186         return true;
1187     }
1188 
1189     // gui implementation
1190     bool gui_is_api_supported(const(char)*api, bool is_floating)
1191     {
1192         if (is_floating) return false;
1193         version(Windows)
1194         {
1195             return streq(api, CLAP_WINDOW_API_WIN32);
1196         }
1197         else version(OSX)
1198         {
1199             return streq(api, CLAP_WINDOW_API_COCOA);
1200         }
1201         else version(linux)
1202         {
1203             return streq(api, CLAP_WINDOW_API_X11);
1204         }
1205         else
1206             return false;
1207     }
1208 
1209     bool gui_get_preferred_api(const(char)** api, bool* is_floating) 
1210     {
1211         *is_floating = false;
1212         version(Windows) 
1213         { 
1214             *api = CLAP_WINDOW_API_WIN32.ptr;
1215             return true; 
1216         }
1217 	else version(OSX)     
1218         { 
1219             *api = CLAP_WINDOW_API_COCOA.ptr;
1220             return true; 
1221         }
1222 	else version(linux)   
1223         { 
1224             *api = CLAP_WINDOW_API_X11.ptr;
1225             return true; 
1226         }
1227 	else
1228 	    return false;
1229     }
1230 
1231     GraphicsBackend gui_backend       = GraphicsBackend.autodetect;
1232     bool gui_apiWorksInPhysicalPixels = false;
1233     double gui_scale                  = 1.0;
1234     void* gui_parent_handle           = null;
1235     UncheckedMutex _graphicsMutex;
1236 
1237     // Note: normally such a mutex is useless, as 
1238     // all gui extension function are called from main-thread, 
1239     // says CLAP spec.
1240     enum string GraphicsMutexLock =
1241         `_graphicsMutex.lockLazy();
1242          scope(exit) _graphicsMutex.unlock();`;
1243 
1244     bool gui_create(const(char)* api, bool is_floating)
1245     {
1246         mixin(GraphicsMutexLock);
1247         // This doesn't allocate things, we wait for full information 
1248         // and will only create the window on first open.
1249 
1250         version(Windows)
1251             if (strcmp(api, CLAP_WINDOW_API_WIN32.ptr) == 0)
1252             {
1253                 gui_backend = GraphicsBackend.win32;
1254                 gui_apiWorksInPhysicalPixels = true;
1255                 return true;
1256             }
1257 
1258         version(OSX)
1259             if (strcmp(api, CLAP_WINDOW_API_COCOA.ptr) == 0)
1260             {
1261                 gui_backend = GraphicsBackend.cocoa;
1262                 gui_apiWorksInPhysicalPixels = false;
1263                 return true;
1264             }
1265 
1266         version(linux)
1267             if (strcmp(api, CLAP_WINDOW_API_X11.ptr) == 0)
1268             {
1269                 gui_backend = GraphicsBackend.x11;
1270                 gui_apiWorksInPhysicalPixels = true;
1271                 return true;
1272             }
1273         return false;
1274     }
1275 
1276     void gui_destroy()
1277     {
1278         mixin(GraphicsMutexLock);
1279         _client.closeGUI();
1280     }
1281 
1282     bool gui_set_scale(double scale)
1283     {
1284         mixin(GraphicsMutexLock);
1285         // FUTURE: We currently do nothing with that information.
1286         gui_scale = scale; 
1287         return true;
1288     }
1289 
1290     bool gui_get_size(uint *width, uint *height)
1291     {
1292         mixin(GraphicsMutexLock);
1293         // FUTURE: physical vs logical?
1294         int widthLogical, heightLogical;
1295         
1296         if (!_client.getGUISize(&widthLogical, &heightLogical))
1297             return false;
1298 
1299         if (widthLogical < 0 || heightLogical < 0)
1300             return false;
1301 
1302         *width = widthLogical;
1303         *height = heightLogical;
1304         return true;
1305     }
1306 
1307     bool gui_can_resize()
1308     {
1309         mixin(GraphicsMutexLock);
1310         return _client.getGraphics().isResizeable();
1311     }
1312 
1313     bool gui_get_resize_hints(clap_gui_resize_hints_t *hints)
1314     {
1315         mixin(GraphicsMutexLock);
1316         IGraphics gr = _client.getGraphics();
1317         int[2] AR    = gr.getPreservedAspectRatio();
1318         hints.can_resize_horizontally = gr.isResizeableHorizontally();
1319         hints.can_resize_vertically   = gr.isResizeableVertically();
1320         hints.preserve_aspect_ratio   = gr.isAspectRatioPreserved();
1321         hints.aspect_ratio_width      = AR[0];
1322         hints.aspect_ratio_height     = AR[1];
1323         return true;
1324     }
1325 
1326     bool gui_adjust_size(uint *width, uint *height)
1327     {
1328         // FUTURE: physical vs logical?
1329         mixin(GraphicsMutexLock);
1330         IGraphics gr = _client.getGraphics();
1331         int w = *width;
1332         int h = *height;
1333         if (w < 0 || h < 0) return false;
1334         gr.getMaxSmallerValidSize(&w, &h);
1335         if (w < 0 || h < 0) return false;
1336         *width  = w;
1337         *height = h;
1338         return true;
1339     }
1340 
1341     bool gui_set_size(uint width, uint height)
1342     {
1343         // FUTURE: physical vs logical?
1344         mixin(GraphicsMutexLock);
1345         IGraphics gr = _client.getGraphics();
1346         return gr.nativeWindowResize(width, height);
1347     }
1348 
1349     bool gui_set_parent(const(clap_window_t)* window)
1350     {
1351         mixin(GraphicsMutexLock);
1352         gui_parent_handle = cast(void*)(window.ptr);
1353         return true;
1354     }
1355 
1356     bool gui_set_transient(const(clap_window_t)* window)
1357     {
1358         // no support
1359         return false;
1360     }
1361 
1362     void gui_suggest_title(const(char)* title)
1363     {
1364         // ignore
1365     }
1366 
1367     bool gui_show()
1368     {
1369         mixin(GraphicsMutexLock);
1370         _client.openGUI(gui_parent_handle, null, gui_backend);
1371         return true;
1372     }
1373 
1374     bool gui_hide()
1375     {
1376         mixin(GraphicsMutexLock);
1377         _client.closeGUI();
1378         return true;
1379     }
1380 
1381     // state impl
1382 
1383     Vec!ubyte _lastChunkLoad;
1384 
1385     // Protect save/load.
1386     // clap-validator calls save() and load() at the same time
1387     UncheckedMutex _stateMutex; 
1388 
1389     enum string StateMutexLock =
1390         `_stateMutex.lockLazy();
1391         scope(exit) _stateMutex.unlock();`;
1392 
1393     bool state_save(const(clap_ostream_t)* stream)
1394     {
1395         mixin(StateMutexLock);
1396         
1397         // PERF: could amortize alloc with 
1398         //       `appendStateChunkFromCurrentState()`
1399         PresetBank bank = _client.presetBank;
1400         assert(stream);
1401         ubyte[] state = bank.getStateChunkFromCurrentState();
1402         assert(state);
1403 
1404         if (state.length > uint.max)
1405             return false; // absurd long chunk
1406 
1407         // write size of chunk
1408         uint size = cast(uint)state.length;
1409         version(LittleEndian)
1410             writeExactly(stream, &size, uint.sizeof);
1411         else
1412             static assert(false);
1413 
1414         // write chunk
1415         writeExactly(stream, state.ptr, state.length);
1416 
1417         free(state.ptr);
1418         return true;
1419     }
1420 
1421     bool state_load(const(clap_istream_t)* stream)
1422     {
1423         mixin(StateMutexLock);
1424         assert(stream);
1425 
1426         // read size of chunk
1427         uint size = 0;
1428         if (readExactly(stream, &size, uint.sizeof) != uint.sizeof)
1429             return false;
1430 
1431         version(LittleEndian)
1432         {}
1433         else
1434             static assert(false);
1435 
1436         // Note sure if we should load chunk with zero size,
1437         // OTOH those chunk probably won't ever exist.
1438         if (size == 0)
1439             return true;
1440 
1441         // read chunk
1442         _lastChunkLoad.resize(size);
1443         if (readExactly(stream, _lastChunkLoad.ptr, size) == size)
1444         {
1445             // apply chunk
1446             bool err = false;
1447 
1448             _client.presetBank.loadStateChunk(_lastChunkLoad[], &err);
1449             if (err)
1450                 return false;
1451 
1452             // Ask for param value rescan.
1453             _host.notifyRequestParamRescan(CLAP_PARAM_RESCAN_VALUES);
1454             return true;
1455         }
1456         else
1457             return false;
1458     }
1459 
1460     // preset-load impl
1461 
1462     bool preset_load_from_location(uint location_kind,
1463                                    const(char)* location,
1464                                    const(char)* load_key)
1465     {
1466         int index;
1467 
1468         if (location_kind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN)
1469             goto error;
1470 
1471         // Same remark as for indexing: MultiTrackStudio sends 
1472         // non-null location, ignore that.
1473 
1474         if (sscanf(load_key, "%d", &index) != 1)
1475             goto error;
1476         if (index < 0 || index > _client.presetBank.numPresets)
1477             goto error;
1478 
1479         _client.presetBank.loadPresetFromHost(index);
1480 
1481         _host.notifyPresetLoaded(location_kind,
1482                                         location,
1483                                         load_key);
1484         // Ask for param value rescan.
1485         _host.notifyRequestParamRescan(CLAP_PARAM_RESCAN_VALUES);
1486         return true;
1487 
1488     error:
1489         _host.notifyPresetError(location_kind,
1490                                 location,
1491                                 load_key,
1492                                 0,
1493                                 "Couldn't load preset");
1494         return false;
1495     }
1496 
1497 
1498     // audio-ports-config impl
1499 
1500     uint ports_config_count()
1501     {   
1502         LegalIO[] legalIOs = _client.legalIOs();
1503         return cast(uint) (legalIOs.length);
1504     }
1505 
1506     bool ports_config_get(uint                        index,
1507                           clap_audio_ports_config_t* config)
1508     {
1509         LegalIO[] legalIOs = _client.legalIOs();
1510         if (index >= legalIOs.length)
1511             return false;
1512 
1513         LegalIO* io = &legalIOs[index];
1514         config.id = index;
1515         // Call that "2-2" for stereo, etc
1516         snprintf(config.name.ptr, CLAP_NAME_SIZE, "%d-%d", 
1517                  io.numInputChannels, io.numOutputChannels);
1518         int inChannels  = io.numInputChannels;
1519         int outChannels = io.numOutputChannels;
1520         config.input_port_count  = inChannels  ? 1 : 0;
1521         config.output_port_count = outChannels ? 1 : 0;
1522         config.has_main_input  = (inChannels != 0);
1523         config.has_main_output = (outChannels != 0);
1524         config.main_input_channel_count  = inChannels;
1525         config.main_output_channel_count = outChannels;
1526         config.main_input_port_type  = portTypeChans(inChannels);
1527         config.main_output_port_type = portTypeChans(outChannels);
1528         return true;
1529     }
1530 
1531     static const(char)* portTypeChans(int channels) pure
1532     {
1533         switch(channels)
1534         {
1535             case 1: return CLAP_PORT_MONO.ptr;
1536             case 2: return CLAP_PORT_STEREO.ptr;
1537             default:
1538                 // no support yet for ambisonic or surround
1539                 return null; 
1540         }
1541     }
1542 
1543     bool ports_config_select(clap_id config_id)
1544     {
1545         LegalIO[] legalIOs = _client.legalIOs();
1546         uint index = config_id;
1547         if (index >= legalIOs.length)
1548             return false;
1549 
1550         LegalIO* io = &legalIOs[index];
1551         Bus* mainIn = getMainInputBus();
1552         Bus* mainOut = getMainOutputBus();
1553 
1554         // FUTURE: this would set the number of channels in SC too
1555         if (mainIn) mainIn.numChannels = io.numInputChannels;
1556         if (mainOut) mainOut.numChannels = io.numOutputChannels;
1557         return true;
1558     }
1559 
1560     clap_id ports_current_config()
1561     {
1562         LegalIO[] legalIOs = _client.legalIOs();
1563         Bus* mainIn = getMainInputBus();
1564         Bus* mainOut = getMainOutputBus();
1565         int inChannels = 0;
1566         int outChannels = 0;
1567         if (mainIn)  inChannels  = mainIn.numChannels;
1568         if (mainOut) outChannels = mainOut.numChannels;
1569         foreach(size_t nth, ref io; legalIOs)
1570         {
1571             if ( (io.numInputChannels == inChannels)
1572               && (io.numOutputChannels == outChannels) )
1573                 return cast(clap_id)nth;
1574         }
1575         return CLAP_INVALID_ID;
1576     }
1577 
1578     bool ports_config_info_get(clap_id config_id,
1579                                uint    port_index,
1580                                bool    is_input,
1581                                clap_audio_port_info_t *info)
1582     {
1583         LegalIO[] legalIOs = _client.legalIOs();
1584         uint index = config_id;
1585         if (index >= legalIOs.length)
1586             return false;
1587         LegalIO* io = &legalIOs[index];
1588         Bus* b = getBus(is_input, port_index);
1589         if (!b)
1590             return false;
1591 
1592         info.id = convertBusIndexToBusID(port_index);
1593         snprintf(info.name.ptr, CLAP_NAME_SIZE, "%.*s", 
1594                  cast(int)(b.name.length), b.name.ptr);
1595 
1596         info.flags = 0;
1597         if (b.isMain)
1598             info.flags |= CLAP_AUDIO_PORT_IS_MAIN;
1599         info.channel_count = is_input ? io.numInputChannels
1600                                       : io.numOutputChannels;
1601 
1602         info.port_type = portTypeChans(info.channel_count);
1603 
1604         // True luxury in life is moments like that, letting the host 
1605         // deal with that at last.
1606         info.in_place_pair = CLAP_INVALID_ID; 
1607         return true;
1608     }
1609 
1610     // note-ports impl
1611     uint note_ports_count(bool is_input)
1612     {
1613         if (is_input)
1614             return cast(uint) noteInputs.length;
1615         else
1616             return cast(uint) noteOutputs.length;
1617     }
1618 
1619     bool note_ports_get(uint index,
1620                         bool is_input,
1621                         clap_note_port_info_t *info) 
1622     {
1623         NoteBus* bus = getNoteBus(is_input, index);
1624         if (!bus) 
1625             return false;
1626         with(info)
1627         {
1628             id = convertBusIndexToBusID(index);
1629             supported_dialects = CLAP_NOTE_DIALECT_MIDI;
1630             
1631             // disabled since bizarre semantics I'm unsure of
1632             // and no time to test that
1633             //supported_dialects |= CLAP_NOTE_DIALECT_CLAP;
1634             
1635             preferred_dialect = CLAP_NOTE_DIALECT_MIDI;
1636             snprintf(name.ptr, CLAP_NAME_SIZE, "Events");
1637         }
1638         return true;
1639     }
1640 
1641     // configurable audio ports
1642 
1643     bool conf_can_apply_config(
1644         const(clap_audio_port_configuration_request_t)* requests,
1645         uint request_count)
1646     {
1647         int ioIndex = matchLegalIO(requests, request_count);
1648         if (ioIndex == -1)
1649             return false;
1650 
1651         // Yes, we found a legalIO that can do that.
1652         return true;
1653     }
1654 
1655     bool conf_apply_config(
1656         const(clap_audio_port_configuration_request_t)* requests,
1657         uint request_count)
1658     {
1659         int ioIndex = matchLegalIO(requests, request_count);
1660         if (ioIndex == -1)
1661             return false;
1662 
1663         // Note: legalIOs index are same as config clap_id
1664         return ports_config_select(ioIndex);
1665     }
1666 
1667     int matchLegalIO(
1668         const(clap_audio_port_configuration_request_t)* requests,
1669         uint request_count)
1670     {
1671         LegalIO[] legalIOs = _client.legalIOs();
1672         int bestIndex = -1;
1673         int bestScore = -1;
1674 
1675         foreach(size_t index, io; legalIOs)
1676         {
1677             int score = 0;
1678 
1679             // match each of the requests with &&
1680 
1681             for (uint n = 0; n < request_count; ++n)
1682             {
1683                 auto r = &requests[n];
1684 
1685                 // Does that port exist?
1686                 Bus* b = getBus(r.is_input, r.port_index);
1687                 if (!b)
1688                 {
1689                     if (r.channel_count != 0)
1690                     {
1691                         score = -1; // fail, expected some channels
1692                         break;
1693                     }
1694                     else
1695                         continue; // bus is matching that zero chan
1696                 }
1697 
1698                 if (r.port_index != 0)
1699                 {
1700                     score = -1; // fail, no support for multiple ports
1701                     break;
1702                 }
1703 
1704                 int chan = r.is_input ? io.numInputChannels : io.numOutputChannels;
1705                 if (chan == r.channel_count)
1706                 {
1707                     // good number of channel
1708                     score += 1;
1709                 }
1710 
1711                 // Note: ignoring port_type or port_details here
1712             }
1713 
1714             if (score > bestScore)
1715             {
1716                 bestIndex = cast(int) index;
1717                 bestScore = score;
1718             }
1719         }
1720 
1721         return bestIndex; // return choosen legalIO
1722     }
1723 }
1724 
1725 extern(C) static
1726 {
1727     enum string ClientCallback =
1728         `ScopedForeignCallback!(false, true) sc;
1729         sc.enter();
1730         CLAPClient client = cast(CLAPClient)(plugin.plugin_data);`;
1731 
1732     // plugin callbacks
1733 
1734     bool plugin_init(const(clap_plugin_t)* plugin)
1735     {
1736         mixin(ClientCallback);
1737         return client.initFun();
1738     }
1739 
1740     void plugin_destroy(const(clap_plugin_t)* plugin)
1741     {
1742         mixin(ClientCallback);
1743         client.destroyFun();
1744     }
1745 
1746     bool plugin_activate(const(clap_plugin_t)* plugin,
1747                          double           sample_rate,
1748                          uint        min_frames_count,
1749                          uint        max_frames_count)
1750     {
1751         mixin(ClientCallback);
1752         return client.activate(sample_rate, 
1753                                min_frames_count, 
1754                                max_frames_count);
1755     }
1756 
1757     void plugin_deactivate(const(clap_plugin_t)*plugin)
1758     {
1759         mixin(ClientCallback);
1760         return client.deactivate();
1761     }
1762 
1763     bool plugin_start_processing(const(clap_plugin_t)*plugin) 
1764     {
1765         mixin(ClientCallback);
1766         return client.start_processing();
1767     }
1768 
1769     void plugin_stop_processing(const(clap_plugin_t)*plugin) 
1770     {
1771         mixin(ClientCallback);
1772         client.stop_processing();
1773     }
1774 
1775     void plugin_reset(const(clap_plugin_t)*plugin) 
1776     {
1777         mixin(ClientCallback);
1778         client.reset();
1779     }
1780 
1781     clap_process_status plugin_process(const(clap_plugin_t)* plugin, 
1782                                        const(clap_process_t)*    pp)
1783     {
1784         mixin(ClientCallback);
1785         return client.process(pp);
1786     }
1787 
1788     const(void)* plugin_get_extension(const(clap_plugin_t)* plugin, 
1789                                       const(char)*              id)
1790     {
1791         mixin(ClientCallback);
1792         return client.get_extension(id);
1793     }
1794 
1795     void plugin_on_main_thread(const(clap_plugin_t)*plugin)
1796     {
1797         // do nothing here
1798     }
1799 
1800 
1801     // clap.params callbacks
1802 
1803     uint plugin_params_count(const(clap_plugin_t)*plugin)
1804     {
1805         mixin(ClientCallback);
1806         return client.params_count();
1807     }
1808 
1809     bool plugin_params_get_info(const(clap_plugin_t)*  plugin, 
1810                                 uint              param_index, 
1811                                 clap_param_info_t* param_info)
1812     {
1813         mixin(ClientCallback);
1814         return client.params_get_info(param_index, param_info);
1815     }
1816 
1817     bool plugin_params_get_value(const(clap_plugin_t)*plugin, 
1818                                  clap_id            param_id, 
1819                                  double*           out_value)
1820     {
1821         mixin(ClientCallback);
1822         return client.params_get_value(param_id, out_value);
1823     }
1824 
1825     // eg: "2.3 kHz"
1826     bool plugin_params_value_to_text(const(clap_plugin_t)* plugin,
1827                                      clap_id             param_id,
1828                                      double                 value,
1829                                      char*             out_buffer,
1830                                      uint     out_buffer_capacity)
1831     {
1832         mixin(ClientCallback);
1833         return client.params_value_to_text(param_id, value, 
1834             out_buffer, out_buffer_capacity);
1835     }
1836 
1837     bool plugin_params_text_to_value(const(clap_plugin_t)*  plugin,
1838                                      clap_id              param_id,
1839                                      const(char)* param_value_text,
1840                                      double*             out_value)
1841     {
1842         mixin(ClientCallback);
1843         return client.params_text_to_value(param_id, 
1844             param_value_text, out_value);
1845     }
1846 
1847     void plugin_params_flush(const(clap_plugin_t)*      plugin,
1848                              const(clap_input_events_t)*   in_,
1849                              const(clap_output_events_t)* out_)
1850     {
1851         mixin(ClientCallback);
1852         return client.params_flush(in_, out_);
1853     }
1854 
1855     uint plugin_audio_ports_count(const(clap_plugin_t)* plugin, 
1856                                   bool                is_input)
1857     {
1858         mixin(ClientCallback);
1859         return client.audio_ports_count(is_input);
1860     }
1861 
1862     bool plugin_audio_ports_get(const(clap_plugin_t)* plugin,
1863                                 uint                   index,
1864                                 bool                is_input,
1865                                 clap_audio_port_info_t *info)
1866     {
1867         mixin(ClientCallback);
1868         return client.audio_ports_get(index, is_input, info);
1869     }
1870 
1871 
1872     // gui callbacks
1873 
1874     bool plugin_gui_is_api_supported(const(clap_plugin_t)* plugin, 
1875                                      const(char)*             api, 
1876                                      bool             is_floating)
1877     {
1878         mixin(ClientCallback);
1879         return client.gui_is_api_supported(api, is_floating);
1880     }
1881 
1882     bool plugin_gui_get_preferred_api(const(clap_plugin_t)* plugin, 
1883                                       const(char)**            api, 
1884                                       bool*            is_floating) 
1885     {
1886         mixin(ClientCallback);
1887         return client.gui_get_preferred_api(api, is_floating);
1888     }
1889 
1890     bool plugin_gui_create(const(clap_plugin_t)* plugin, 
1891                            const(char)*             api, 
1892                            bool             is_floating)
1893     {
1894         mixin(ClientCallback);
1895         return client.gui_create(api, is_floating);
1896     }
1897 
1898     void plugin_gui_destroy(const(clap_plugin_t)* plugin)
1899     {
1900         mixin(ClientCallback);
1901         return client.gui_destroy();
1902     }
1903     
1904     bool plugin_gui_set_scale(const(clap_plugin_t)* plugin, double s)
1905     {
1906         mixin(ClientCallback);
1907         return client.gui_set_scale(s);
1908     }
1909 
1910     bool plugin_gui_get_size(const(clap_plugin_t)* plugin, 
1911                              uint*                  width, 
1912                              uint*                 height)
1913     {
1914         mixin(ClientCallback);
1915         return client.gui_get_size(width, height);
1916     }
1917 
1918     bool plugin_gui_can_resize(const(clap_plugin_t)* plugin)
1919     {
1920         mixin(ClientCallback);
1921         return client.gui_can_resize();
1922     }
1923 
1924     bool plugin_gui_get_resize_hints(const(clap_plugin_t)*   plugin, 
1925                                      clap_gui_resize_hints_t* hints)
1926     {
1927         mixin(ClientCallback);
1928         return client.gui_get_resize_hints(hints);
1929     }
1930 
1931     bool plugin_gui_adjust_size(const(clap_plugin_t)* plugin, 
1932                                 uint*                  width, 
1933                                 uint*                 height)
1934     {
1935         mixin(ClientCallback);
1936         return client.gui_adjust_size(width, height);
1937     }
1938 
1939     bool plugin_gui_set_size(const(clap_plugin_t)* plugin, 
1940                              uint                   width, 
1941                              uint                  height)
1942     {
1943         mixin(ClientCallback);
1944         return client.gui_set_size(width, height);
1945     }
1946 
1947     bool plugin_gui_set_parent(const(clap_plugin_t)* plugin, 
1948                                const(clap_window_t)* window)
1949     {
1950         mixin(ClientCallback);
1951         return client.gui_set_parent(window);
1952     }
1953 
1954     bool plugin_gui_set_transient(const(clap_plugin_t)* plugin, 
1955                                   const(clap_window_t)* window)
1956     {
1957         mixin(ClientCallback);
1958         return client.gui_set_transient(window);
1959     }
1960 
1961     void plugin_gui_suggest_title(const(clap_plugin_t)* plugin, 
1962                                   const(char)*           title)
1963     {
1964         mixin(ClientCallback);
1965         return client.gui_suggest_title(title);
1966     }
1967 
1968     bool plugin_gui_show(const(clap_plugin_t)* plugin)
1969     {
1970         mixin(ClientCallback);
1971         return client.gui_show();
1972     }
1973 
1974     bool plugin_gui_hide(const(clap_plugin_t)* plugin)
1975     {
1976         mixin(ClientCallback);
1977         return client.gui_hide();
1978     }
1979 
1980     // latency callbacks
1981     uint plugin_latency_get(const(clap_plugin_t)* plugin)
1982     {
1983         mixin(ClientCallback);
1984         int samples = client._latencySamples;
1985         assert(samples >= 0);
1986         return samples;
1987     }
1988 
1989     // tail callbacks
1990     uint plugin_tail_get(const(clap_plugin_t)* plugin)
1991     {
1992         mixin(ClientCallback);
1993         int samples = client._tailSamples;
1994         assert(samples >= 0);
1995         return samples;
1996     }
1997 
1998     // state callbacks
1999 
2000     bool plugin_state_save(const(clap_plugin_t)*  plugin, 
2001                            const(clap_ostream_t)* stream)
2002     {
2003         mixin(ClientCallback);
2004         return client.state_save(stream);
2005     }
2006 
2007     bool plugin_state_load(const(clap_plugin_t)*  plugin, 
2008                            const(clap_istream_t)* stream)
2009     {
2010         mixin(ClientCallback);
2011         return client.state_load(stream);
2012     }
2013 
2014     // preset-load impl
2015     bool plugin_preset_load_from_location(
2016         const(clap_plugin_t)*plugin,
2017         uint                 location_kind,
2018         const(char)         *location,
2019         const(char)         *load_key)
2020     {
2021         mixin(ClientCallback);
2022         return client.preset_load_from_location(location_kind, 
2023                                                 location, 
2024                                                 load_key);
2025     }
2026 
2027     // audio-port-config callbacks
2028 
2029     uint plugin_ports_config_count(const(clap_plugin_t)* plugin)
2030     {
2031         mixin(ClientCallback);
2032         return client.ports_config_count();
2033     }
2034 
2035     bool plugin_ports_config_get(const(clap_plugin_t)*      plugin,
2036                                  uint                        index,
2037                                  clap_audio_ports_config_t* config)
2038     {
2039         mixin(ClientCallback);
2040         return client.ports_config_get(index, config);
2041     }
2042 
2043     bool plugin_ports_config_select(const(clap_plugin_t)* plugin, 
2044                                     clap_id            config_id)
2045     {
2046         mixin(ClientCallback);
2047         return client.ports_config_select(config_id);
2048     }
2049 
2050     // clap_plugin_audio_ports_config_info_t
2051 
2052     clap_id plugin_ports_current_config(const(clap_plugin_t)*plugin)
2053     {
2054         mixin(ClientCallback);
2055         return client.ports_current_config();
2056     }
2057 
2058     bool plugin_ports_config_info_get(const(clap_plugin_t)*plugin,
2059         clap_id config_id,
2060         uint    port_index,
2061         bool    is_input,
2062         clap_audio_port_info_t *info)
2063     {
2064         mixin(ClientCallback);
2065         return client.ports_config_info_get(config_id, port_index, 
2066             is_input, info);
2067     }
2068 
2069     // note-ports callbacks
2070 
2071     uint plugin_note_ports_count(const(clap_plugin_t)* plugin, 
2072                                  bool                is_input)
2073     {
2074         mixin(ClientCallback);
2075         return client.note_ports_count(is_input);
2076     }
2077 
2078     // Get info about a note port.
2079     // Returns true on success and stores the result into info.
2080     // [main-thread]
2081     bool plugin_note_ports_get(const(clap_plugin_t)* plugin,
2082                                uint                   index,
2083                                bool                is_input,
2084                                clap_note_port_info_t*  info)
2085     {
2086         mixin(ClientCallback);
2087         return client.note_ports_get(index, is_input, info);
2088     }
2089 
2090     // configurable-audio-ports callbacks
2091 
2092     bool plugin_conf_can_apply_config(
2093         const(clap_plugin_t)* plugin,
2094         const(clap_audio_port_configuration_request_t)* requests,
2095         uint request_count)
2096     {
2097         mixin(ClientCallback);
2098         return client.conf_can_apply_config(requests,
2099                                             request_count);
2100     }
2101 
2102     bool plugin_conf_apply_config(
2103         const(clap_plugin_t)* plugin,
2104         const(clap_audio_port_configuration_request_t)* requests,
2105         uint request_count)
2106     {
2107         mixin(ClientCallback);
2108         return client.conf_apply_config(requests,
2109                                         request_count);
2110     }
2111 }
2112 
2113 
2114 // CLAP host commands
2115 
2116 class CLAPHost : IHostCommand
2117 {
2118 nothrow @nogc:
2119     this(CLAPClient backRef, const(clap_host_t)* host)
2120     {
2121         _backRef      = backRef;
2122         _host         = host;
2123         _host_gui     = cast(clap_host_gui_t*)     
2124                         host.get_extension(host, "clap.gui".ptr);
2125         _host_latency = cast(clap_host_latency_t*) 
2126                         host.get_extension(host, "clap.latency".ptr);
2127         _host_params  = cast(clap_host_params_t*)  
2128                         host.get_extension(host, "clap.params".ptr);
2129         _host_tail    = cast(clap_host_tail_t*)    
2130                         host.get_extension(host, "clap.tail".ptr);
2131         _host_preset  = cast(clap_host_preset_load_t*) 
2132                    host.get_extension(host, CLAP_EXT_PRESET_LOAD.ptr);
2133         if (_host_preset)
2134             return;
2135         _host_preset  = cast(clap_host_preset_load_t*) 
2136             host.get_extension(host, CLAP_EXT_PRESET_LOAD_COMPAT.ptr);
2137     }
2138 
2139     /// Notifies the host that editing of a parameter has begun from 
2140     /// UI side.
2141     override void beginParamEdit(int paramIndex)
2142     {
2143         Parameter p = _backRef._client.param(paramIndex);
2144         if (!p)
2145             return;
2146         _backRef.enqueueParamBeginEdit(p);
2147         notifyRequestFlush();
2148     }
2149 
2150     /// Notifies the host that a parameter was edited from the UI side.
2151     /// This enables the host to record automation.
2152     /// It is illegal to call `paramAutomate` outside of a 
2153     /// `beginParamEdit`/`endParamEdit` pair.
2154     override void paramAutomate(int paramIndex, float value)
2155     {
2156         Parameter p = _backRef._client.param(paramIndex);
2157         if (!p)
2158             return;
2159         _backRef.enqueueParamChange(p);
2160         notifyRequestFlush();
2161     }
2162 
2163     /// Notifies the host that editing of a parameter has finished 
2164     /// from UI side.
2165     override void endParamEdit(int paramIndex)
2166     {
2167         Parameter p = _backRef._client.param(paramIndex);
2168         if (!p)
2169             return;
2170         _backRef.enqueueParamEndEdit(p);
2171         notifyRequestFlush();
2172     }
2173 
2174     /// Requests to the host a resize of the plugin window's PARENT 
2175     /// window, given logical pixels of plugin window.
2176     ///
2177     /// Note: UI widgets and plugin format clients have different 
2178     ///       coordinate systems.
2179     ///
2180     /// Params:
2181     ///     width New width of the plugin, in logical pixels.
2182     ///     height New height of the plugin, in logical pixels.
2183     /// Returns: `true` if the host parent window has been resized.
2184     override bool requestResize(int  widthLogicalPixels, 
2185                                 int heightLogicalPixels)
2186     {
2187         if (!_host_gui)
2188             return false;
2189         if (widthLogicalPixels < 0 || heightLogicalPixels < 0)
2190             return false;
2191         return _host_gui.request_resize(_host, 
2192                                         widthLogicalPixels, 
2193                                         heightLogicalPixels);
2194     }
2195 
2196     override bool notifyResized()
2197     {
2198         return false;
2199     }
2200 
2201     void notifyRequestFlush()
2202     {
2203         // says to the host to call flush or process, so that input
2204         // and output events can be processed
2205         if (_host_params)
2206             _host_params.request_flush(_host);
2207     }
2208 
2209     void notifyRequestParamRescan(clap_param_rescan_flags flags)
2210     {
2211         // says to the host to rescan parameters
2212         if (_host_params)
2213             _host_params.rescan(_host, flags);
2214     }
2215 
2216     // Tell the host the latency changed while activated.
2217     bool notifyLatencyChanged()
2218     {
2219         if (_host_latency)
2220         {
2221             _host_latency.changed(_host);
2222             return true;
2223         }
2224         else
2225             return false;
2226     }
2227 
2228     // Tell the host the tail size changed.
2229     bool notifyTailChanged()
2230     {
2231         if (_host_tail)
2232         {
2233             _host_tail.changed(_host);
2234             return true;
2235         }
2236         else 
2237             return false;
2238     }
2239 
2240     void notifyPresetLoaded(uint location_kind,
2241                             const(char) *location,
2242                             const(char) *load_key)
2243     {
2244         if (_host_preset)
2245             _host_preset.loaded(_host, 
2246                                 location_kind, 
2247                                 location, 
2248                                 load_key);
2249     }
2250 
2251     void notifyPresetError(uint location_kind,
2252                            const(char) *location,
2253                            const(char) *load_key,
2254                            int os_error,
2255                            const(char)* msg)
2256     {
2257         if (_host_preset)
2258             _host_preset.on_error(_host, 
2259                                   location_kind, 
2260                                   location, 
2261                                   load_key,
2262                                   os_error,
2263                                   msg);
2264     }
2265 
2266     DAW getDAW()
2267     {
2268         char[128] dawStr;
2269         snprintf(dawStr.ptr, 128, "%s", _host.name);
2270 
2271         // Force lowercase
2272         for (char* p =  dawStr.ptr; *p != '\0'; ++p)
2273         {
2274             if (*p >= 'A' && *p <= 'Z')
2275                 *p += ('a' - 'A');
2276         }
2277 
2278         return identifyDAW(dawStr.ptr);
2279     }
2280 
2281     PluginFormat getPluginFormat()
2282     {
2283         return PluginFormat.clap;
2284     }
2285 
2286     CLAPClient                      _backRef;
2287     const(clap_host_t)*             _host;
2288     const(clap_host_gui_t)*         _host_gui;
2289     const(clap_host_latency_t)*     _host_latency;
2290     const(clap_host_params_t)*      _host_params;
2291     const(clap_host_tail_t)*        _host_tail;
2292     const(clap_host_preset_load_t)* _host_preset;
2293 }
2294 
2295 class CLAPPresetProvider
2296 {
2297 public:
2298 nothrow:
2299 @nogc:
2300 
2301     this(Client client, const(clap_preset_discovery_indexer_t)* idxer)
2302     {
2303         _indexer = idxer;
2304         _client = client;
2305     }
2306 
2307     ~this()
2308     {
2309         destroyFree(_client);
2310     }
2311 
2312     UncheckedMutex _presetMutex;
2313     __gshared clap_preset_discovery_filetype_t filetype;
2314     __gshared clap_universal_plugin_id_t thisPlugin;
2315 
2316     bool init_()
2317     {
2318         _presetMutex.lockLazy();
2319         scope(exit) _presetMutex.unlock();
2320 
2321         clap_preset_discovery_location_t loc;
2322         loc.flags = CLAP_PRESET_DISCOVERY_IS_FACTORY_CONTENT;
2323         loc.name  = "Factory presets";
2324         loc.kind = CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN;
2325         loc.location = null;
2326 
2327         thisPlugin.abi = "clap";
2328         thisPlugin.id  = assumeZeroTerminated(_client.CLAPIdentifier);
2329 
2330         // Note: this file extension makes no sense but CLAP
2331         //       apparently forces us to declare one.
2332         filetype.name = "Dplug CLAP chunk";
2333         filetype.description = "Dplug factory preset format";
2334         filetype.file_extension = "patch";
2335 
2336         // FUTURE: rare error ignored here
2337         bool res = _indexer.declare_filetype(_indexer, &filetype);
2338         res = _indexer.declare_location(_indexer, &loc);
2339         return true;
2340     }
2341 
2342     bool get_metadata(
2343         uint                                  location_kind,
2344         const(char)*                               location,
2345         const(clap_preset_discovery_metadata_receiver_t)* m)
2346     {
2347         _presetMutex.lockLazy();
2348         scope(exit) _presetMutex.unlock();
2349 
2350         if (location_kind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN)
2351             return false;
2352 
2353         // Here is a trick, multi-track studio when given a NULL 
2354         // location, will get back to us with a "" location, and a
2355         // non-null pointer. Do NOT check location.
2356 
2357         PresetBank bank = _client.presetBank();
2358         int numPresets = bank.numPresets();
2359 
2360         for (int n = 0; n < numPresets; ++n)
2361         {
2362             Preset preset = bank.preset(n);
2363 
2364             // Assuming the load key is copied here.
2365             // Load key is simply the preset index.
2366             char[24] load_key;
2367             snprintf(load_key.ptr, 24, "%d", n);
2368             const(char)* nameZ = assumeZeroTerminated(preset.name);
2369             if (!m.begin_preset(m, nameZ, load_key.ptr))
2370                 break;
2371 
2372             // say this preset is for that plugin
2373             m.add_plugin_id(m, &thisPlugin);
2374             m.set_flags(m, CLAP_PRESET_DISCOVERY_IS_FACTORY_CONTENT);
2375         }
2376         return true;
2377     }
2378 
2379 private:
2380     const(clap_preset_discovery_indexer_t)* _indexer;
2381     Client _client;
2382 }
2383 
2384 extern(C) static
2385 {
2386     enum string PresetCallback =
2387         `ScopedForeignCallback!(false, true) sc;
2388         sc.enter();
2389         CLAPPresetProvider provobj = cast(CLAPPresetProvider)
2390             cast(Object)(provider.provider_data);`;
2391 
2392     alias clap_preset_dp_t = clap_preset_discovery_provider_t;
2393 
2394     // plugin callbacks
2395 
2396     bool provider_init(const(clap_preset_dp_t)* provider)
2397     {
2398         mixin(PresetCallback);
2399         return provobj.init_();
2400     }
2401 
2402     void provider_destroy(const(clap_preset_dp_t)* provider)
2403     {
2404         mixin(PresetCallback);
2405         destroyFree(provobj);
2406     }
2407 
2408     bool provider_get_metadata(
2409         const(clap_preset_dp_t)* provider,
2410         uint location_kind,
2411         const(char)* location,
2412         const(clap_preset_discovery_metadata_receiver_t)* mr)
2413     {
2414         mixin(PresetCallback);
2415         return provobj.get_metadata(location_kind, location, mr);
2416     }
2417 
2418     const(void)* provider_get_extension(
2419         const(clap_preset_dp_t)* provider,
2420         const(char)* extension_id)
2421     {
2422         return null;
2423     }
2424 }