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