1 /*
2  * XY Controller UI, taken from Cadence
3  * Copyright (C) 2011-2020 Filipe Coelho <falktx@falktx.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of
8  * the License, or any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * For a full copy of the GNU General Public License see the doc/GPL.txt file.
16  */
17 
18 #include "CarlaDefines.h"
19 
20 #include "CarlaMIDI.h"
21 #include "CarlaNativeExtUI.hpp"
22 
23 #include "midi-queue.hpp"
24 #include "water/text/StringArray.h"
25 
26 // -----------------------------------------------------------------------
27 
28 class XYControllerPlugin : public NativePluginAndUiClass
29 {
30 public:
31     enum Parameters {
32         kParamInX,
33         kParamInY,
34         kParamOutX,
35         kParamOutY,
36         kParamCount,
37     };
38 
XYControllerPlugin(const NativeHostDescriptor * const host)39     XYControllerPlugin(const NativeHostDescriptor* const host)
40         : NativePluginAndUiClass(host, "xycontroller-ui"),
41           params(),
42           channels(),
43           mqueue(),
44           mqueueRT()
45     {
46         carla_zeroStruct(params);
47         carla_zeroStruct(channels);
48         channels[0] = true;
49     }
50 
51 protected:
52     // -------------------------------------------------------------------
53     // Plugin parameter calls
54 
getParameterCount() const55     uint32_t getParameterCount() const override
56     {
57         return kParamCount;
58     }
59 
getParameterInfo(const uint32_t index) const60     const NativeParameter* getParameterInfo(const uint32_t index) const override
61     {
62         CARLA_SAFE_ASSERT_RETURN(index < kParamCount, nullptr);
63 
64         static NativeParameter param;
65 
66         int hints = NATIVE_PARAMETER_IS_ENABLED|NATIVE_PARAMETER_IS_AUTOMABLE;
67 
68         param.name = nullptr;
69         param.unit = "%";
70         param.ranges.def       = 0.0f;
71         param.ranges.min       = -100.0f;
72         param.ranges.max       = 100.0f;
73         param.ranges.step      = 1.0f;
74         param.ranges.stepSmall = 0.01f;
75         param.ranges.stepLarge = 10.0f;
76         param.scalePointCount  = 0;
77         param.scalePoints      = nullptr;
78 
79         switch (index)
80         {
81         case kParamInX:
82             param.name = "X";
83             break;
84         case kParamInY:
85             param.name = "Y";
86             break;
87         case kParamOutX:
88             hints |= NATIVE_PARAMETER_IS_OUTPUT;
89             param.name = "Out X";
90             break;
91         case kParamOutY:
92             hints |= NATIVE_PARAMETER_IS_OUTPUT;
93             param.name = "Out Y";
94             break;
95         }
96 
97         param.hints = static_cast<NativeParameterHints>(hints);
98 
99         return &param;
100     }
101 
getParameterValue(const uint32_t index) const102     float getParameterValue(const uint32_t index) const override
103     {
104         CARLA_SAFE_ASSERT_RETURN(index < kParamCount, 0.0f);
105 
106         return params[index];
107     }
108 
109     // -------------------------------------------------------------------
110     // Plugin state calls
111 
setParameterValue(const uint32_t index,const float value)112     void setParameterValue(const uint32_t index, const float value) override
113     {
114         switch (index)
115         {
116         case kParamInX:
117         case kParamInY:
118             params[index] = value;
119             break;
120         }
121     }
122 
setCustomData(const char * const key,const char * const value)123     void setCustomData(const char* const key, const char* const value) override
124     {
125         CARLA_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',);
126         CARLA_SAFE_ASSERT_RETURN(value != nullptr,);
127 
128         if (std::strcmp(key, "channels") == 0)
129         {
130             const water::StringArray chans(water::StringArray::fromTokens(value, ",", ""));
131 
132             carla_zeroStruct(channels);
133 
134             for (const water::String *it=chans.begin(), *end=chans.end(); it != end; ++it)
135             {
136                 const int ichan = std::atoi((*it).toRawUTF8());
137                 CARLA_SAFE_ASSERT_INT_CONTINUE(ichan >= 1 && ichan <= 16, ichan);
138 
139                 channels[ichan-1] = true;
140             }
141         }
142     }
143 
144     // -------------------------------------------------------------------
145     // Plugin process calls
146 
process(const float * const *,float **,const uint32_t,const NativeMidiEvent * const midiEvents,const uint32_t midiEventCount)147     void process(const float* const*, float**, const uint32_t,
148                  const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override
149     {
150         params[kParamOutX] = params[kParamInX];
151         params[kParamOutY] = params[kParamInY];
152 
153         if (mqueue.isNotEmpty() && mqueueRT.tryToCopyDataFrom(mqueue))
154         {
155             uint8_t d1, d2, d3;
156             NativeMidiEvent ev = { 0, 0, 3, { 0, 0, 0, 0 } };
157 
158             while (mqueueRT.get(d1, d2, d3))
159             {
160                 ev.data[0] = d1;
161                 ev.data[1] = d2;
162                 ev.data[2] = d3;
163                 writeMidiEvent(&ev);
164             }
165         }
166 
167         for (uint32_t i=0; i < midiEventCount; ++i)
168             writeMidiEvent(&midiEvents[i]);
169     }
170 
171     // -------------------------------------------------------------------
172     // Pipe Server calls
173 
msgReceived(const char * const msg)174     bool msgReceived(const char* const msg) noexcept override
175     {
176         if (NativePluginAndUiClass::msgReceived(msg))
177             return true;
178 
179         if (std::strcmp(msg, "cc") == 0)
180         {
181             uint8_t cc, value;
182             CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(cc), true);
183             CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(value), true);
184 
185             const CarlaMutexLocker cml(mqueue.getMutex());
186 
187             for (int i=0; i<16; ++i)
188             {
189                 if (channels[i])
190                     if (! mqueue.put(uint8_t(MIDI_STATUS_CONTROL_CHANGE | (i & MIDI_CHANNEL_BIT)), cc, value))
191                         break;
192             }
193 
194             return true;
195         }
196 
197         if (std::strcmp(msg, "cc2") == 0)
198         {
199             uint8_t cc1, value1, cc2, value2;
200             CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(cc1), true);
201             CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(value1), true);
202             CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(cc2), true);
203             CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(value2), true);
204 
205             const CarlaMutexLocker cml(mqueue.getMutex());
206 
207             for (int i=0; i<16; ++i)
208             {
209                 if (channels[i])
210                 {
211                     if (! mqueue.put(uint8_t(MIDI_STATUS_CONTROL_CHANGE | (i & MIDI_CHANNEL_BIT)), cc1, value1))
212                         break;
213                     if (! mqueue.put(uint8_t(MIDI_STATUS_CONTROL_CHANGE | (i & MIDI_CHANNEL_BIT)), cc2, value2))
214                         break;
215                 }
216             }
217 
218             return true;
219         }
220 
221         if (std::strcmp(msg, "note") == 0)
222         {
223             bool onOff;
224             uint8_t note;
225             CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(onOff), true);
226             CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(note), true);
227 
228             const uint8_t status   = onOff ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF;
229             const uint8_t velocity = onOff ? 100 : 0;
230 
231             const CarlaMutexLocker cml(mqueue.getMutex());
232 
233             for (int i=0; i<16; ++i)
234             {
235                 if (channels[i])
236                     if (! mqueue.put(uint8_t(status | (i & MIDI_CHANNEL_BIT)), note, velocity))
237                         break;
238             }
239 
240             return true;
241         }
242 
243         return false;
244     }
245 
246 private:
247     float params[kParamCount];
248     bool channels[16];
249 
250     MIDIEventQueue<128> mqueue, mqueueRT;
251 
252     PluginClassEND(XYControllerPlugin)
253     CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(XYControllerPlugin)
254 };
255 
256 // -----------------------------------------------------------------------
257 
258 static const NativePluginDescriptor notesDesc = {
259     /* category  */ NATIVE_PLUGIN_CATEGORY_UTILITY,
260     /* hints     */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE
261                                                   |NATIVE_PLUGIN_HAS_UI),
262     /* supports  */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
263     /* audioIns  */ 0,
264     /* audioOuts */ 0,
265     /* midiIns   */ 1,
266     /* midiOuts  */ 1,
267     /* paramIns  */ 2,
268     /* paramOuts */ 2,
269     /* name      */ "XY Controller",
270     /* label     */ "xycontroller",
271     /* maker     */ "falkTX",
272     /* copyright */ "GNU GPL v2+",
273     PluginDescriptorFILL(XYControllerPlugin)
274 };
275 
276 // -----------------------------------------------------------------------
277 
278 CARLA_EXPORT
279 void carla_register_native_plugin_xycontroller();
280 
281 CARLA_EXPORT
carla_register_native_plugin_xycontroller()282 void carla_register_native_plugin_xycontroller()
283 {
284     carla_register_native_plugin(&notesDesc);
285 }
286 
287 // -----------------------------------------------------------------------
288