libsmf
smfsh.c
Go to the documentation of this file.
1 /*-
2  * Copyright (c) 2007, 2008 Edward Tomasz NapieraƂa <trasz@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  *
14  * ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
15  * AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18  * THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
20  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
21  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  */
27 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #ifdef __MINGW32__
38 #define EX_OK 0
39 #define EX_USAGE 64
40 #else /* ! __MINGW32__ */
41 #include <sysexits.h>
42 #endif /* ! __MINGW32__ */
43 #include <string.h>
44 #include <ctype.h>
45 #include <assert.h>
46 #include "smf.h"
47 #include "config.h"
48 
49 #ifdef HAVE_LIBREADLINE
50 #include <readline/readline.h>
51 #include <readline/history.h>
52 #endif
53 
56 smf_t *smf = NULL;
57 char *last_file_name = NULL;
58 
59 #define COMMAND_LENGTH 10
60 
61 static void
62 log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer notused)
63 {
64  if (strcmp(log_domain, "smfsh") == 0)
65  fprintf(stderr, "%s\n", message);
66  else
67  fprintf(stderr, "%s: %s\n", log_domain, message);
68 }
69 
70 static int cmd_track(char *arg);
71 
72 static int
73 cmd_load(char *file_name)
74 {
75  char *decoded;
76 
77  if (file_name == NULL) {
78  if (last_file_name == NULL) {
79  g_critical("Please specify file name.");
80  return (-1);
81  }
82 
83  file_name = strdup(last_file_name);
84  } else {
85  file_name = strdup(file_name);
86  }
87 
88  selected_track = NULL;
89  selected_event = NULL;
90 
91  if (smf != NULL) {
92  smf_delete(smf);
93  smf = NULL;
94  }
95 
96  if (last_file_name != NULL)
97  free(last_file_name);
98  last_file_name = strdup(file_name);
99 
100  smf = smf_load(file_name);
101  if (smf == NULL) {
102  g_critical("Couldn't load '%s'.", file_name);
103 
104  smf = smf_new();
105  if (smf == NULL) {
106  g_critical("Cannot initialize smf_t.");
107  return (-1);
108  }
109 
110  return (-2);
111  }
112 
113  g_message("File '%s' loaded.", file_name);
114  decoded = smf_decode(smf);
115  g_message("%s.", decoded);
116  free(decoded);
117 
118  cmd_track("1");
119 
120  free(file_name);
121 
122  return (0);
123 }
124 
125 static int
126 cmd_save(char *file_name)
127 {
128  int ret;
129 
130  if (file_name == NULL) {
131  if (last_file_name == NULL) {
132  g_critical("Please specify file name.");
133  return (-1);
134  }
135 
136  file_name = strdup(last_file_name);
137  } else {
138  file_name = strdup(file_name);
139  }
140 
141  if (last_file_name != NULL)
142  free(last_file_name);
143  last_file_name = strdup(file_name);
144 
145  ret = smf_save(smf, file_name);
146  if (ret) {
147  g_critical("Couldn't save '%s'", file_name);
148  return (-1);
149  }
150 
151  g_message("File '%s' saved.", file_name);
152 
153  free(file_name);
154 
155  return (0);
156 }
157 
158 static int
159 cmd_ppqn(char *new_ppqn)
160 {
161  int tmp;
162  char *end;
163 
164  if (new_ppqn == NULL) {
165  g_message("Pulses Per Quarter Note (aka Division) is %d.", smf->ppqn);
166  } else {
167  tmp = strtol(new_ppqn, &end, 10);
168  if (end - new_ppqn != strlen(new_ppqn)) {
169  g_critical("Invalid PPQN, garbage characters after the number.");
170  return (-1);
171  }
172 
173  if (tmp <= 0) {
174  g_critical("Invalid PPQN, valid values are greater than zero.");
175  return (-2);
176  }
177 
178  if (smf_set_ppqn(smf, tmp)) {
179  g_message("smf_set_ppqn failed.");
180  return (-3);
181  }
182 
183  g_message("Pulses Per Quarter Note changed to %d.", smf->ppqn);
184  }
185 
186  return (0);
187 }
188 
189 static int
190 cmd_format(char *new_format)
191 {
192  int tmp;
193  char *end;
194 
195  if (new_format == NULL) {
196  g_message("Format is %d.", smf->format);
197  } else {
198  tmp = strtol(new_format, &end, 10);
199  if (end - new_format != strlen(new_format)) {
200  g_critical("Invalid format value, garbage characters after the number.");
201  return (-1);
202  }
203 
204  if (tmp < 0 || tmp > 2) {
205  g_critical("Invalid format value, valid values are in range 0 - 2, inclusive.");
206  return (-2);
207  }
208 
209  if (smf_set_format(smf, tmp)) {
210  g_critical("smf_set_format failed.");
211  return (-3);
212  }
213 
214  g_message("Forma changed to %d.", smf->format);
215  }
216 
217  return (0);
218 }
219 
220 static int
221 cmd_tracks(char *notused)
222 {
223  if (smf->number_of_tracks > 0)
224  g_message("There are %d tracks, numbered from 1 to %d.", smf->number_of_tracks, smf->number_of_tracks);
225  else
226  g_message("There are no tracks.");
227 
228  return (0);
229 }
230 
231 static int
232 parse_track_number(const char *arg)
233 {
234  int num;
235  char *end;
236 
237  if (arg == NULL) {
238  if (selected_track == NULL) {
239  g_message("No track currently selected and no track number given.");
240  return (-1);
241  } else {
242  return (selected_track->track_number);
243  }
244  }
245 
246  num = strtol(arg, &end, 10);
247  if (end - arg != strlen(arg)) {
248  g_critical("Invalid track number, garbage characters after the number.");
249  return (-1);
250  }
251 
252  if (num < 1 || num > smf->number_of_tracks) {
253  if (smf->number_of_tracks > 0) {
254  g_critical("Invalid track number specified; valid choices are 1 - %d.", smf->number_of_tracks);
255  } else {
256  g_critical("There are no tracks.");
257  }
258 
259  return (-1);
260  }
261 
262  return (num);
263 }
264 
265 static int
266 cmd_track(char *arg)
267 {
268  int num;
269 
270  if (arg == NULL) {
271  if (selected_track == NULL)
272  g_message("No track currently selected.");
273  else
274  g_message("Currently selected is track number %d, containing %d events.",
275  selected_track->track_number, selected_track->number_of_events);
276  } else {
277  if (smf->number_of_tracks == 0) {
278  g_message("There are no tracks.");
279  return (-1);
280  }
281 
282  num = parse_track_number(arg);
283  if (num < 0)
284  return (-1);
285 
286  selected_track = smf_get_track_by_number(smf, num);
287  if (selected_track == NULL) {
288  g_critical("smf_get_track_by_number() failed, track not selected.");
289  return (-3);
290  }
291 
292  selected_event = NULL;
293 
294  g_message("Track number %d selected; it contains %d events.",
295  selected_track->track_number, selected_track->number_of_events);
296  }
297 
298  return (0);
299 }
300 
301 static int
302 cmd_trackadd(char *notused)
303 {
304  selected_track = smf_track_new();
305  if (selected_track == NULL) {
306  g_critical("smf_track_new() failed, track not created.");
307  return (-1);
308  }
309 
310  smf_add_track(smf, selected_track);
311 
312  selected_event = NULL;
313 
314  g_message("Created new track; track number %d selected.", selected_track->track_number);
315 
316  return (0);
317 }
318 
319 static int
320 cmd_trackrm(char *arg)
321 {
322  int num = parse_track_number(arg);
323 
324  if (num < 0)
325  return (-1);
326 
327  if (selected_track != NULL && num == selected_track->track_number) {
328  selected_track = NULL;
329  selected_event = NULL;
330  }
331 
333 
334  g_message("Track %d removed.", num);
335 
336  return (0);
337 }
338 
339 #define BUFFER_SIZE 1024
340 
341 static int
342 show_event(smf_event_t *event)
343 {
344  int off = 0, i;
345  char *decoded, *type;
346 
347  if (smf_event_is_metadata(event))
348  type = "Metadata";
349  else
350  type = "Event";
351 
352  decoded = smf_event_decode(event);
353 
354  if (decoded == NULL) {
355  decoded = malloc(BUFFER_SIZE);
356  if (decoded == NULL) {
357  g_critical("show_event: malloc failed.");
358  return (-1);
359  }
360 
361  off += snprintf(decoded + off, BUFFER_SIZE - off, "Unknown event:");
362 
363  for (i = 0; i < event->midi_buffer_length && i < 5; i++)
364  off += snprintf(decoded + off, BUFFER_SIZE - off, " 0x%x", event->midi_buffer[i]);
365  }
366 
367  g_message("%d: %s: %s, %f seconds, %d pulses, %d delta pulses", event->event_number, type, decoded,
368  event->time_seconds, event->time_pulses, event->delta_time_pulses);
369 
370  free(decoded);
371 
372  return (0);
373 }
374 
375 static int
376 cmd_events(char *notused)
377 {
378  smf_event_t *event;
379 
380  if (selected_track == NULL) {
381  g_critical("No track selected - please use 'track <number>' command first.");
382  return (-1);
383  }
384 
385  if (selected_track->number_of_events == 0) {
386  g_message("Selected track is empty.");
387  return (0);
388  }
389 
390  g_message("List of events in track %d follows:", selected_track->track_number);
391 
392  smf_rewind(smf);
393 
394  while ((event = smf_track_get_next_event(selected_track)) != NULL)
395  show_event(event);
396 
397  smf_rewind(smf);
398 
399  return (0);
400 }
401 
402 static int
403 parse_event_number(const char *arg)
404 {
405  int num;
406  char *end;
407 
408  if (selected_track == NULL) {
409  g_critical("You need to select track first (using 'track <number>').");
410  return (-1);
411  }
412 
413  if (arg == NULL) {
414  if (selected_event == NULL) {
415  g_message("No event currently selected and no event number given.");
416  return (-1);
417  } else {
418  return (selected_event->event_number);
419  }
420  }
421 
422  num = strtol(arg, &end, 10);
423  if (end - arg != strlen(arg)) {
424  g_critical("Invalid event number, garbage characters after the number.");
425  return (-1);
426  }
427 
428  if (num < 1 || num > selected_track->number_of_events) {
429  if (selected_track->number_of_events > 0)
430  g_critical("Invalid event number specified; valid choices are 1 - %d.", selected_track->number_of_events);
431  else
432  g_critical("There are no events in currently selected track.");
433 
434  return (-1);
435  }
436 
437  return (num);
438 }
439 
440 static int
441 cmd_event(char *arg)
442 {
443  int num;
444 
445  if (arg == NULL) {
446  if (selected_event == NULL) {
447  g_message("No event currently selected.");
448  } else {
449  g_message("Currently selected is event %d, track %d.", selected_event->event_number, selected_track->track_number);
450  show_event(selected_event);
451  }
452  } else {
453  num = parse_event_number(arg);
454  if (num < 0)
455  return (-1);
456 
457  selected_event = smf_track_get_event_by_number(selected_track, num);
458  if (selected_event == NULL) {
459  g_critical("smf_get_event_by_number() failed, event not selected.");
460  return (-2);
461  }
462 
463  g_message("Event number %d selected.", selected_event->event_number);
464  show_event(selected_event);
465  }
466 
467  return (0);
468 }
469 
470 static int
471 decode_hex(char *str, unsigned char **buffer, int *length)
472 {
473  int i, value, midi_buffer_length;
474  char buf[3];
475  unsigned char *midi_buffer = NULL;
476  char *end = NULL;
477 
478  if ((strlen(str) % 2) != 0) {
479  g_critical("Hex value should have even number of characters, you know.");
480  goto error;
481  }
482 
483  midi_buffer_length = strlen(str) / 2;
484  midi_buffer = malloc(midi_buffer_length);
485  if (midi_buffer == NULL) {
486  g_critical("malloc() failed.");
487  goto error;
488  }
489 
490  for (i = 0; i < midi_buffer_length; i++) {
491  buf[0] = str[i * 2];
492  buf[1] = str[i * 2 + 1];
493  buf[2] = '\0';
494  value = strtoll(buf, &end, 16);
495 
496  if (end - buf != 2) {
497  g_critical("Garbage characters detected after hex.");
498  goto error;
499  }
500 
501  midi_buffer[i] = value;
502  }
503 
504  *buffer = midi_buffer;
505  *length = midi_buffer_length;
506 
507  return (0);
508 
509 error:
510  if (midi_buffer != NULL)
511  free(midi_buffer);
512 
513  return (-1);
514 }
515 
516 static void
517 eventadd_usage(void)
518 {
519  g_message("Usage: add <time-in-seconds> <midi-in-hex> - for example, 'add 1 903C7F' will add");
520  g_message("Note On event, note C4, velocity 127, channel 1, one second from the start of song, channel 1.");
521 }
522 
523 static int
524 cmd_eventadd(char *str)
525 {
526  int midi_buffer_length;
527  double seconds;
528  unsigned char *midi_buffer;
529  char *time, *endtime;
530 
531  if (selected_track == NULL) {
532  g_critical("Please select a track first, using 'track <number>' command.");
533  return (-1);
534  }
535 
536  if (str == NULL) {
537  eventadd_usage();
538  return (-2);
539  }
540 
541  /* Extract the time. Don't use strsep(3), it doesn't work on SunOS. */
542  time = str;
543  str = strchr(str, ' ');
544  if (str != NULL) {
545  *str = '\0';
546  str++;
547  }
548 
549  seconds = strtod(time, &endtime);
550  if (endtime - time != strlen(time)) {
551  g_critical("Time is supposed to be a number, without trailing characters.");
552  return (-3);
553  }
554 
555  /* Called with one parameter? */
556  if (str == NULL) {
557  eventadd_usage();
558  return (-4);
559  }
560 
561  if (decode_hex(str, &midi_buffer, &midi_buffer_length)) {
562  eventadd_usage();
563  return (-5);
564  }
565 
566  selected_event = smf_event_new();
567  if (selected_event == NULL) {
568  g_critical("smf_event_new() failed, event not created.");
569  return (-6);
570  }
571 
572  selected_event->midi_buffer = midi_buffer;
573  selected_event->midi_buffer_length = midi_buffer_length;
574 
575  if (smf_event_is_valid(selected_event) == 0) {
576  g_critical("Event is invalid from the MIDI specification point of view, not created.");
577  smf_event_delete(selected_event);
578  selected_event = NULL;
579  return (-7);
580  }
581 
582  smf_track_add_event_seconds(selected_track, selected_event, seconds);
583 
584  g_message("Event created.");
585 
586  return (0);
587 }
588 
589 static int
590 cmd_text(char *str)
591 {
592  double seconds, type;
593  char *time, *typestr, *end;
594 
595  if (selected_track == NULL) {
596  g_critical("Please select a track first, using 'track <number>' command.");
597  return (-1);
598  }
599 
600  if (str == NULL) {
601  g_critical("Usage: text <time-in-seconds> <event-type> <text-itself>");
602  return (-2);
603  }
604 
605  /* Extract the time. Don't use strsep(3), it doesn't work on SunOS. */
606  time = str;
607  str = strchr(str, ' ');
608  if (str != NULL) {
609  *str = '\0';
610  str++;
611  }
612 
613  seconds = strtod(time, &end);
614  if (end - time != strlen(time)) {
615  g_critical("Time is supposed to be a number, without trailing characters.");
616  return (-3);
617  }
618 
619  /* Called with one parameter? */
620  if (str == NULL) {
621  g_critical("Usage: text <time-in-seconds> <event-type> <text-itself>");
622  return (-4);
623  }
624 
625  /* Extract the event type. */
626  typestr = str;
627  str = strchr(str, ' ');
628  if (str != NULL) {
629  *str = '\0';
630  str++;
631  }
632 
633  type = strtod(typestr, &end);
634  if (end - typestr != strlen(typestr)) {
635  g_critical("Type is supposed to be a number, without trailing characters.");
636  return (-4);
637  }
638 
639  if (type < 1 || type > 9) {
640  g_critical("Valid values for type are 1 - 9, inclusive.");
641  return (-5);
642  }
643 
644  /* Called with one parameter? */
645  if (str == NULL) {
646  g_critical("Usage: text <time-in-seconds> <event-type> <text-itself>");
647  return (-4);
648  }
649 
650  selected_event = smf_event_new_textual(type, str);
651  if (selected_event == NULL) {
652  g_critical("smf_event_new_textual() failed, event not created.");
653  return (-6);
654  }
655 
656  assert(smf_event_is_valid(selected_event));
657 
658  smf_track_add_event_seconds(selected_track, selected_event, seconds);
659 
660  g_message("Event created.");
661 
662  return (0);
663 }
664 
665 
666 static int
667 cmd_eventaddeot(char *time)
668 {
669  double seconds;
670  char *end;
671 
672  if (selected_track == NULL) {
673  g_critical("Please select a track first, using 'track <number>' command.");
674  return (-1);
675  }
676 
677  if (time == NULL) {
678  g_critical("Please specify the time, in seconds.");
679  return (-2);
680  }
681 
682  seconds = strtod(time, &end);
683  if (end - time != strlen(time)) {
684  g_critical("Time is supposed to be a number, without trailing characters.");
685  return (-3);
686  }
687 
688  if (smf_track_add_eot_seconds(selected_track, seconds)) {
689  g_critical("smf_track_add_eot() failed.");
690  return (-4);
691  }
692 
693  g_message("Event created.");
694 
695  return (0);
696 }
697 
698 static int
699 cmd_eventrm(char *number)
700 {
701  int num = parse_event_number(number);
702 
703  if (num < 0)
704  return (-1);
705 
706  if (selected_event != NULL && num == selected_event->event_number)
707  selected_event = NULL;
708 
709  smf_event_delete(smf_track_get_event_by_number(selected_track, num));
710 
711  g_message("Event #%d removed.", num);
712 
713  return (0);
714 }
715 
716 static int
717 cmd_tempo(char *notused)
718 {
719  int i;
720  smf_tempo_t *tempo;
721 
722  for (i = 0;; i++) {
723  tempo = smf_get_tempo_by_number(smf, i);
724  if (tempo == NULL)
725  break;
726 
727  g_message("Tempo #%d: Starts at %d pulses, %f seconds, setting %d microseconds per quarter note, %.2f BPM.",
728  i, tempo->time_pulses, tempo->time_seconds, tempo->microseconds_per_quarter_note,
729  60000000.0 / (double)tempo->microseconds_per_quarter_note);
730  g_message("Time signature: %d/%d, %d clocks per click, %d 32nd notes per quarter note.",
731  tempo->numerator, tempo->denominator, tempo->clocks_per_click, tempo->notes_per_note);
732  }
733 
734  return (0);
735 }
736 
737 static int
738 cmd_length(char *notused)
739 {
740  g_message("Length: %d pulses, %f seconds.", smf_get_length_pulses(smf), smf_get_length_seconds(smf));
741 
742  return (0);
743 }
744 
745 static int
746 cmd_version(char *notused)
747 {
748  g_message("libsmf version %s.", smf_get_version());
749 
750  return (0);
751 }
752 
753 static int
754 cmd_exit(char *notused)
755 {
756  g_debug("Good bye.");
757  exit(0);
758 }
759 
760 static int cmd_help(char *notused);
761 
762 static struct command_struct {
763  char *name;
764  int (*function)(char *command);
765  char *help;
766 } commands[] = {{"help", cmd_help, "Show this help."},
767  {"?", cmd_help, NULL},
768  {"load", cmd_load, "Load named file."},
769  {"open", cmd_load},
770  {"save", cmd_save, "Save to named file."},
771  {"ppqn", cmd_ppqn, "Show ppqn (aka division), or set ppqn if used with parameter."},
772  {"format", cmd_format, "Show format, or set format if used with parameter."},
773  {"tracks", cmd_tracks, "Show number of tracks."},
774  {"track", cmd_track, "Show number of currently selected track, or select a track."},
775  {"trackadd", cmd_trackadd, "Add a track and select it."},
776  {"trackrm", cmd_trackrm, "Remove currently selected track."},
777  {"events", cmd_events, "Show events in the currently selected track."},
778  {"event", cmd_event, "Show number of currently selected event, or select an event."},
779  {"add", cmd_eventadd, "Add an event and select it."},
780  {"text", cmd_text, "Add textual event and select it."},
781  {"eventadd", cmd_eventadd, NULL},
782  {"eot", cmd_eventaddeot, "Add an End Of Track event."},
783  {"eventaddeot", cmd_eventaddeot, NULL},
784  {"eventrm", cmd_eventrm, NULL},
785  {"rm", cmd_eventrm, "Remove currently selected event."},
786  {"tempo", cmd_tempo, "Show tempo map."},
787  {"length", cmd_length, "Show length of the song."},
788  {"version", cmd_version, "Show libsmf version."},
789  {"exit", cmd_exit, "Exit to shell."},
790  {"quit", cmd_exit, NULL},
791  {"bye", cmd_exit, NULL},
792  {NULL, NULL, NULL}};
793 
794 static int
795 cmd_help(char *notused)
796 {
797  int i, padding_length;
798  char padding[COMMAND_LENGTH + 1];
799  struct command_struct *tmp;
800 
801  g_message("Available commands:");
802 
803  for (tmp = commands; tmp->name != NULL; tmp++) {
804  /* Skip commands with no help string. */
805  if (tmp->help == NULL)
806  continue;
807 
808  padding_length = COMMAND_LENGTH - strlen(tmp->name);
809  assert(padding_length >= 0);
810  for (i = 0; i < padding_length; i++)
811  padding[i] = ' ';
812  padding[i] = '\0';
813 
814  g_message("%s:%s%s", tmp->name, padding, tmp->help);
815  }
816 
817  return (0);
818 }
819 
825 static void
826 strip_unneeded_whitespace(char *str, int len)
827 {
828  char *src, *dest;
829  int skip_white = 1;
830 
831  for (src = str, dest = str; src < dest + len; src++) {
832  if (*src == '\n' || *src == '\0') {
833  *dest = '\0';
834  break;
835  }
836 
837  if (isspace(*src)) {
838  if (skip_white)
839  continue;
840 
841  skip_white = 1;
842  } else {
843  skip_white = 0;
844  }
845 
846  *dest = *src;
847  dest++;
848  }
849 
850  /* Remove trailing whitespace. */
851  len = strlen(dest);
852  if (isspace(dest[len - 1]))
853  dest[len - 1] = '\0';
854 }
855 
856 static char *
857 read_command(void)
858 {
859  char *buf;
860  int len;
861 
862 #ifdef HAVE_LIBREADLINE
863  buf = readline("smfsh> ");
864 #else
865  buf = malloc(1024);
866  if (buf == NULL) {
867  g_critical("Malloc failed.");
868  return (NULL);
869  }
870 
871  fprintf(stdout, "smfsh> ");
872  fflush(stdout);
873 
874  buf = fgets(buf, 1024, stdin);
875 #endif
876 
877  if (buf == NULL) {
878  fprintf(stdout, "exit\n");
879  return (strdup("exit"));
880  }
881 
882  strip_unneeded_whitespace(buf, 1024);
883 
884  len = strlen(buf);
885 
886  if (len == 0)
887  return (read_command());
888 
889 #ifdef HAVE_LIBREADLINE
890  add_history(buf);
891 #endif
892 
893  return (buf);
894 }
895 
896 static int
897 execute_command(char *line)
898 {
899  char *command, *args;
900  struct command_struct *tmp;
901 
902  command = line;
903  args = strchr(line, ' ');
904  if (args != NULL) {
905  *args = '\0';
906  args++;
907  }
908 
909  for (tmp = commands; tmp->name != NULL; tmp++) {
910  if (strcmp(tmp->name, command) == 0)
911  return ((tmp->function)(args));
912  }
913 
914  g_warning("No such command: '%s'. Type 'help' to see available commands.", command);
915 
916  return (-1);
917 }
918 
919 static void
920 read_and_execute_command(void)
921 {
922  int ret;
923  char *command_line, *command, *next_command;
924 
925  command = command_line = read_command();
926 
927  do {
928  next_command = strchr(command, ';');
929  if (next_command != NULL) {
930  *next_command = '\0';
931  next_command++;
932  }
933 
934  strip_unneeded_whitespace(command, 1024);
935  if (strlen(command) > 0) {
936  ret = execute_command(command);
937  if (ret)
938  g_warning("Command finished with error.");
939  }
940 
941  command = next_command;
942 
943  } while (command);
944 
945  free(command_line);
946 }
947 
948 #ifdef HAVE_LIBREADLINE
949 
950 static char *
951 smfsh_command_generator(const char *text, int state)
952 {
953  static struct command_struct *command = commands;
954  char *tmp;
955 
956  if (state == 0)
957  command = commands;
958 
959  while (command->name != NULL) {
960  tmp = command->name;
961  command++;
962 
963  if (strncmp(tmp, text, strlen(text)) == 0)
964  return (strdup(tmp));
965  }
966 
967  return (NULL);
968 }
969 
970 static char **
971 smfsh_completion(const char *text, int start, int end)
972 {
973  int i;
974 
975  /* Return NULL if "text" is not the first word in the input line. */
976  if (start != 0) {
977  for (i = 0; i < start; i++) {
978  if (!isspace(rl_line_buffer[i]))
979  return (NULL);
980  }
981  }
982 
983  return (rl_completion_matches(text, smfsh_command_generator));
984 }
985 
986 #endif
987 
988 static void
989 usage(void)
990 {
991  fprintf(stderr, "usage: smfsh [-V | file]\n");
992 
993  exit(EX_USAGE);
994 }
995 
996 int
997 main(int argc, char *argv[])
998 {
999  int ch;
1000 
1001  while ((ch = getopt(argc, argv, "V")) != -1) {
1002  switch (ch) {
1003  case 'V':
1004  cmd_version(NULL);
1005  exit(EX_OK);
1006 
1007  case '?':
1008  default:
1009  usage();
1010  }
1011  }
1012 
1013  if (argc > 2)
1014  usage();
1015 
1016  g_log_set_default_handler(log_handler, NULL);
1017 
1018  smf = smf_new();
1019  if (smf == NULL) {
1020  g_critical("Cannot initialize smf_t.");
1021  return (-1);
1022  }
1023 
1024  if (argc == 2)
1025  cmd_load(argv[1]);
1026  else
1027  cmd_trackadd(NULL);
1028 
1029 #ifdef HAVE_LIBREADLINE
1030  rl_readline_name = "smfsh";
1031  rl_attempted_completion_function = smfsh_completion;
1032 #endif
1033 
1034  for (;;)
1035  read_and_execute_command();
1036 
1037  return (0);
1038 }
1039 
void smf_rewind(smf_t *smf)
Rewinds the SMF.
Definition: smf.c:899
smf_event_t * smf_track_get_event_by_number(const smf_track_t *track, int event_number)
Definition: smf.c:769
double smf_get_length_seconds(const smf_t *smf)
Definition: smf.c:1071
int clocks_per_click
Definition: smf.h:264
double time_seconds
Definition: smf.h:260
int smf_event_is_valid(const smf_event_t *event) WARN_UNUSED_RESULT
Definition: smf_load.c:745
int format
Definition: smf.h:231
int numerator
Definition: smf.h:262
int smf_set_ppqn(smf_t *smf, int ppqn)
Sets the PPQN (&quot;Division&quot;) field of MThd header.
Definition: smf.c:674
int smf_save(smf_t *smf, const char *file_name) WARN_UNUSED_RESULT
Writes the contents of SMF to the file given.
Definition: smf_save.c:620
unsigned char * midi_buffer
Pointer to the buffer containing MIDI message.
Definition: smf.h:322
Represents a &quot;song&quot;, that is, collection of one or more tracks.
Definition: smf.h:230
smf_t * smf_load(const char *file_name) WARN_UNUSED_RESULT
Loads SMF file.
Definition: smf_load.c:912
Represents a single track.
Definition: smf.h:271
void smf_add_track(smf_t *smf, smf_track_t *track)
Appends smf_track_t to smf.
Definition: smf.c:156
smf_event_t * selected_event
Definition: smfsh.c:55
int ppqn
These fields are extracted from &quot;division&quot; field of MThd header.
Definition: smf.h:234
Describes a single tempo or time signature change.
Definition: smf.h:258
int smf_event_is_metadata(const smf_event_t *event) WARN_UNUSED_RESULT
Definition: smf_decode.c:57
int denominator
Definition: smf.h:263
smf_tempo_t * smf_get_tempo_by_number(const smf_t *smf, int number) WARN_UNUSED_RESULT
Definition: smf_tempo.c:257
int track_number
Definition: smf.h:274
int notes_per_note
Definition: smf.h:265
void smf_event_delete(smf_event_t *event)
Detaches event from its track and frees it.
Definition: smf.c:363
char * smf_event_decode(const smf_event_t *event) WARN_UNUSED_RESULT
Definition: smf_decode.c:514
char * last_file_name
Definition: smfsh.c:57
int event_number
Number of this event in the track.
Definition: smf.h:306
smf_track_t * selected_track
Definition: smfsh.c:54
int time_pulses
Time, in pulses, since the start of the song.
Definition: smf.h:313
void smf_delete(smf_t *smf)
Frees smf and all it&#39;s descendant structures.
Definition: smf.c:88
const char * smf_get_version(void)
Definition: smf.c:1112
smf_track_t * smf_track_new(void)
Allocates new smf_track_t structure.
Definition: smf.c:110
int midi_buffer_length
Length of the MIDI message in the buffer, in bytes.
Definition: smf.h:325
int number_of_tracks
Definition: smf.h:237
smf_event_t * smf_event_new(void)
Allocates new smf_event_t structure.
Definition: smf.c:217
int number_of_events
Definition: smf.h:275
Public interface declaration for libsmf, Standard MIDI File format library.
int smf_get_length_pulses(const smf_t *smf)
Definition: smf.c:1044
smf_event_t * smf_track_get_next_event(smf_track_t *track)
Returns next event from the track given and advances next event counter.
Definition: smf.c:692
smf_track_t * smf_get_track_by_number(const smf_t *smf, int track_number)
Definition: smf.c:748
double time_seconds
Time, in seconds, since the start of the song.
Definition: smf.h:316
int microseconds_per_quarter_note
Definition: smf.h:261
char * smf_decode(const smf_t *smf) WARN_UNUSED_RESULT
Definition: smf_decode.c:598
smf_event_t * smf_event_new_textual(int type, const char *text)
Definition: smf_save.c:193
Represents a single MIDI event or metaevent.
Definition: smf.h:301
void smf_track_add_event_seconds(smf_track_t *track, smf_event_t *event, double seconds)
Adds event to the track at the time &quot;seconds&quot; seconds from the start of song.
Definition: smf_tempo.c:437
void smf_track_delete(smf_track_t *track)
Detaches track from its smf and frees it.
Definition: smf.c:131
int smf_track_add_eot_seconds(smf_track_t *track, double seconds)
Definition: smf.c:558
smf_t * smf_new(void)
Allocates new smf_t structure.
Definition: smf.c:55
int delta_time_pulses
Note that the time fields are invalid, if event is not attached to a track.
Definition: smf.h:310
int smf_set_format(smf_t *smf, int format)
Sets &quot;Format&quot; field of MThd header to the specified value.
Definition: smf.c:652
int time_pulses
Definition: smf.h:259
int main(int argc, char *argv[])
Definition: smfsh.c:997
smf_t * smf
Definition: smfsh.c:56