Import of jack-smf-utils 1.0.
[jack-smf-utils.git] / libsmf / smfsh.c
blobc7d41d6a2d0430a4f6490cd43e85de3f326655b8
1 /*-
2 * Copyright (c) 2007, 2008 Edward Tomasz NapieraƂa <trasz@FreeBSD.org>
3 * All rights reserved.
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.
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.
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <sysexits.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include "smf.h"
34 #include "config.h"
36 #ifdef HAVE_LIBREADLINE
37 #include <readline/readline.h>
38 #include <readline/history.h>
39 #endif
41 smf_track_t *selected_track = NULL;
42 smf_event_t *selected_event = NULL;
43 smf_t *smf = NULL;
44 char *last_file_name = NULL;
46 static void
47 usage(void)
49 fprintf(stderr, "usage: smfsh [file]\n");
51 exit(EX_USAGE);
54 static void
55 log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer notused)
57 fprintf(stderr, "%s: %s\n", log_domain, message);
60 static int cmd_track(char *arg);
62 static int
63 cmd_load(char *file_name)
65 if (file_name == NULL) {
66 if (last_file_name == NULL) {
67 g_critical("Please specify file name.");
68 return -1;
71 file_name = last_file_name;
74 if (smf != NULL)
75 smf_delete(smf);
77 selected_track = NULL;
78 selected_event = NULL;
80 last_file_name = strdup(file_name);
81 smf = smf_load(file_name);
82 if (smf == NULL) {
83 g_critical("Couldn't load '%s'.", file_name);
85 smf = smf_new();
86 if (smf == NULL) {
87 g_critical("Cannot initialize smf_t.");
88 return -1;
91 return -2;
94 g_message("File '%s' loaded.", file_name);
95 g_message("%s.", smf_decode(smf));
97 cmd_track("1");
99 return 0;
102 static int
103 cmd_save(char *file_name)
105 int ret;
107 if (file_name == NULL) {
108 if (last_file_name == NULL) {
109 g_critical("Please specify file name.");
110 return -1;
113 file_name = last_file_name;
116 if (file_name == NULL) {
117 g_critical("Please specify file name.");
118 return -1;
121 last_file_name = strdup(file_name);
122 ret = smf_save(smf, file_name);
123 if (ret) {
124 g_critical("Couldn't save '%s'", file_name);
125 return -1;
128 g_message("File '%s' saved.", file_name);
130 return 0;
133 static int
134 cmd_ppqn(char *new_ppqn)
136 int tmp;
137 char *end;
139 if (new_ppqn == NULL) {
140 g_message("Pulses Per Quarter Note (aka Division) is %d.", smf->ppqn);
141 } else {
142 tmp = strtol(new_ppqn, &end, 10);
143 if (end - new_ppqn != strlen(new_ppqn)) {
144 g_critical("Invalid PPQN, garbage characters after the number.");
145 return -1;
148 if (tmp <= 0) {
149 g_critical("Invalid PPQN, valid values are greater than zero.");
150 return -2;
153 if (smf_set_ppqn(smf, tmp)) {
154 g_message("smf_set_ppqn failed.");
155 return -3;
158 g_message("Pulses Per Quarter Note changed to %d.", smf->ppqn);
161 return 0;
164 static int
165 cmd_format(char *new_format)
167 int tmp;
168 char *end;
170 if (new_format == NULL) {
171 g_message("Format is %d.", smf->format);
172 } else {
173 tmp = strtol(new_format, &end, 10);
174 if (end - new_format != strlen(new_format)) {
175 g_critical("Invalid format value, garbage characters after the number.");
176 return -1;
179 if (tmp < 0 || tmp > 2) {
180 g_critical("Invalid format value, valid values are in range 0 - 2, inclusive.");
181 return -2;
184 if (smf_set_format(smf, tmp)) {
185 g_critical("smf_set_format failed.");
186 return -3;
189 g_message("Forma changed to %d.", smf->format);
192 return 0;
195 static int
196 cmd_tracks(char *notused)
198 if (smf->number_of_tracks > 0)
199 g_message("There are %d tracks, numbered from 1 to %d.", smf->number_of_tracks, smf->number_of_tracks);
200 else
201 g_message("There are no tracks.");
203 return 0;
206 static int
207 parse_track_number(const char *arg)
209 int num;
210 char *end;
212 if (arg == NULL) {
213 if (selected_track == NULL) {
214 g_message("No track currently selected and no track number given.");
215 return -1;
216 } else {
217 return selected_track->track_number;
221 num = strtol(arg, &end, 10);
222 if (end - arg != strlen(arg)) {
223 g_critical("Invalid track number, garbage characters after the number.");
224 return -1;
227 if (num < 1 || num > smf->number_of_tracks) {
228 if (smf->number_of_tracks > 0) {
229 g_critical("Invalid track number specified; valid choices are 1 - %d.", smf->number_of_tracks);
230 } else {
231 g_critical("There are no tracks.");
234 return -1;
237 return num;
240 static int
241 cmd_track(char *arg)
243 int num;
245 if (arg == NULL) {
246 if (selected_track == NULL)
247 g_message("No track currently selected.");
248 else
249 g_message("Currently selected is track number %d, containing %d events.",
250 selected_track->track_number, selected_track->number_of_events);
251 } else {
252 if (smf->number_of_tracks == 0) {
253 g_message("There are no tracks.");
254 return -1;
257 num = parse_track_number(arg);
258 if (num < 0)
259 return -1;
261 selected_track = smf_get_track_by_number(smf, num);
262 if (selected_track == NULL) {
263 g_critical("smf_get_track_by_number() failed, track not selected.");
264 return -3;
267 selected_event = NULL;
269 g_message("Track number %d selected; it contains %d events.",
270 selected_track->track_number, selected_track->number_of_events);
273 return 0;
276 static int
277 cmd_trackadd(char *notused)
279 selected_track = smf_track_new();
280 if (selected_track == NULL) {
281 g_critical("smf_track_new() failed, track not created.");
282 return -1;
285 smf_add_track(smf, selected_track);
287 selected_event = NULL;
289 g_message("Created new track; track number %d selected.", selected_track->track_number);
291 return 0;
294 static int
295 cmd_trackrm(char *arg)
297 int num = parse_track_number(arg);
299 if (num < 0)
300 return -1;
302 if (selected_track != NULL && num == selected_track->track_number) {
303 selected_track = NULL;
304 selected_event = NULL;
307 smf_track_delete(smf_get_track_by_number(smf, num));
309 g_message("Track #%d removed.", num);
311 return 0;
314 #define BUFFER_SIZE 1024
316 static int
317 show_event(smf_event_t *event)
319 int off = 0, i;
320 char *decoded, *type;
322 if (smf_event_is_metadata(event))
323 type = "Metadata";
324 else
325 type = "Event";
327 decoded = smf_event_decode(event);
329 if (decoded == NULL) {
330 decoded = malloc(BUFFER_SIZE);
331 if (decoded == NULL) {
332 g_critical("show_event: malloc failed.");
333 return -1;
336 off += snprintf(decoded + off, BUFFER_SIZE - off, "Unknown event:");
338 for (i = 0; i < event->midi_buffer_length && i < 5; i++)
339 off += snprintf(decoded + off, BUFFER_SIZE - off, " 0x%x", event->midi_buffer[i]);
342 g_message("%d: %s: %s, %f seconds, %d pulses, %d delta pulses", event->event_number, type, decoded,
343 event->time_seconds, event->time_pulses, event->delta_time_pulses);
345 free(decoded);
347 return 0;
350 static int
351 cmd_events(char *notused)
353 smf_event_t *event;
355 if (selected_track == NULL) {
356 g_critical("No track selected - please use 'track [number]' command first.");
357 return -1;
360 g_message("List of events in track %d follows:", selected_track->track_number);
362 smf_rewind(smf);
364 while ((event = smf_track_get_next_event(selected_track)) != NULL) {
365 show_event(event);
368 smf_rewind(smf);
370 return 0;
373 static int
374 parse_event_number(const char *arg)
376 int num;
377 char *end;
379 if (selected_track == NULL) {
380 g_critical("You need to select track first (using 'track <number>').");
381 return -1;
384 if (arg == NULL) {
385 if (selected_event == NULL) {
386 g_message("No event currently selected and no event number given.");
387 return -1;
388 } else {
389 return selected_event->event_number;
393 num = strtol(arg, &end, 10);
394 if (end - arg != strlen(arg)) {
395 g_critical("Invalid event number, garbage characters after the number.");
396 return -1;
399 if (num < 1 || num > selected_track->number_of_events) {
400 if (selected_track->number_of_events > 0) {
401 g_critical("Invalid event number specified; valid choices are 1 - %d.", selected_track->number_of_events);
402 } else {
403 g_critical("There are no events in currently selected track.");
406 return -1;
409 return num;
412 static int
413 cmd_event(char *arg)
415 int num;
417 if (arg == NULL) {
418 if (selected_event == NULL) {
419 g_message("No event currently selected.");
420 } else {
421 g_message("Currently selected is event %d, track %d.", selected_event->event_number, selected_track->track_number);
422 show_event(selected_event);
424 } else {
425 num = parse_event_number(arg);
426 if (num < 0)
427 return -1;
429 selected_event = smf_track_get_event_by_number(selected_track, num);
430 if (selected_event == NULL) {
431 g_critical("smf_get_event_by_number() failed, event not selected.");
432 return -2;
435 g_message("Event number %d selected.", selected_event->event_number);
436 show_event(selected_event);
439 return 0;
442 static int
443 decode_hex(char *str, unsigned char **buffer, int *length)
445 int i, value, midi_buffer_length;
446 char buf[3];
447 unsigned char *midi_buffer = NULL;
448 char *end = NULL;
450 if ((strlen(str) % 2) != 0) {
451 g_critical("Hex value should have even number of characters, you know.");
452 goto error;
455 midi_buffer_length = strlen(str) / 2;
456 midi_buffer = malloc(midi_buffer_length);
457 if (midi_buffer == NULL) {
458 g_critical("malloc() failed.");
459 goto error;
462 for (i = 0; i < midi_buffer_length; i++) {
463 buf[0] = str[i * 2];
464 buf[1] = str[i * 2 + 1];
465 buf[2] = '\0';
466 value = strtoll(buf, &end, 16);
468 if (end - buf != 2) {
469 g_critical("Garbage characters detected after hex.");
470 goto error;
473 midi_buffer[i] = value;
476 *buffer = midi_buffer;
477 *length = midi_buffer_length;
479 return 0;
481 error:
482 if (midi_buffer != NULL)
483 free(midi_buffer);
485 return -1;
488 static void
489 eventadd_usage(void)
491 g_message("Usage: eventadd time-in-seconds midi-in-hex. For example, 'eventadd 1 903C7F'");
492 g_message("will add Note On event, one second from the start of song, channel 1, note C4, velocity 127.");
495 static int
496 cmd_eventadd(char *str)
498 int midi_buffer_length;
499 double seconds;
500 unsigned char *midi_buffer;
501 char *time, *endtime;
503 if (selected_track == NULL) {
504 g_critical("Please select a track first.");
505 return -1;
508 if (str == NULL) {
509 eventadd_usage();
510 return -2;
513 /* Extract the time. */
514 time = strsep(&str, " ");
515 seconds = strtod(time, &endtime);
516 if (endtime - time != strlen(time)) {
517 g_critical("Time is supposed to be a number, without trailing characters.");
518 return -3;
521 /* Called with one parameter? */
522 if (str == NULL) {
523 eventadd_usage();
524 return -4;
527 if (decode_hex(str, &midi_buffer, &midi_buffer_length)) {
528 eventadd_usage();
529 return -5;
532 selected_event = smf_event_new();
533 if (selected_event == NULL) {
534 g_critical("smf_event_new() failed, event not created.");
535 return -6;
538 selected_event->midi_buffer = midi_buffer;
539 selected_event->midi_buffer_length = midi_buffer_length;
541 if (smf_event_is_valid(selected_event) == 0) {
542 g_critical("Event is invalid from the MIDI specification point of view, not created.");
543 smf_event_delete(selected_event);
544 selected_event = NULL;
545 return -7;
548 smf_track_add_event_seconds(selected_track, selected_event, seconds);
550 g_message("Event created.");
552 return 0;
555 static int
556 cmd_eventaddeot(char *time)
558 double seconds;
559 char *end;
561 if (selected_track == NULL) {
562 g_critical("Please select a track first.");
563 return -1;
566 if (time == NULL) {
567 g_critical("Please specify the time, in seconds.");
568 return -2;
571 seconds = strtod(time, &end);
572 if (end - time != strlen(time)) {
573 g_critical("Time is supposed to be a number, without trailing characters.");
574 return -3;
577 if (smf_track_add_eot_seconds(selected_track, seconds)) {
578 g_critical("smf_track_add_eot() failed.");
579 return -4;
582 g_message("Event created.");
584 return 0;
587 static int
588 cmd_eventrm(char *number)
590 int num = parse_event_number(number);
592 if (num < 0)
593 return -1;
595 if (selected_event != NULL && num == selected_event->event_number)
596 selected_event = NULL;
598 smf_event_delete(smf_track_get_event_by_number(selected_track, num));
600 g_message("Event #%d removed.", num);
602 return 0;
605 static int
606 cmd_tempo(char *notused)
608 int i;
609 smf_tempo_t *tempo;
611 for (i = 0;; i++) {
612 tempo = smf_get_tempo_by_number(smf, i);
613 if (tempo == NULL)
614 break;
616 g_message("Tempo #%d: Starts at %d pulses, %f seconds, setting %d microseconds per quarter note, %.2f BPM.",
617 i, tempo->time_pulses, tempo->time_seconds, tempo->microseconds_per_quarter_note,
618 60000000.0 / (double)tempo->microseconds_per_quarter_note);
619 g_message("Time signature: %d/%d, %d clocks per click, %d 32nd notes per quarter note.",
620 tempo->numerator, tempo->denominator, tempo->clocks_per_click, tempo->notes_per_note);
623 return 0;
626 static int
627 cmd_length(char *notused)
629 g_message("Length: %d pulses, %f seconds.", smf_get_length_pulses(smf), smf_get_length_seconds(smf));
631 return 0;
634 static int
635 cmd_version(char *notused)
637 g_message("libsmf version %s.", smf_get_version());
639 return 0;
642 static int
643 cmd_exit(char *notused)
645 g_debug("Good bye.");
646 exit(0);
649 static int cmd_help(char *notused);
651 struct command_struct {
652 char *name;
653 int (*function)(char *command);
654 char *help;
655 } commands[] = {{"help", cmd_help, "show this help."},
656 {"load", cmd_load, "load named file."},
657 {"save", cmd_save, "save to named file."},
658 {"ppqn", cmd_ppqn, "show ppqn (aka division), or set ppqn if used with parameter."},
659 {"format", cmd_format, "show format, or set format if used with parameter."},
660 {"tracks", cmd_tracks, "show number of tracks."},
661 {"track", cmd_track, "show number of currently selected track, or select a track."},
662 {"trackadd", cmd_trackadd, "add a track and select it."},
663 {"trackrm", cmd_trackrm, "remove currently selected track."},
664 {"events", cmd_events, "show events in the currently selected track."},
665 {"event", cmd_event, "show number of currently selected event, or select an event."},
666 {"eventadd", cmd_eventadd, "add an event and select it."},
667 {"add", cmd_eventadd, NULL},
668 {"eventaddeot", cmd_eventaddeot, "add an End Of Track event."},
669 {"eot", cmd_eventaddeot, NULL},
670 {"eventrm", cmd_eventrm, "remove currently selected event."},
671 {"rm", cmd_eventrm, NULL},
672 {"tempo", cmd_tempo, "show tempo map."},
673 {"length", cmd_length, "show length of the song."},
674 {"version", cmd_version, "show libsmf version."},
675 {"exit", cmd_exit, "exit to shell."},
676 {"quit", cmd_exit, NULL},
677 {"bye", cmd_exit, NULL},
678 {NULL, NULL, NULL}};
680 static int
681 cmd_help(char *notused)
683 struct command_struct *tmp;
685 g_message("Available commands:");
687 for (tmp = commands; tmp->name != NULL; tmp++) {
688 /* Skip commands with no help string. */
689 if (tmp->help == NULL)
690 continue;
691 g_message("%s: %s", tmp->name, tmp->help);
694 return 0;
698 * Removes (in place) all whitespace characters before the first
699 * non-whitespace and all trailing whitespace characters. Replaces
700 * more than one consecutive whitespace characters with one.
702 static void
703 strip_unneeded_whitespace(char *str, int len)
705 char *src, *dest;
706 int skip_white = 1;
708 for (src = str, dest = str; src < dest + len; src++) {
709 if (*src == '\n' || *src == '\0') {
710 *dest = '\0';
711 break;
714 if (isspace(*src)) {
715 if (skip_white)
716 continue;
718 skip_white = 1;
719 } else {
720 skip_white = 0;
723 *dest = *src;
724 dest++;
727 /* Remove trailing whitespace. */
728 len = strlen(dest);
729 if (isspace(dest[len - 1]))
730 dest[len - 1] = '\0';
733 static char *
734 read_command(void)
736 char *buf;
737 int len;
739 #ifdef HAVE_LIBREADLINE
740 buf = readline("smfsh> ");
741 #else
742 buf = malloc(1024);
743 if (buf == NULL) {
744 g_critical("Malloc failed.");
745 return NULL;
748 fprintf(stdout, "smfsh> ");
749 fflush(stdout);
751 buf = fgets(buf, 1024, stdin);
752 #endif
754 if (buf == NULL) {
755 fprintf(stdout, "exit\n");
756 return "exit";
759 strip_unneeded_whitespace(buf, 1024);
761 len = strlen(buf);
763 if (len == 0)
764 return read_command();
766 #ifdef HAVE_LIBREADLINE
767 add_history(buf);
768 #endif
770 return buf;
773 static int
774 execute_command(char *line)
776 char *command, *args;
777 struct command_struct *tmp;
779 args = line;
780 command = strsep(&args, " ");
782 for (tmp = commands; tmp->name != NULL; tmp++) {
783 if (strcmp(tmp->name, command) == 0)
784 return (tmp->function)(args);
787 g_warning("No such command: '%s'. Type 'help' to see available commands.", command);
789 return -1;
792 static void
793 read_and_execute_command(void)
795 int ret;
796 char *command;
798 command = read_command();
800 ret = execute_command(command);
801 if (ret) {
802 g_warning("Command finished with error.");
805 free(command);
808 #ifdef HAVE_LIBREADLINE
810 static char *
811 smfsh_command_generator(const char *text, int state)
813 static struct command_struct *command = commands;
814 char *tmp;
816 if (state == 0)
817 command = commands;
819 while (command->name != NULL) {
820 tmp = command->name;
821 command++;
823 if (strncmp(tmp, text, strlen(text)) == 0)
824 return strdup(tmp);
827 return NULL;
830 static char **
831 smfsh_completion(const char *text, int start, int end)
833 int i;
835 /* Return NULL if "text" is not the first word in the input line. */
836 if (start != 0) {
837 for (i = 0; i < start; i++) {
838 if (!isspace(rl_line_buffer[i]))
839 return NULL;
843 return rl_completion_matches(text, smfsh_command_generator);
846 #endif
848 int main(int argc, char *argv[])
850 if (argc > 2) {
851 usage();
854 g_log_set_default_handler(log_handler, NULL);
856 smf = smf_new();
857 if (smf == NULL) {
858 g_critical("Cannot initialize smf_t.");
859 return -1;
862 if (argc == 2) {
863 last_file_name = argv[1];
864 cmd_load(last_file_name);
865 } else {
866 cmd_trackadd(NULL);
869 #ifdef HAVE_LIBREADLINE
870 rl_readline_name = "smfsh";
871 rl_attempted_completion_function = smfsh_completion;
872 #endif
874 for (;;)
875 read_and_execute_command();
877 return 0;