1 /*
2     CliFuncs.h
3 
4     Copyright 2019, Will Godfrey.
5 
6     Copyright 2021, Rainer Hans Liffers
7 
8     This file is part of yoshimi, which is free software: you can
9     redistribute it and/or modify it under the terms of the GNU General
10     Public License as published by the Free Software Foundation, either
11     version 2 of the License, or (at your option) any later version.
12 
13     yoshimi is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17 
18     You should have received a copy of the GNU General Public License
19     along with yoshimi.  If not, see <http://www.gnu.org/licenses/>.
20 
21 */
22 
23 #ifndef CLIFUNCS_H
24 #define CLIFUNCS_H
25 
26 #include <cmath>
27 #include <string>
28 #include <cstring>
29 
30 #include <readline/readline.h>
31 #include <cassert>
32 
33 #include "CLI/Parser.h"
34 #include "Interface/TextLists.h"
35 #include "Misc/SynthEngine.h"
36 #include "Misc/TextMsgBuffer.h"
37 #include "Misc/NumericFuncs.h"
38 #include "Misc/FormatFuncs.h"
39 
40 
41 namespace cli {
42 
43 using func::bitTest;
44 using func::bitFindHigh;
45 
46 using func::asString;
47 
48 using std::string;
49 
50 
contextToEngines(int context)51 inline int contextToEngines(int context)
52 {
53     int engine = UNUSED;
54     if (bitTest(context, LEVEL::SubSynth))
55         engine = PART::engine::subSynth;
56     else if (bitTest(context, LEVEL::PadSynth))
57         engine = PART::engine::padSynth;
58     else if (bitTest(context, LEVEL::AddMod))
59         engine = PART::engine::addMod1;
60     else if (bitTest(context, LEVEL::AddVoice))
61         engine = PART::engine::addVoice1;
62     else if (bitTest(context, LEVEL::AddSynth))
63         engine = PART::engine::addSynth;
64     return engine;
65 }
66 
67 
68 inline float readControl(SynthEngine *synth,
69                          unsigned char action, unsigned char control, unsigned char part,
70                          unsigned char kit = UNUSED,
71                          unsigned char engine = UNUSED,
72                          unsigned char insert = UNUSED,
73                          unsigned char parameter = UNUSED,
74                          unsigned char offset = UNUSED,
75                          unsigned char miscmsg = NO_MSG)
76 {
77     float value;
78     CommandBlock putData;
79 
80     putData.data.value = 0;
81     putData.data.type = 0;
82     putData.data.source = action;
83     putData.data.control = control;
84     putData.data.part = part;
85     putData.data.kit = kit;
86     putData.data.engine = engine;
87     putData.data.insert = insert;
88     putData.data.parameter = parameter;
89     putData.data.offset = offset;
90     putData.data.miscmsg = miscmsg;
91     value = synth->interchange.readAllData(&putData);
92     //if (putData.data.type & TOPLEVEL::type::Error)
93         //return 0xfffff;
94         //std::cout << "err" << std::endl;
95     return value;
96 }
97 
98 
99 inline string readControlText(SynthEngine *synth,
100                               unsigned char action, unsigned char control, unsigned char part,
101                               unsigned char kit = UNUSED,
102                               unsigned char engine = UNUSED,
103                               unsigned char insert = UNUSED,
104                               unsigned char parameter = UNUSED,
105                               unsigned char offset = UNUSED)
106 {
107     float value;
108     CommandBlock putData;
109 
110     putData.data.value = 0;
111     putData.data.type = 0;
112     putData.data.source = action;
113     putData.data.control = control;
114     putData.data.part = part;
115     putData.data.kit = kit;
116     putData.data.engine = engine;
117     putData.data.insert = insert;
118     putData.data.parameter = parameter;
119     putData.data.offset = offset;
120     putData.data.miscmsg = UNUSED;
121     value = synth->interchange.readAllData(&putData);
122     return TextMsgBuffer::instance().fetch(value);
123 }
124 
125 
readLimits(SynthEngine * synth,float value,unsigned char type,unsigned char control,unsigned char part,unsigned char kit,unsigned char engine,unsigned char insert,unsigned char parameter,unsigned char miscmsg)126 inline void readLimits(SynthEngine *synth,
127                        float value, unsigned char type, unsigned char control, unsigned char part,
128                        unsigned char kit, unsigned char engine, unsigned char insert,
129                        unsigned char parameter, unsigned char miscmsg)
130 {
131     CommandBlock putData;
132 
133     putData.data.value = value;
134     putData.data.type = type;
135     putData.data.control = control;
136     putData.data.part = part;
137     putData.data.kit = kit;
138     putData.data.engine = engine;
139     putData.data.insert = insert;
140     putData.data.parameter = parameter;
141     putData.data.miscmsg = miscmsg;
142 
143     value = synth->interchange.readAllData(&putData);
144     string name;
145     switch (type & 3)
146     {
147         case TOPLEVEL::type::Minimum:
148             name = "Min ";
149             break;
150         case TOPLEVEL::type::Maximum:
151             name = "Max ";
152             break;
153         default:
154             name = "Default ";
155             break;
156     }
157     type = putData.data.type;
158     if ((type & TOPLEVEL::type::Integer) == 0)
159         name += std::to_string(value);
160     else if (value < 0)
161         name += std::to_string(int(value - 0.5f));
162     else
163         name += std::to_string(int(value + 0.5f));
164     if (type & TOPLEVEL::type::Error)
165         name += " - error";
166     else if (type & TOPLEVEL::type::Learnable)
167         name += " - learnable";
168     synth->getRuntime().Log(name);
169 }
170 
171 
172 inline int sendNormal(SynthEngine *synth,
173                       unsigned char action, float value, unsigned char type, unsigned char control, unsigned char part,
174                       unsigned char kit = UNUSED,
175                       unsigned char engine = UNUSED,
176                       unsigned char insert = UNUSED,
177                       unsigned char parameter = UNUSED,
178                       unsigned char offset = UNUSED,
179                       unsigned char miscmsg = NO_MSG)
180 {
181     if ((type & TOPLEVEL::type::Limits) && part != TOPLEVEL::section::midiLearn)
182     {
183         readLimits(synth, value, type, control, part, kit, engine, insert, parameter, miscmsg);
184         return REPLY::done_msg;
185     }
186     action |= TOPLEVEL::action::fromCLI;
187 
188     CommandBlock putData;
189 
190     putData.data.value = value;
191     putData.data.type = type;
192     putData.data.control = control;
193     putData.data.part = part;
194     putData.data.kit = kit;
195     putData.data.engine = engine;
196     putData.data.insert = insert;
197     putData.data.parameter = parameter;
198     putData.data.offset = offset;
199     putData.data.miscmsg = miscmsg;
200 
201     /*
202      * MIDI learn settings are synced by the audio thread
203      * but not passed on to any of the normal controls.
204      * The type field is used for a different purpose.
205      */
206 
207     if (part != TOPLEVEL::section::midiLearn)
208     {
209         putData.data.type |= TOPLEVEL::type::Limits;
210         float newValue = synth->interchange.readAllData(&putData);
211         if (type & TOPLEVEL::type::LearnRequest)
212         {
213             if ((putData.data.type & TOPLEVEL::type::Learnable) == 0)
214             {
215             synth->getRuntime().Log("Can't learn this control");
216             return REPLY::failed_msg;
217             }
218         }
219         else
220         {
221             if (putData.data.type & TOPLEVEL::type::Error)
222                 return REPLY::available_msg;
223             if (newValue != value && (type & TOPLEVEL::type::Write))
224             { // checking the original type not the reported one
225                 putData.data.value = newValue;
226                 synth->getRuntime().Log("Range adjusted");
227             }
228         }
229         action |= TOPLEVEL::action::fromCLI;
230     }
231     putData.data.source = action;
232     putData.data.type = type;
233     if (synth->interchange.fromCLI.write(putData.bytes))
234     {
235         synth->getRuntime().finishedCLI = false;
236     }
237     else
238     {
239         synth->getRuntime().Log("Unable to write to fromCLI buffer");
240         return REPLY::failed_msg;
241     }
242     return REPLY::done_msg;
243 }
244 
245 
246 inline int sendDirect(SynthEngine *synth,
247                       unsigned char action, float value, unsigned char type, unsigned char control, unsigned char part,
248                       unsigned char kit = UNUSED,
249                       unsigned char engine = UNUSED,
250                       unsigned char insert = UNUSED,
251                       unsigned char parameter = UNUSED,
252                       unsigned char offset = UNUSED,
253                       unsigned char miscmsg = NO_MSG,
254                       unsigned char request = UNUSED)
255 {
256     if (action == TOPLEVEL::action::fromMIDI && part != TOPLEVEL::section::midiLearn)
257         request = type & TOPLEVEL::type::Default;
258     CommandBlock putData;
259 
260     putData.data.value = value;
261     putData.data.control = control;
262     putData.data.part = part;
263     putData.data.kit = kit;
264     putData.data.engine = engine;
265     putData.data.insert = insert;
266     putData.data.parameter = parameter;
267     putData.data.offset = offset;
268     putData.data.miscmsg = miscmsg;
269 
270     if (type == TOPLEVEL::type::Default)
271     {
272         putData.data.type = TOPLEVEL::type::Limits;
273         synth->interchange.readAllData(&putData);
274         if ((putData.data.type & TOPLEVEL::type::Learnable) == 0)
275         {
276             synth->getRuntime().Log("Can't learn this control");
277             return 0;
278         }
279     }
280 
281     if (part != TOPLEVEL::section::midiLearn)
282         action |= TOPLEVEL::action::fromCLI;
283     /*
284      * MIDI learn is synced by the audio thread but
285      * not passed on to any of the normal controls.
286      * The type field is used for a different purpose.
287      */
288     putData.data.source = action | TOPLEVEL::action::fromCLI;
289     putData.data.type = type;
290     if (request < TOPLEVEL::type::Limits)
291     {
292         putData.data.type = request | TOPLEVEL::type::Limits;
293         value = synth->interchange.readAllData(&putData);
294         string name;
295         switch (request)
296         {
297             case TOPLEVEL::type::Minimum:
298                 name = "Min ";
299                 break;
300             case TOPLEVEL::type::Maximum:
301                 name = "Max ";
302                 break;
303             default:
304                 name = "Default ";
305                 break;
306         }
307         type = putData.data.type;
308         if ((type & TOPLEVEL::type::Integer) == 0)
309             name += std::to_string(value);
310         else if (value < 0)
311             name += std::to_string(int(value - 0.5f));
312         else
313             name += std::to_string(int(value + 0.5f));
314         if (type & TOPLEVEL::type::Error)
315             name += " - error";
316         else if (type & TOPLEVEL::type::Learnable)
317             name += " - learnable";
318         synth->getRuntime().Log(name);
319         return 0;
320     }
321 
322     if (part == TOPLEVEL::section::main && (type & TOPLEVEL::type::Write) == 0 && control >= MAIN::control::readPartPeak && control <= MAIN::control::readMainLRrms)
323     {
324         string name;
325         switch (control)
326         {
327             case MAIN::control::readPartPeak:
328                 name = "part " + std::to_string(int(kit));
329                 if (engine == 0)
330                     name += "L ";
331                 else
332                     name += "R ";
333                 name += "peak ";
334                 break;
335             case MAIN::control::readMainLRpeak:
336                 name = "main ";
337                 if (kit == 0)
338                     name += "L ";
339                 else
340                     name += "R ";
341                 name += "peak ";
342                 break;
343             case MAIN::control::readMainLRrms:
344                 name = "main ";
345                 if (kit == 0)
346                     name += "L ";
347                 else
348                     name += "R ";
349                 name += "RMS ";
350                 break;
351             }
352             value = synth->interchange.readAllData(&putData);
353             synth->getRuntime().Log(name + std::to_string(value));
354         return 0;
355     }
356 
357     if (part == TOPLEVEL::section::config && putData.data.miscmsg != UNUSED && (control == CONFIG::control::bankRootCC || control == CONFIG::control::bankCC || control == CONFIG::control::extendedProgramChangeCC))
358     {
359         synth->getRuntime().Log("In use by " + TextMsgBuffer::instance().fetch(putData.data.miscmsg) );
360         return 0;
361     }
362 
363     if (parameter != UNUSED && (parameter & TOPLEVEL::action::lowPrio))
364         action |= (parameter & TOPLEVEL::action::muteAndLoop); // transfer low prio and loopback
365     putData.data.source = action;
366 
367     if (synth->interchange.fromCLI.write(putData.bytes))
368     {
369         synth->getRuntime().finishedCLI = false;
370     }
371     else
372         synth->getRuntime().Log("Unable to write to fromCLI buffer");
373     return 0; // no function for this yet
374 }
375 
376 
377 }//(End)namespace cli
378 #endif /*CLIFUNCS_H*/
379