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