2 * Copyright (c) 2007, 2008 Edward Tomasz NapieraĆa <trasz@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
36 #ifdef HAVE_LIBREADLINE
37 #include <readline/readline.h>
38 #include <readline/history.h>
41 smf_track_t
*selected_track
= NULL
;
42 smf_event_t
*selected_event
= NULL
;
44 char *last_file_name
= NULL
;
49 fprintf(stderr
, "usage: smfsh [file]\n");
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
);
63 cmd_load(char *file_name
)
65 if (file_name
== NULL
) {
66 if (last_file_name
== NULL
) {
67 g_critical("Please specify file name.");
71 file_name
= last_file_name
;
77 selected_track
= NULL
;
78 selected_event
= NULL
;
80 last_file_name
= strdup(file_name
);
81 smf
= smf_load(file_name
);
83 g_critical("Couldn't load '%s'.", file_name
);
87 g_critical("Cannot initialize smf_t.");
94 g_message("File '%s' loaded.", file_name
);
95 g_message("%s.", smf_decode(smf
));
103 cmd_save(char *file_name
)
107 if (file_name
== NULL
) {
108 if (last_file_name
== NULL
) {
109 g_critical("Please specify file name.");
113 file_name
= last_file_name
;
116 if (file_name
== NULL
) {
117 g_critical("Please specify file name.");
121 last_file_name
= strdup(file_name
);
122 ret
= smf_save(smf
, file_name
);
124 g_critical("Couldn't save '%s'", file_name
);
128 g_message("File '%s' saved.", file_name
);
134 cmd_ppqn(char *new_ppqn
)
139 if (new_ppqn
== NULL
) {
140 g_message("Pulses Per Quarter Note (aka Division) is %d.", smf
->ppqn
);
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.");
149 g_critical("Invalid PPQN, valid values are greater than zero.");
153 if (smf_set_ppqn(smf
, tmp
)) {
154 g_message("smf_set_ppqn failed.");
158 g_message("Pulses Per Quarter Note changed to %d.", smf
->ppqn
);
165 cmd_format(char *new_format
)
170 if (new_format
== NULL
) {
171 g_message("Format is %d.", smf
->format
);
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.");
179 if (tmp
< 0 || tmp
> 2) {
180 g_critical("Invalid format value, valid values are in range 0 - 2, inclusive.");
184 if (smf_set_format(smf
, tmp
)) {
185 g_critical("smf_set_format failed.");
189 g_message("Forma changed to %d.", smf
->format
);
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
);
201 g_message("There are no tracks.");
207 parse_track_number(const char *arg
)
213 if (selected_track
== NULL
) {
214 g_message("No track currently selected and no track number given.");
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.");
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
);
231 g_critical("There are no tracks.");
246 if (selected_track
== NULL
)
247 g_message("No track currently selected.");
249 g_message("Currently selected is track number %d, containing %d events.",
250 selected_track
->track_number
, selected_track
->number_of_events
);
252 if (smf
->number_of_tracks
== 0) {
253 g_message("There are no tracks.");
257 num
= parse_track_number(arg
);
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.");
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
);
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.");
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
);
295 cmd_trackrm(char *arg
)
297 int num
= parse_track_number(arg
);
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
);
314 #define BUFFER_SIZE 1024
317 show_event(smf_event_t
*event
)
320 char *decoded
, *type
;
322 if (smf_event_is_metadata(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.");
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
);
351 cmd_events(char *notused
)
355 if (selected_track
== NULL
) {
356 g_critical("No track selected - please use 'track [number]' command first.");
360 g_message("List of events in track %d follows:", selected_track
->track_number
);
364 while ((event
= smf_track_get_next_event(selected_track
)) != NULL
) {
374 parse_event_number(const char *arg
)
379 if (selected_track
== NULL
) {
380 g_critical("You need to select track first (using 'track <number>').");
385 if (selected_event
== NULL
) {
386 g_message("No event currently selected and no event number given.");
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.");
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
);
403 g_critical("There are no events in currently selected track.");
418 if (selected_event
== NULL
) {
419 g_message("No event currently selected.");
421 g_message("Currently selected is event %d, track %d.", selected_event
->event_number
, selected_track
->track_number
);
422 show_event(selected_event
);
425 num
= parse_event_number(arg
);
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.");
435 g_message("Event number %d selected.", selected_event
->event_number
);
436 show_event(selected_event
);
443 decode_hex(char *str
, unsigned char **buffer
, int *length
)
445 int i
, value
, midi_buffer_length
;
447 unsigned char *midi_buffer
= NULL
;
450 if ((strlen(str
) % 2) != 0) {
451 g_critical("Hex value should have even number of characters, you know.");
455 midi_buffer_length
= strlen(str
) / 2;
456 midi_buffer
= malloc(midi_buffer_length
);
457 if (midi_buffer
== NULL
) {
458 g_critical("malloc() failed.");
462 for (i
= 0; i
< midi_buffer_length
; i
++) {
464 buf
[1] = str
[i
* 2 + 1];
466 value
= strtoll(buf
, &end
, 16);
468 if (end
- buf
!= 2) {
469 g_critical("Garbage characters detected after hex.");
473 midi_buffer
[i
] = value
;
476 *buffer
= midi_buffer
;
477 *length
= midi_buffer_length
;
482 if (midi_buffer
!= NULL
)
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.");
496 cmd_eventadd(char *str
)
498 int midi_buffer_length
;
500 unsigned char *midi_buffer
;
501 char *time
, *endtime
;
503 if (selected_track
== NULL
) {
504 g_critical("Please select a track first.");
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.");
521 /* Called with one parameter? */
527 if (decode_hex(str
, &midi_buffer
, &midi_buffer_length
)) {
532 selected_event
= smf_event_new();
533 if (selected_event
== NULL
) {
534 g_critical("smf_event_new() failed, event not created.");
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
;
548 smf_track_add_event_seconds(selected_track
, selected_event
, seconds
);
550 g_message("Event created.");
556 cmd_eventaddeot(char *time
)
561 if (selected_track
== NULL
) {
562 g_critical("Please select a track first.");
567 g_critical("Please specify the time, in seconds.");
571 seconds
= strtod(time
, &end
);
572 if (end
- time
!= strlen(time
)) {
573 g_critical("Time is supposed to be a number, without trailing characters.");
577 if (smf_track_add_eot_seconds(selected_track
, seconds
)) {
578 g_critical("smf_track_add_eot() failed.");
582 g_message("Event created.");
588 cmd_eventrm(char *number
)
590 int num
= parse_event_number(number
);
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
);
606 cmd_tempo(char *notused
)
612 tempo
= smf_get_tempo_by_number(smf
, i
);
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
);
627 cmd_length(char *notused
)
629 g_message("Length: %d pulses, %f seconds.", smf_get_length_pulses(smf
), smf_get_length_seconds(smf
));
635 cmd_version(char *notused
)
637 g_message("libsmf version %s.", smf_get_version());
643 cmd_exit(char *notused
)
645 g_debug("Good bye.");
649 static int cmd_help(char *notused
);
651 struct command_struct
{
653 int (*function
)(char *command
);
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
},
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
)
691 g_message("%s: %s", tmp
->name
, tmp
->help
);
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.
703 strip_unneeded_whitespace(char *str
, int len
)
708 for (src
= str
, dest
= str
; src
< dest
+ len
; src
++) {
709 if (*src
== '\n' || *src
== '\0') {
727 /* Remove trailing whitespace. */
729 if (isspace(dest
[len
- 1]))
730 dest
[len
- 1] = '\0';
739 #ifdef HAVE_LIBREADLINE
740 buf
= readline("smfsh> ");
744 g_critical("Malloc failed.");
748 fprintf(stdout
, "smfsh> ");
751 buf
= fgets(buf
, 1024, stdin
);
755 fprintf(stdout
, "exit\n");
759 strip_unneeded_whitespace(buf
, 1024);
764 return read_command();
766 #ifdef HAVE_LIBREADLINE
774 execute_command(char *line
)
776 char *command
, *args
;
777 struct command_struct
*tmp
;
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
);
793 read_and_execute_command(void)
798 command
= read_command();
800 ret
= execute_command(command
);
802 g_warning("Command finished with error.");
808 #ifdef HAVE_LIBREADLINE
811 smfsh_command_generator(const char *text
, int state
)
813 static struct command_struct
*command
= commands
;
819 while (command
->name
!= NULL
) {
823 if (strncmp(tmp
, text
, strlen(text
)) == 0)
831 smfsh_completion(const char *text
, int start
, int end
)
835 /* Return NULL if "text" is not the first word in the input line. */
837 for (i
= 0; i
< start
; i
++) {
838 if (!isspace(rl_line_buffer
[i
]))
843 return rl_completion_matches(text
, smfsh_command_generator
);
848 int main(int argc
, char *argv
[])
854 g_log_set_default_handler(log_handler
, NULL
);
858 g_critical("Cannot initialize smf_t.");
863 last_file_name
= argv
[1];
864 cmd_load(last_file_name
);
869 #ifdef HAVE_LIBREADLINE
870 rl_readline_name
= "smfsh";
871 rl_attempted_completion_function
= smfsh_completion
;
875 read_and_execute_command();