1 #include <rtosc/rtosc.h>
2 #include <rtosc/ports.h>
3 #include <rtosc/default-value.h>
4 #include <rtosc/savefile.h>
5 #include <rtosc/port-sugar.h>
6 
7 #include "common.h"
8 
9 using namespace rtosc;
10 
port_sugar()11 void port_sugar()
12 {
13     const char* presets_str = rPresetsAt(1, 3, 2);
14     assert_str_eq(":default 1", presets_str,
15                   "Port sugar for presets (1)", __LINE__);
16     assert_str_eq("=3", presets_str+11,
17                   "Port sugar for presets (2)", __LINE__);
18     assert_str_eq(":default 2", presets_str+14,
19                   "Port sugar for presets (3)", __LINE__);
20     assert_str_eq("=2", presets_str+25,
21                   "Port sugar for presets (4)", __LINE__);
22 
23     const char* multi_str = rPresetAtMulti(false, 0, 2);
24     assert_str_eq(":default 0", multi_str,
25                   "Port sugar for presets (1)", __LINE__);
26     assert_str_eq("=false", multi_str+11,
27                   "Port sugar for presets (2)", __LINE__);
28     assert_str_eq(":default 2", multi_str+18,
29                   "Port sugar for presets (3)", __LINE__);
30     assert_str_eq("=false", multi_str+29,
31                   "Port sugar for presets (4)", __LINE__);
32 }
33 
34 static const Ports ports = {
35     {"A::i", rDefault(64) rDoc("..."), NULL, NULL },
36     {"B#2::i", rOpt(-2, Master) rOpt(-1, Off)
37         rOptions(Part1, Part2) rDefault(Off), NULL, NULL },
38     {"C::T", rDefault(false), NULL, NULL},
39     {"D::f", rDefault(1.0), NULL, NULL},
40     {"E::i", "", NULL, NULL}
41 };
42 
canonical_values()43 void canonical_values()
44 {
45     const Ports ports = {
46         { "A::ii:cc:SS", rOpt(1, one) rOpt(2, two) rOpt(3, three), NULL, NULL }
47     };
48 
49     rtosc_arg_val_t av[3];
50     av[0].type = 'S';
51     av[0].val.s = "two";
52     av[1].type = 'i';
53     av[1].val.i = 42;
54     av[2].type = 'S';
55     av[2].val.s = "three";
56 
57     const Port* p = ports.apropos("A");
58     const char* port_args = strchr(p->name, ':');
59 
60     assert_int_eq(1, canonicalize_arg_vals(av, 3, port_args, p->meta()),
61                   "One argument was not converted", __LINE__);
62 
63     assert_char_eq(av[0].type, 'i',
64                    "value was converted to correct canonical type", __LINE__);
65     assert_int_eq(av[0].val.i, 2,
66                   "value was converted to correct canonical type", __LINE__);
67     assert_char_eq(av[1].type, 'i',
68                    "value was not converted to canonical since "
69                    "it was already canonical", __LINE__);
70     assert_char_eq(av[2].type, 'S',
71                    "value was not converted to canonical since "
72                    "there was no recipe left in the port args", __LINE__);
73 
74 }
75 
simple_default_values()76 void simple_default_values()
77 {
78     assert_str_eq(get_default_value("A", ports, NULL), "64",
79                   "get integer default value", __LINE__);
80     assert_str_eq(get_default_value("B0", ports, NULL), "Off",
81                   "get integer array default value with options", __LINE__);
82     assert_str_eq(get_default_value("B1", ports, NULL), "Off",
83                   "get integer array default value with options", __LINE__);
84     assert_str_eq(get_default_value("C", ports, NULL), "false",
85                   "get boolean default value", __LINE__);
86     assert_str_eq(get_default_value("D", ports, NULL), "1.0",
87                   "get float default value", __LINE__);
88     assert_null(get_default_value("E", ports, NULL),
89                 "get default value where there's none", __LINE__);
90 }
91 
92 struct Envelope
93 {
94     static const rtosc::Ports& ports;
95 
read_or_storeEnvelope96     bool read_or_store(const char* m, RtData& d, size_t& ref)
97     {
98         if(*rtosc_argument_string(m)) {
99             ref = rtosc_argument(m, 0).i;
100             return true;
101         }
102         else
103         {
104             d.reply(d.loc, "i", (int)ref);
105             return false;
106         }
107     }
108 
109     std::size_t sustain, attack_rate;
110     int scale_type; //!< 0=log, 1=lin
111     size_t env_type;
112     int array[4];
113 
114     // constructor; sets all values to default
EnvelopeEnvelope115     Envelope() : scale_type(1), env_type(0)
116     {
117         update_env_type_dependencies();
118     }
119 
update_env_type_dependenciesEnvelope120     void update_env_type_dependencies()
121     {
122         switch(env_type)
123         {
124             case 0:
125                 sustain = 30;
126                 attack_rate = 40;
127                 memset(array, 0, 4*sizeof(int));
128                 break;
129             case 1:
130                 sustain = 127;
131                 attack_rate = 127;
132                 for(std::size_t i = 0; i < 4; ++i)
133                     array[i] = i;
134                 break;
135         }
136     }
137 };
138 
139 #define rObject Envelope
140 // preset 0: sustain value low (30), attack slow (40)
141 // preset 1: all knobs high
142 static const Ports envelope_ports = {
143     /*
144      * usual parameters
145      */
146     {"sustain::i", rProp(parameter) rDefaultDepends(env_type)
147      rMap(default 0, 30) rMap(default 1, 127), NULL,
__anon630812af0102() 148         [](const char* m, RtData& d) {
149             Envelope* obj = static_cast<Envelope*>(d.obj);
150             obj->read_or_store(m, d, obj->sustain); }},
151     {"attack_rate::i", rProp(parameter) rDefaultDepends(env_type)
152      rMap(default 0, 40) rDefault(127), NULL,
__anon630812af0202() 153         [](const char* m, RtData& d) {
154             Envelope* obj = static_cast<Envelope*>(d.obj);
155             obj->read_or_store(m, d, obj->attack_rate); }},
156     rOption(scale_type, rProp(parameter) rOpt(0, logarithmic), rOpt(1, linear),
157             rDefault(linear), "scale type"),
158     // array: env_type 0: 0 0 0 0
159     //        env_type 1: 0 1 2 3
160     rArrayI(array, 4, rDefaultDepends(env_type),
161             /*rPreset(0, 0),*/
162             rPreset(0, [4x0]),
163             rPreset(1, [0 1 2 3]), // bash the whole array to 0 for preset 1
164             rDefault(3), // all not yet specified values are set to 3
165             "some bundle"),
166     // port without rDefault macros
167     // must not be noted for the savefile
168     {"paste:b", rProp(parameter), NULL,
__anon630812af0302() 169         [](const char* , RtData& ) { assert(false); } },
170     /*
171      * envelope type (~preset)
172      */
173     {"env_type::i", rProp(parameter) rDefault(0), NULL,
__anon630812af0402() 174     [](const char* m, RtData& d) {
175         Envelope* obj = static_cast<Envelope*>(d.obj);
176         if(obj->read_or_store(m, d, obj->env_type)) {
177             obj->update_env_type_dependencies();
178         }}}
179 };
180 #undef rObject
181 
182 const rtosc::Ports& Envelope::ports = envelope_ports;
183 
envelope_types()184 void envelope_types()
185 {
186     // by default, the envelope has preset 0, i.e.
187     // sustain value low (30), attack slow (40)
188     assert_str_eq("30", get_default_value("sustain::i", envelope_ports, NULL),
189                   "get default value without runtime (1)", __LINE__);
190     assert_str_eq("40", get_default_value("attack_rate::i", envelope_ports,
191                                           NULL),
192                   "get default value without runtime (2)", __LINE__);
193 
194     Envelope e1, e2;
195 
196     e1.sustain         =  40; // != envelope 0's default
197     e2.sustain         =   0; // != envelope 1's default
198     e2.attack_rate     = 127; // = envelope 1's default
199     e2.env_type        =   1; // != default
200     e2.scale_type      =   0; // != default
201     for(size_t i = 0; i < 4; ++i)
202         e2.array[i] = 3-i;
203 
204     assert_str_eq("30", get_default_value("sustain::i", envelope_ports, &e1),
205                   "get default value with runtime (1)", __LINE__);
206     assert_str_eq("40", get_default_value("attack_rate::i", envelope_ports,
207                                           &e1),
208                   "get default value with runtime (2)", __LINE__);
209 
210     assert_str_eq("127", get_default_value("sustain::i", envelope_ports, &e2),
211                   "get default value with runtime (3)", __LINE__);
212     assert_str_eq("127",
213                   get_default_value("attack_rate::i", envelope_ports, &e2 ),
214                   "get default value with runtime (4)", __LINE__);
215 
216     assert_str_eq("/sustain 40",
217                   get_changed_values(envelope_ports, &e1).c_str(),
218                   "get changed values where none are changed", __LINE__);
219     const char* changed_e2 = "/sustain 0\n/scale_type logarithmic\n"
220                              "/array [3 2 1 0]\n/env_type 1";
221     assert_str_eq(changed_e2,
222                   get_changed_values(envelope_ports, &e2).c_str(),
223                   "get changed values where three are changed", __LINE__);
224 
225     // restore values to envelope from a savefile
226     // this is indirectly related to default values
227     Envelope e3;
228     // note: setting the envelope type does overwrite all other values
229     //       this means that "/sustain 60" has to be executed after
230     //       "/env_type 1", even though the order in the savefile is different
231     //       dispatch_printed_messages() executes these messages in the
232     //       correct order.
233     int num = dispatch_printed_messages("%this is a savefile\n"
234                                         "/sustain 60 /env_type 1\n"
235                                         "/attack_rate 10 % a useless comment\n"
236                                         "/scale_type 0",
237                                         envelope_ports, &e3);
238     assert_int_eq(4, num,
239                   "restore values from savefile - correct number", __LINE__);
240     assert_int_eq(60, e3.sustain, "restore values from savefile (1)", __LINE__);
241     assert_int_eq(1, e3.env_type, "restore values from savefile (2)", __LINE__);
242     assert_int_eq(10, e3.attack_rate,
243                   "restore values from savefile (3)", __LINE__);
244     assert_int_eq(0, e3.scale_type,
245                   "restore values from savefile (4)", __LINE__);
246 
247     num = dispatch_printed_messages("/sustain 60 /env_type $1",
248                                     envelope_ports, &e3);
249     assert_int_eq(-13, num,
250                   "restore values from a corrupt savefile (3)", __LINE__);
251 
252     std::string appname = "default-values-test",
253                 appver_str = "0.0.1";
254     rtosc_version appver = rtosc_version { 0, 0, 1 };
255     std::string savefile = save_to_file(envelope_ports, &e2,
256                                         appname.c_str(),
257                                         appver);
258     char cur_rtosc_buf[12];
259     rtosc_version cur_rtosc = rtosc_current_version();
260     rtosc_version_print_to_12byte_str(&cur_rtosc, cur_rtosc_buf);
261     std::string exp_savefile = "% RT OSC v";
262                 exp_savefile += cur_rtosc_buf;
263                 exp_savefile += " savefile\n"
264                                "% " + appname + " v" + appver_str + "\n";
265     exp_savefile += changed_e2;
266 
267     assert_str_eq(exp_savefile.c_str(), savefile.c_str(),
268                   "save testfile", __LINE__);
269 
270     Envelope e2_restored; // e2 will be loaded from the savefile
271 
272     int rval = load_from_file(exp_savefile.c_str(),
273                               envelope_ports, &e2_restored,
274                               appname.c_str(), appver);
275 
276     assert_int_eq(4, rval,
277                   "load savefile from file, 4 messages read", __LINE__);
278 
279     auto check_restored = [](int i1, int i2, const char* member) {
280         std::string tcs = "restore "; tcs += member; tcs += " from file";
281         assert_int_eq(i1, i2, tcs.c_str(), __LINE__);
282     };
283 
284     check_restored(e2.sustain, e2_restored.sustain, "sustain value");
285     check_restored(e2.attack_rate, e2_restored.attack_rate, "attack rate");
286     check_restored(e2.scale_type, e2_restored.scale_type, "scale type");
287     check_restored(e2.array[0], e2_restored.array[0], "array[0]");
288     check_restored(e2.array[1], e2_restored.array[1], "array[1]");
289     check_restored(e2.array[2], e2_restored.array[2], "array[2]");
290     check_restored(e2.array[3], e2_restored.array[3], "array[3]");
291     check_restored(e2.env_type, e2_restored.env_type, "envelope type");
292 }
293 
presets()294 void presets()
295 {
296     // for presets, it would be exactly the same,
297     // with only the env_type port being named as "preset" port.
298 }
299 
300 struct SavefileTest
301 {
302     static const rtosc::Ports& ports;
303 
304     int new_param;
305     bool very_old_version;
306     int further_param;
307 };
308 
309 #define rObject SavefileTest
310 static const Ports savefile_test_ports = {
311     {"new_param:i", "", NULL,
__anon630812af0602() 312         [](const char* m, RtData& d) {
313             SavefileTest* obj = static_cast<SavefileTest*>(d.obj);
314             obj->new_param = rtosc_argument(m, 0).i; }},
315     {"very_old_version:T:F", "", NULL,
__anon630812af0702() 316         [](const char* m, RtData& d) {
317             SavefileTest* obj = static_cast<SavefileTest*>(d.obj);
318             obj->very_old_version = rtosc_argument(m, 0).T; }},
319     {"further_param:i", "", NULL,
__anon630812af0802() 320         [](const char* m, RtData& d) {
321             SavefileTest* obj = static_cast<SavefileTest*>(d.obj);
322             obj->further_param = rtosc_argument(m, 0).i; }}
323 };
324 #undef rObject
325 
326 const rtosc::Ports& SavefileTest::ports = savefile_test_ports;
327 
savefiles()328 void savefiles()
329 {
330     int rval = load_from_file("% NOT AN RT OSC v0.0.1 savefile\n"
331                               // => error: it should be "RT OSC"
332                               "% my_application v0.0.1",
333                               savefile_test_ports, NULL,
334                               "my_application", rtosc_version {1, 2, 3});
335     assert_int_eq(-1, rval,
336                   "reject file that has an invalid first line", __LINE__);
337 
338     rval = load_from_file("% RT OSC v0.0.1 savefile\n"
339                           "% not_my_application v0.0.1",
340                           // => error: application name mismatch
341                           savefile_test_ports, NULL,
342                           "my_application", rtosc_version {1, 2, 3});
343     assert_int_eq(-25, rval,
344                   "reject file from another application", __LINE__);
345 
346     struct my_dispatcher_t : public rtosc::savefile_dispatcher_t
347     {
348 
349         int modify(char* portname,
350                    size_t maxargs, size_t nargs, rtosc_arg_val_t* args,
351                    bool round2)
352         {
353             int newsize = nargs;
354 
355             if(round2)
356             {
357                 if(rtosc_version_cmp(rtosc_filever,
358                                      rtosc_version{0, 0, 4}) < 0)
359                    /* version < 0.0.4 ? */
360                 {
361                     if(rtosc_version_cmp(rtosc_filever,
362                                          rtosc_version{0, 0, 3}) < 0)
363                     {
364                         if(rtosc_version_cmp(rtosc_filever,
365                                              rtosc_version{0, 0, 2}) < 0)
366                         {
367                             {
368                                 // dispatch an additional message
369                                 char buffer[32];
370                                 rtosc_message(buffer, 32,
371                                               "/very_old_version", "T");
372                                 operator()(buffer);
373                             }
374 
375                             if(nargs == 0 && maxargs >= 1)
376                             {
377                                 memcpy(portname+1, "new", 3);
378                                 args[0].val.i = 42;
379                                 args[0].type = 'i';
380                                 newsize = 1;
381                             }
382                             else // wrong argument count or not enough space
383                                 newsize = abort;
384                         }
385                         else // i.e. version = 0.0.2
386                             newsize = discard;
387                     }
388                     else // i.e. version = 0.0.3
389                         newsize = abort;
390                 }
391                 /* for versions >= 0.0.4, we just let "/old_param" pass */
392                 /* in order to get a dispatch error */
393             }
394             else {
395                 // discard "/old_param" in round 1, since nothing depends on it
396                 newsize = discard;
397             }
398 
399             return newsize;
400         }
401 
402         int on_dispatch(size_t, char* portname,
403                         size_t maxargs, size_t nargs, rtosc_arg_val_t* args,
404                         bool round2, dependency_t dependency)
405         {
406             return (portname[1] == 'o' && !strcmp(portname, "/old_param"))
407                 ? modify(portname, maxargs, nargs, args, round2)
408                 : default_response(nargs, round2, dependency);
409         }
410     };
411 
412     my_dispatcher_t my_dispatcher;
413     SavefileTest sft;
414 
415     auto reset_savefile = [](SavefileTest& sft) {
416         sft.new_param = 0; sft.very_old_version = false;
417         sft.further_param = 0;
418     };
419 #define MAKE_TESTFILE(ver) "% RT OSC " ver " savefile\n" \
420                            "% savefiletest v1.2.3\n" \
421                            "/old_param\n" \
422                            "/further_param 123"
423 
424     reset_savefile(sft);
425     rval = load_from_file(MAKE_TESTFILE("v0.0.1"),
426                           savefile_test_ports, &sft,
427                           "savefiletest", rtosc_version {1, 2, 3},
428                           &my_dispatcher);
429     assert_int_eq(2, rval, "savefile: 2 messages read for v0.0.1", __LINE__);
430     assert_int_eq(42, sft.new_param, "port renaming works", __LINE__);
431     assert_true(sft.very_old_version,
432                 "additional messages work", __LINE__);
433     assert_int_eq(123, sft.further_param,
434                   "further parameter is being dispatched for v0.0.1", __LINE__);
435 
436     reset_savefile(sft);
437     rval = load_from_file(MAKE_TESTFILE("v0.0.2"),
438                           savefile_test_ports, &sft,
439                           "savefiletest", rtosc_version {1, 2, 3},
440                           &my_dispatcher);
441     assert_int_eq(2, rval, "savefile: 2 messages read for v0.0.2", __LINE__);
442     assert_int_eq(0, sft.new_param, "message discarding works", __LINE__);
443     assert_int_eq(123, sft.further_param,
444                   "further parameter is being dispatched for v0.0.2", __LINE__);
445 
446     reset_savefile(sft);
447     // test with v0.0.3
448     // abort explicitly (see savefile dispatcher implementation)
449     rval = load_from_file(MAKE_TESTFILE("v0.0.3"),
450                           savefile_test_ports, &sft,
451                           "savefiletest", rtosc_version {1, 2, 3},
452                           &my_dispatcher);
453     assert_int_eq(-59, rval, "savefile: 1 error for v0.0.3", __LINE__);
454     assert_int_eq(123, sft.further_param,
455                   "no further parameter is being dispatched for v0.0.3",
456                   __LINE__);
457     // test with v0.0.4
458     // abort implicitly (the port is unknown)
459     rval = load_from_file(MAKE_TESTFILE("v0.0.4"),
460                           savefile_test_ports, &sft,
461                           "savefiletest", rtosc_version {1, 2, 3},
462                           &my_dispatcher);
463     assert_int_eq(-59, rval, "savefile: 1 error for v0.0.4", __LINE__);
464     assert_int_eq(123, sft.further_param,
465                   "no further parameter is being dispatched for v0.0.4",
466                   __LINE__);
467 
468 #undef MAKE_TESTFILE
469 }
470 
main()471 int main()
472 {
473     port_sugar();
474 
475     canonical_values();
476     simple_default_values();
477     envelope_types();
478     presets();
479     savefiles();
480 
481     return test_summary();
482 }
483