1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html
2
3 #include <glib.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <stdlib.h>
7 #include <assert.h>
8
9 #include "config.h"
10 #include <smwavset.hh>
11 #include <smaudio.hh>
12 #include <smmain.hh>
13 #include <smmicroconf.hh>
14 #include "smjobqueue.hh"
15 #include "smutils.hh"
16 #include "smwavdata.hh"
17 #include "sminstrument.hh"
18 #include "smwavsetbuilder.hh"
19
20 #include <string>
21 #include <map>
22
23 using std::string;
24 using std::vector;
25 using std::map;
26
27 using namespace SpectMorph;
28
29 static vector<string>
string_tokenize(const string & str)30 string_tokenize (const string& str)
31 {
32 vector<string> words;
33
34 string::const_iterator word_start = str.begin();
35 for (string::const_iterator si = str.begin(); si != str.end(); si++)
36 {
37 if (*si == ',') /* colon indicates word boundary */
38 {
39 words.push_back (string (word_start, si));
40 word_start = si + 1;
41 }
42 }
43
44 if (!str.empty()) /* handle last word in string */
45 words.push_back (string (word_start, str.end()));
46
47 return words;
48 }
49
50 /// @cond
51 struct Options
52 {
53 string program_name; /* FIXME: what to do with that */
54 string data_dir;
55 string args;
56 string smenc;
57 int channel;
58 int min_velocity;
59 int max_velocity;
60 vector<string> format;
61 int max_jobs;
62 bool loop_markers = false;
63 bool loop_markers_ms = false;
64 enum { NONE, INIT, ADD, LIST, ENCODE, DECODE, DELTA, LINK, EXTRACT, GET_MARKERS, SET_MARKERS, SET_NAMES, GET_NAMES, BUILD } command;
65
66 Options ();
67 void parse (int *argc_p, char **argv_p[]);
68 static void print_usage ();
69 } options;
70 /// @endcond
71
Options()72 Options::Options ()
73 {
74 program_name = "smwavset";
75 smenc = "smenc";
76 data_dir = "/tmp";
77 args = "";
78 command = NONE;
79 channel = 0;
80 min_velocity = 0;
81 max_velocity = 127;
82 format = string_tokenize ("midi-note,filename");
83 max_jobs = 1;
84 loop_markers = false;
85 }
86
87 #include "stwutils.hh"
88
89 void
parse(int * argc_p,char ** argv_p[])90 Options::parse (int *argc_p,
91 char **argv_p[])
92 {
93 guint argc = *argc_p;
94 gchar **argv = *argv_p;
95 unsigned int i, e;
96
97 /* I am tired of seeing .libs/lt-gst123 all the time,
98 * but basically this should be done (to allow renaming the binary):
99 *
100 if (argc && argv[0])
101 program_name = argv[0];
102 */
103
104 for (i = 1; i < argc; i++)
105 {
106 const char *opt_arg;
107 if (strcmp (argv[i], "--help") == 0 ||
108 strcmp (argv[i], "-h") == 0)
109 {
110 print_usage();
111 exit (0);
112 }
113 else if (strcmp (argv[i], "--version") == 0 || strcmp (argv[i], "-v") == 0)
114 {
115 sm_printf ("%s %s\n", program_name.c_str(), VERSION);
116 exit (0);
117 }
118 else if (check_arg (argc, argv, &i, "--args", &opt_arg))
119 {
120 args = opt_arg;
121 }
122 else if (check_arg (argc, argv, &i, "--smenc", &opt_arg))
123 {
124 smenc = opt_arg;
125 }
126 else if (check_arg (argc, argv, &i, "-d", &opt_arg) ||
127 check_arg (argc, argv, &i, "--data-dir", &opt_arg))
128 {
129 data_dir = opt_arg;
130 }
131 else if (check_arg (argc, argv, &i, "-c", &opt_arg) ||
132 check_arg (argc, argv, &i, "--channel", &opt_arg))
133 {
134 channel = atoi (opt_arg);
135 }
136 else if (check_arg (argc, argv, &i, "--min-velocity", &opt_arg))
137 {
138 min_velocity = atoi (opt_arg);
139 }
140 else if (check_arg (argc, argv, &i, "--max-velocity", &opt_arg))
141 {
142 max_velocity = atoi (opt_arg);
143 }
144 else if (check_arg (argc, argv, &i, "--format", &opt_arg))
145 {
146 format = string_tokenize (opt_arg);
147 }
148 else if (check_arg (argc, argv, &i, "-j", &opt_arg))
149 {
150 max_jobs = atoi (opt_arg);
151 }
152 else if (check_arg (argc, argv, &i, "--loop"))
153 {
154 loop_markers = true;
155 }
156 else if (check_arg (argc, argv, &i, "--loop-ms"))
157 {
158 loop_markers_ms = true;
159 }
160 }
161
162 bool resort_required = true;
163
164 while (resort_required)
165 {
166 /* resort argc/argv */
167 e = 1;
168 for (i = 1; i < argc; i++)
169 if (argv[i])
170 {
171 argv[e++] = argv[i];
172 if (i >= e)
173 argv[i] = NULL;
174 }
175 *argc_p = e;
176 resort_required = false;
177
178 // parse command
179 if (*argc_p >= 2 && command == NONE)
180 {
181 if (strcmp (argv[1], "init") == 0)
182 {
183 command = INIT;
184 }
185 else if (strcmp (argv[1], "add") == 0)
186 {
187 command = ADD;
188 }
189 else if (strcmp (argv[1], "list") == 0)
190 {
191 command = LIST;
192 }
193 else if (strcmp (argv[1], "encode") == 0)
194 {
195 command = ENCODE;
196 }
197 else if (strcmp (argv[1], "decode") == 0)
198 {
199 command = DECODE;
200 }
201 else if (strcmp (argv[1], "delta") == 0)
202 {
203 command = DELTA;
204 }
205 else if (strcmp (argv[1], "link") == 0)
206 {
207 command = LINK;
208 }
209 else if (strcmp (argv[1], "extract") == 0)
210 {
211 command = EXTRACT;
212 }
213 else if (strcmp (argv[1], "get-markers") == 0)
214 {
215 command = GET_MARKERS;
216 }
217 else if (strcmp (argv[1], "set-markers") == 0)
218 {
219 command = SET_MARKERS;
220 }
221 else if (strcmp (argv[1], "set-names") == 0)
222 {
223 command = SET_NAMES;
224 }
225 else if (strcmp (argv[1], "get-names") == 0)
226 {
227 command = GET_NAMES;
228 }
229 else if (strcmp (argv[1], "build") == 0)
230 {
231 command = BUILD;
232 }
233
234 if (command != NONE)
235 {
236 argv[1] = NULL;
237 resort_required = true;
238 }
239 }
240 }
241 }
242
243 void
print_usage()244 Options::print_usage ()
245 {
246 sm_printf ("usage: %s <command> [ <options> ] [ <command specific args...> ]\n", options.program_name.c_str());
247 sm_printf ("\n");
248 sm_printf ("command specific args:\n");
249 sm_printf ("\n");
250 sm_printf (" smwavset init [ <options> ] <wset_filename>...\n");
251 sm_printf (" smwavset add [ <options> ] <wset_filename> <midi_note> <path>\n");
252 sm_printf (" smwavset list [ <options> ] <wset_filename>\n");
253 sm_printf (" smwavset encode [ <options> ] <wset_filename> <smset_filename>\n");
254 sm_printf (" smwavset decode [ <options> ] <smset_filename> <wset_filename>\n");
255 sm_printf (" smwavset delta [ <options> ] <wset_filename1>...<wset_filenameN>\n");
256 sm_printf (" smwavset link [ <options> ] <wset_filename>\n");
257 sm_printf (" smwavset get-markers [ <options> ] <wset_filename>\n");
258 sm_printf (" smwavset set-markers [ <options> ] <wset_filename> <marker_filename>\n");
259 sm_printf (" smwavset build <inst_filename> <smset_filename>\n");
260 sm_printf ("\n");
261 sm_printf ("options:\n");
262 sm_printf (" -h, --help help for %s\n", options.program_name.c_str());
263 sm_printf (" -v, --version print version\n");
264 sm_printf (" --args \"<args>\" arguments for decoder or encoder\n");
265 sm_printf (" -d, --data-dir <dir> set data directory for newly created .sm or .wav files\n");
266 sm_printf (" -c, --channel <ch> set channel for added .sm file\n");
267 sm_printf (" --format <f1>,...,<fN> set fields to display in list\n");
268 sm_printf (" -j <jobs> run <jobs> commands simultaneously (use multiple cpus for encoding)\n");
269 sm_printf (" --smenc <cmd> use <cmd> as smenc command\n");
270 sm_printf (" --loop also extract loop markers (for smwavset get-markers)\n");
271 sm_printf ("\n");
272 }
273
274 string
int2str(int i)275 int2str (int i)
276 {
277 char buffer[512];
278 sprintf (buffer, "%d", i);
279 return buffer;
280 }
281
282 vector<WavSetWave>::iterator
find_wave(WavSet & wset,int midi_note)283 find_wave (WavSet& wset, int midi_note)
284 {
285 vector<WavSetWave>::iterator wi = wset.waves.begin();
286
287 while (wi != wset.waves.end())
288 {
289 if (wi->midi_note == midi_note)
290 return wi;
291 wi++;
292 }
293
294 return wi;
295 }
296
297 string
time2str(double t)298 time2str (double t)
299 {
300 long long ms = t * 1000;
301 long long s = ms / 1000;
302 long long m = s / 60;
303 long long h = m / 60;
304
305 char buf[1024];
306 sprintf (buf, "%02lld:%02lld:%02lld.%03lld", h, m % 60, s % 60, ms % 1000);
307 return buf;
308 }
309
310 bool
load_wav_file(const string & filename,vector<float> & data_out)311 load_wav_file (const string& filename, vector<float>& data_out)
312 {
313 /* open input */
314 WavData wav_data;
315 if (!wav_data.load_mono (filename))
316 {
317 fprintf (stderr, "%s: can't open the input file %s: %s\n", options.program_name.c_str(), filename.c_str(), wav_data.error_blurb());
318 return false;
319 }
320 data_out = wav_data.samples();
321 return true;
322 }
323
324 double
delta(vector<float> & d0,vector<float> & d1)325 delta (vector<float>& d0, vector<float>& d1)
326 {
327 double error = 0;
328 for (size_t t = 0; t < MAX (d0.size(), d1.size()); t++)
329 {
330 double a0 = 0, a1 = 0;
331 if (t < d0.size())
332 a0 = d0[t];
333 if (t < d1.size())
334 a1 = d1[t];
335 error += (a0 - a1) * (a0 - a1);
336 }
337 return error / MAX (d0.size(), d1.size());
338 }
339
340 void
load_or_die(WavSet & wset,string name)341 load_or_die (WavSet& wset, string name)
342 {
343 Error error = wset.load (name);
344 if (error)
345 {
346 fprintf (stderr, "%s: can't open input file: %s: %s\n", options.program_name.c_str(), name.c_str(), error.message());
347 exit (1);
348 }
349 }
350
351 int
main(int argc,char ** argv)352 main (int argc, char **argv)
353 {
354 double start_time = get_time();
355
356 Main main (&argc, &argv);
357
358 options.parse (&argc, &argv);
359
360 if (options.command == Options::INIT)
361 {
362 for (int i = 1; i < argc; i++)
363 {
364 WavSet wset;
365 wset.save (argv[i]);
366 }
367 }
368 else if (options.command == Options::ADD)
369 {
370 assert (argc == 4);
371
372 WavSet wset;
373 load_or_die (wset, argv[1]);
374
375 WavSetWave new_wave;
376 new_wave.midi_note = atoi (argv[2]);
377 new_wave.path = argv[3];
378 new_wave.channel = options.channel;
379 new_wave.velocity_range_min = options.min_velocity;
380 new_wave.velocity_range_max = options.max_velocity;
381
382 wset.waves.push_back (new_wave);
383 wset.save (argv[1]);
384 }
385 else if (options.command == Options::LIST)
386 {
387 WavSet wset;
388 load_or_die (wset, argv[1]);
389
390 for (vector<WavSetWave>::iterator wi = wset.waves.begin(); wi != wset.waves.end(); wi++)
391 {
392 for (vector<string>::const_iterator fi = options.format.begin(); fi != options.format.end(); fi++)
393 {
394 if (fi != options.format.begin()) // not first field
395 sm_printf (" ");
396 if (*fi == "midi-note")
397 sm_printf ("%d", wi->midi_note);
398 else if (*fi == "filename")
399 sm_printf ("%s", wi->path.c_str());
400 else if (*fi == "channel")
401 sm_printf ("%d", wi->channel);
402 else if (*fi == "min-velocity")
403 sm_printf ("%d", wi->velocity_range_min);
404 else if (*fi == "max-velocity")
405 sm_printf ("%d", wi->velocity_range_max);
406 else
407 {
408 fprintf (stderr, "list command: invalid field for format: %s\n", fi->c_str());
409 exit (1);
410 }
411 }
412 sm_printf ("\n");
413 }
414 }
415 else if (options.command == Options::SET_NAMES)
416 {
417 assert (argc == 4);
418
419 WavSet wset;
420 load_or_die (wset, argv[1]);
421 wset.name = argv[2];
422 wset.short_name = argv[3];
423 wset.save (argv[1]);
424 }
425 else if (options.command == Options::GET_NAMES)
426 {
427 assert (argc == 2);
428
429 WavSet wset;
430 load_or_die (wset, argv[1]);
431 sm_printf ("name \"%s\"\n", wset.name.c_str());
432 sm_printf ("short_name \"%s\"\n", wset.short_name.c_str());
433 }
434 else if (options.command == Options::ENCODE)
435 {
436 assert (argc == 3);
437
438 WavSet wset, smset;
439 load_or_die (wset, argv[1]);
440
441 JobQueue job_queue (options.max_jobs);
442
443 for (vector<WavSetWave>::iterator wi = wset.waves.begin(); wi != wset.waves.end(); wi++)
444 {
445 string smpath = options.data_dir + "/" + int2str (wi->midi_note) + ".sm";
446 string cmd = options.smenc + " -m " + int2str (wi->midi_note) + " \"" + wi->path.c_str() + "\" " + smpath + " " + options.args;
447 sm_printf ("[%s] ## %s\n", time2str (get_time() - start_time).c_str(), cmd.c_str());
448 job_queue.run (cmd);
449
450 WavSetWave new_wave = *wi;
451 new_wave.path = smpath;
452 smset.waves.push_back (new_wave);
453 }
454 if (!job_queue.wait_for_all())
455 {
456 g_printerr ("smwavset: encoding commands did not complete successfully\n");
457 exit (1);
458 }
459 smset.save (argv[2]);
460 }
461 else if (options.command == Options::DECODE)
462 {
463 assert (argc == 3);
464
465 WavSet smset, wset;
466 load_or_die (smset, argv[1]);
467
468 for (vector<WavSetWave>::const_iterator si = smset.waves.begin(); si != smset.waves.end(); si++)
469 {
470 Audio audio;
471 if (!audio.load (si->path, AUDIO_SKIP_DEBUG))
472 {
473 sm_printf ("can't load %s\n", si->path.c_str());
474 exit (1);
475 }
476
477 string wavpath = options.data_dir + "/" + int2str (si->midi_note) + ".wav";
478 string cmd = "smplay --rate=" + int2str (audio.mix_freq) + " " +si->path.c_str() + " --export " + wavpath + " " + options.args;
479 sm_printf ("[%s] ## %s\n", time2str (get_time() - start_time).c_str(), cmd.c_str());
480 int rc = system (cmd.c_str());
481 if (rc != 0)
482 {
483 printf ("command execution failed: %d\n", WEXITSTATUS (rc));
484 exit (1);
485 }
486
487 WavSetWave new_wave = *si;
488 new_wave.path = wavpath;
489 new_wave.audio = NULL; // without this, Audio destructor will be run twice
490 wset.waves.push_back (new_wave);
491 }
492 wset.save (argv[2]);
493 }
494 else if (options.command == Options::DELTA)
495 {
496 assert (argc >= 2);
497
498 vector<WavSet> wsets;
499 map<int,bool> midi_note_used;
500
501 for (int i = 1; i < argc; i++)
502 {
503 WavSet wset;
504 load_or_die (wset, argv[i]);
505 wsets.push_back (wset);
506 for (vector<WavSetWave>::const_iterator wi = wset.waves.begin(); wi != wset.waves.end(); wi++)
507 midi_note_used[wi->midi_note] = true;
508 }
509 for (int i = 0; i < 128; i++)
510 {
511 if (midi_note_used[i])
512 {
513 double ref_delta = -1; /* init to get rid of gcc warning */
514
515 sm_printf ("%3d: ", i);
516 vector<WavSetWave>::iterator w0 = find_wave (wsets[0], i);
517 assert (w0 != wsets[0].waves.end());
518 for (size_t w = 1; w < wsets.size(); w++)
519 {
520 vector<WavSetWave>::iterator w1 = find_wave (wsets[w], i);
521 assert (w1 != wsets[w].waves.end());
522
523 vector<float> data0, data1;
524 if (load_wav_file (w0->path, data0) && load_wav_file (w1->path, data1))
525 {
526 double this_delta = delta (data0, data1);
527 if (w == 1)
528 ref_delta = this_delta;
529 sm_printf ("%3.4f%% ", 100 * this_delta / ref_delta);
530 }
531 else
532 {
533 sm_printf ("an error occured during loading the files.\n");
534 exit (1);
535 }
536 }
537 sm_printf ("\n");
538 }
539 }
540 }
541 else if (options.command == Options::LINK)
542 {
543 assert (argc == 2);
544
545 WavSet wset;
546 load_or_die (wset, argv[1]);
547 wset.save (argv[1], true);
548 }
549 else if (options.command == Options::EXTRACT)
550 {
551 assert (argc == 3);
552 WavSet wset;
553 load_or_die (wset, argv[1]);
554 for (vector<WavSetWave>::const_iterator wi = wset.waves.begin(); wi != wset.waves.end(); wi++)
555 if (wi->path == argv[2])
556 {
557 wi->audio->save (argv[2]);
558 return 0;
559 }
560 fprintf (stderr, "error: path %s not found\n", argv[2]);
561 exit (1);
562 }
563 else if (options.command == Options::GET_MARKERS)
564 {
565 assert (argc == 2);
566
567 WavSet wset;
568 load_or_die (wset, argv[1]);
569
570 sm_printf ("# smwavset markers for %s\n", argv[1]);
571 sm_printf ("\n");
572
573 for (vector<WavSetWave>::const_iterator wi = wset.waves.begin(); wi != wset.waves.end(); wi++)
574 {
575 string loop_type;
576 if (!Audio::loop_type_to_string (wi->audio->loop_type, loop_type))
577 {
578 g_printerr ("smwavset: can't convert loop type to string");
579 exit (1);
580 }
581 if (options.loop_markers)
582 {
583 sm_printf ("set-marker loop-type %d %d %d %d %s\n", wi->midi_note, wi->channel,
584 wi->velocity_range_min, wi->velocity_range_max, loop_type.c_str());
585 sm_printf ("set-marker loop-start %d %d %d %d %d\n", wi->midi_note, wi->channel,
586 wi->velocity_range_min, wi->velocity_range_max, wi->audio->loop_start);
587 sm_printf ("set-marker loop-end %d %d %d %d %d\n", wi->midi_note, wi->channel,
588 wi->velocity_range_min, wi->velocity_range_max, wi->audio->loop_end);
589 }
590 if (options.loop_markers_ms)
591 {
592 const double zero_values_ms = wi->audio->zero_values_at_start / wi->audio->mix_freq * 1000.0;
593 const double loop_start = wi->audio->loop_start * wi->audio->frame_step_ms - zero_values_ms;
594 const double loop_end = wi->audio->loop_end * wi->audio->frame_step_ms - zero_values_ms;
595
596 sm_printf ("set-loop-ms %d %s %f %f\n", wi->midi_note, loop_type.c_str(), loop_start, loop_end);
597 }
598 }
599 }
600 else if (options.command == Options::SET_MARKERS)
601 {
602 assert (argc == 3);
603
604 WavSet wset;
605 load_or_die (wset, argv[1]);
606
607 MicroConf cfg (argv[2]);
608 while (cfg.next())
609 {
610 string marker_type;
611 int midi_note, channel, vmin, vmax;
612 string arg;
613
614 if (cfg.command ("set-marker", marker_type, midi_note, channel, vmin, vmax, arg))
615 {
616 bool match = false;
617 for (vector<WavSetWave>::const_iterator wi = wset.waves.begin(); wi != wset.waves.end(); wi++)
618 {
619 if ((wi->midi_note == midi_note)
620 && (wi->channel == channel)
621 && (wi->velocity_range_min == vmin)
622 && (wi->velocity_range_max == vmax))
623 {
624 if (marker_type == "start")
625 {
626 fprintf (stderr, "smwavset: ignoring no longer supported marker start\n");
627 match = true;
628 }
629 else if (marker_type == "loop-type")
630 {
631 Audio::LoopType loop_type;
632
633 if (Audio::string_to_loop_type (arg, loop_type))
634 {
635 wi->audio->loop_type = loop_type;
636 match = true;
637 }
638 else
639 {
640 g_printerr ("smwavset: unknown loop_type %s\n", arg.c_str());
641 exit (1);
642 }
643 }
644 else if (marker_type == "loop-start")
645 {
646 wi->audio->loop_start = atoi (arg.c_str());
647 match = true;
648 }
649 else if (marker_type == "loop-end")
650 {
651 wi->audio->loop_end = atoi (arg.c_str());
652 match = true;
653 }
654 }
655 }
656 if (!match)
657 {
658 sm_printf ("no match for marker %s %d %d %d %d %s\n", marker_type.c_str(), midi_note, channel,
659 vmin, vmax, arg.c_str());
660 exit (1);
661 }
662 }
663 else
664 {
665 cfg.die_if_unknown();
666 }
667 }
668 wset.save (argv[1]);
669 }
670 else if (options.command == Options::BUILD)
671 {
672 assert (argc == 3);
673
674 string inst_filename = argv[1];
675 string out_filename = argv[2];
676
677 Instrument inst;
678 Error error = inst.load (inst_filename);
679 if (error)
680 {
681 fprintf (stderr, "%s: can't open input file: %s: %s\n", options.program_name.c_str(), inst_filename.c_str(), error.message());
682 exit (1);
683 }
684
685 WavSetBuilder builder (&inst, /* keep_samples */ false);
686 std::unique_ptr<WavSet> smset (builder.run());
687 assert (smset);
688
689 smset->save (out_filename);
690 }
691 else
692 {
693 sm_printf ("You need to specify a command (init, add, list, encode, decode, delta, build).\n\n");
694 Options::print_usage();
695 exit (1);
696 }
697 }
698