49 #ifdef HAVE_LIBREADLINE
50 #include <readline/readline.h>
51 #include <readline/history.h>
59 #define COMMAND_LENGTH 10
62 log_handler(
const gchar *log_domain, GLogLevelFlags log_level,
const gchar *message, gpointer notused)
64 if (strcmp(log_domain,
"smfsh") == 0)
65 fprintf(stderr,
"%s\n", message);
67 fprintf(stderr,
"%s: %s\n", log_domain, message);
70 static int cmd_track(
char *arg);
73 cmd_load(
char *file_name)
77 if (file_name == NULL) {
79 g_critical(
"Please specify file name.");
85 file_name = strdup(file_name);
88 selected_track = NULL;
89 selected_event = NULL;
102 g_critical(
"Couldn't load '%s'.", file_name);
106 g_critical(
"Cannot initialize smf_t.");
113 g_message(
"File '%s' loaded.", file_name);
115 g_message(
"%s.", decoded);
126 cmd_save(
char *file_name)
130 if (file_name == NULL) {
132 g_critical(
"Please specify file name.");
138 file_name = strdup(file_name);
147 g_critical(
"Couldn't save '%s'", file_name);
151 g_message(
"File '%s' saved.", file_name);
159 cmd_ppqn(
char *new_ppqn)
164 if (new_ppqn == NULL) {
165 g_message(
"Pulses Per Quarter Note (aka Division) is %d.", smf->
ppqn);
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.");
174 g_critical(
"Invalid PPQN, valid values are greater than zero.");
179 g_message(
"smf_set_ppqn failed.");
183 g_message(
"Pulses Per Quarter Note changed to %d.", smf->
ppqn);
190 cmd_format(
char *new_format)
195 if (new_format == NULL) {
196 g_message(
"Format is %d.", smf->
format);
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.");
204 if (tmp < 0 || tmp > 2) {
205 g_critical(
"Invalid format value, valid values are in range 0 - 2, inclusive.");
210 g_critical(
"smf_set_format failed.");
214 g_message(
"Forma changed to %d.", smf->
format);
221 cmd_tracks(
char *notused)
226 g_message(
"There are no tracks.");
232 parse_track_number(
const char *arg)
238 if (selected_track == NULL) {
239 g_message(
"No track currently selected and no track number given.");
246 num = strtol(arg, &end, 10);
247 if (end - arg != strlen(arg)) {
248 g_critical(
"Invalid track number, garbage characters after the number.");
254 g_critical(
"Invalid track number specified; valid choices are 1 - %d.", smf->
number_of_tracks);
256 g_critical(
"There are no tracks.");
271 if (selected_track == NULL)
272 g_message(
"No track currently selected.");
274 g_message(
"Currently selected is track number %d, containing %d events.",
278 g_message(
"There are no tracks.");
282 num = parse_track_number(arg);
287 if (selected_track == NULL) {
288 g_critical(
"smf_get_track_by_number() failed, track not selected.");
292 selected_event = NULL;
294 g_message(
"Track number %d selected; it contains %d events.",
302 cmd_trackadd(
char *notused)
305 if (selected_track == NULL) {
306 g_critical(
"smf_track_new() failed, track not created.");
312 selected_event = NULL;
314 g_message(
"Created new track; track number %d selected.", selected_track->
track_number);
320 cmd_trackrm(
char *arg)
322 int num = parse_track_number(arg);
327 if (selected_track != NULL && num == selected_track->
track_number) {
328 selected_track = NULL;
329 selected_event = NULL;
334 g_message(
"Track %d removed.", num);
339 #define BUFFER_SIZE 1024
345 char *decoded, *type;
354 if (decoded == NULL) {
355 decoded = malloc(BUFFER_SIZE);
356 if (decoded == NULL) {
357 g_critical(
"show_event: malloc failed.");
361 off += snprintf(decoded + off, BUFFER_SIZE - off,
"Unknown event:");
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]);
367 g_message(
"%d: %s: %s, %f seconds, %d pulses, %d delta pulses", event->
event_number, type, decoded,
376 cmd_events(
char *notused)
380 if (selected_track == NULL) {
381 g_critical(
"No track selected - please use 'track <number>' command first.");
386 g_message(
"Selected track is empty.");
390 g_message(
"List of events in track %d follows:", selected_track->
track_number);
403 parse_event_number(
const char *arg)
408 if (selected_track == NULL) {
409 g_critical(
"You need to select track first (using 'track <number>').");
414 if (selected_event == NULL) {
415 g_message(
"No event currently selected and no event number given.");
422 num = strtol(arg, &end, 10);
423 if (end - arg != strlen(arg)) {
424 g_critical(
"Invalid event number, garbage characters after the number.");
430 g_critical(
"Invalid event number specified; valid choices are 1 - %d.", selected_track->
number_of_events);
432 g_critical(
"There are no events in currently selected track.");
446 if (selected_event == NULL) {
447 g_message(
"No event currently selected.");
450 show_event(selected_event);
453 num = parse_event_number(arg);
458 if (selected_event == NULL) {
459 g_critical(
"smf_get_event_by_number() failed, event not selected.");
463 g_message(
"Event number %d selected.", selected_event->
event_number);
464 show_event(selected_event);
471 decode_hex(
char *str,
unsigned char **buffer,
int *
length)
473 int i, value, midi_buffer_length;
475 unsigned char *midi_buffer = NULL;
478 if ((strlen(str) % 2) != 0) {
479 g_critical(
"Hex value should have even number of characters, you know.");
483 midi_buffer_length = strlen(str) / 2;
484 midi_buffer = malloc(midi_buffer_length);
485 if (midi_buffer == NULL) {
486 g_critical(
"malloc() failed.");
490 for (i = 0; i < midi_buffer_length; i++) {
492 buf[1] = str[i * 2 + 1];
494 value = strtoll(buf, &end, 16);
496 if (end - buf != 2) {
497 g_critical(
"Garbage characters detected after hex.");
501 midi_buffer[i] = value;
504 *buffer = midi_buffer;
505 *length = midi_buffer_length;
510 if (midi_buffer != NULL)
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.");
524 cmd_eventadd(
char *str)
526 int midi_buffer_length;
528 unsigned char *midi_buffer;
529 char *time, *endtime;
531 if (selected_track == NULL) {
532 g_critical(
"Please select a track first, using 'track <number>' command.");
543 str = strchr(str,
' ');
549 seconds = strtod(time, &endtime);
550 if (endtime - time != strlen(time)) {
551 g_critical(
"Time is supposed to be a number, without trailing characters.");
561 if (decode_hex(str, &midi_buffer, &midi_buffer_length)) {
567 if (selected_event == NULL) {
568 g_critical(
"smf_event_new() failed, event not created.");
576 g_critical(
"Event is invalid from the MIDI specification point of view, not created.");
578 selected_event = NULL;
584 g_message(
"Event created.");
592 double seconds, type;
593 char *time, *typestr, *end;
595 if (selected_track == NULL) {
596 g_critical(
"Please select a track first, using 'track <number>' command.");
601 g_critical(
"Usage: text <time-in-seconds> <event-type> <text-itself>");
607 str = strchr(str,
' ');
613 seconds = strtod(time, &end);
614 if (end - time != strlen(time)) {
615 g_critical(
"Time is supposed to be a number, without trailing characters.");
621 g_critical(
"Usage: text <time-in-seconds> <event-type> <text-itself>");
627 str = strchr(str,
' ');
633 type = strtod(typestr, &end);
634 if (end - typestr != strlen(typestr)) {
635 g_critical(
"Type is supposed to be a number, without trailing characters.");
639 if (type < 1 || type > 9) {
640 g_critical(
"Valid values for type are 1 - 9, inclusive.");
646 g_critical(
"Usage: text <time-in-seconds> <event-type> <text-itself>");
651 if (selected_event == NULL) {
652 g_critical(
"smf_event_new_textual() failed, event not created.");
660 g_message(
"Event created.");
667 cmd_eventaddeot(
char *time)
672 if (selected_track == NULL) {
673 g_critical(
"Please select a track first, using 'track <number>' command.");
678 g_critical(
"Please specify the time, in seconds.");
682 seconds = strtod(time, &end);
683 if (end - time != strlen(time)) {
684 g_critical(
"Time is supposed to be a number, without trailing characters.");
689 g_critical(
"smf_track_add_eot() failed.");
693 g_message(
"Event created.");
699 cmd_eventrm(
char *number)
701 int num = parse_event_number(number);
706 if (selected_event != NULL && num == selected_event->
event_number)
707 selected_event = NULL;
711 g_message(
"Event #%d removed.", num);
717 cmd_tempo(
char *notused)
727 g_message(
"Tempo #%d: Starts at %d pulses, %f seconds, setting %d microseconds per quarter note, %.2f BPM.",
730 g_message(
"Time signature: %d/%d, %d clocks per click, %d 32nd notes per quarter note.",
738 cmd_length(
char *notused)
746 cmd_version(
char *notused)
754 cmd_exit(
char *notused)
756 g_debug(
"Good bye.");
760 static int cmd_help(
char *notused);
762 static struct command_struct {
764 int (*
function)(
char *command);
766 } commands[] = {{
"help", cmd_help,
"Show this help."},
767 {
"?", cmd_help, NULL},
768 {
"load", cmd_load,
"Load named file."},
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},
795 cmd_help(
char *notused)
797 int i, padding_length;
798 char padding[COMMAND_LENGTH + 1];
799 struct command_struct *tmp;
801 g_message(
"Available commands:");
803 for (tmp = commands; tmp->name != NULL; tmp++) {
805 if (tmp->help == NULL)
808 padding_length = COMMAND_LENGTH - strlen(tmp->name);
809 assert(padding_length >= 0);
810 for (i = 0; i < padding_length; i++)
814 g_message(
"%s:%s%s", tmp->name, padding, tmp->help);
826 strip_unneeded_whitespace(
char *str,
int len)
831 for (src = str, dest = str; src < dest + len; src++) {
832 if (*src ==
'\n' || *src ==
'\0') {
852 if (isspace(dest[len - 1]))
853 dest[len - 1] =
'\0';
862 #ifdef HAVE_LIBREADLINE
863 buf = readline(
"smfsh> ");
867 g_critical(
"Malloc failed.");
871 fprintf(stdout,
"smfsh> ");
874 buf = fgets(buf, 1024, stdin);
878 fprintf(stdout,
"exit\n");
879 return (strdup(
"exit"));
882 strip_unneeded_whitespace(buf, 1024);
887 return (read_command());
889 #ifdef HAVE_LIBREADLINE
897 execute_command(
char *line)
899 char *command, *args;
900 struct command_struct *tmp;
903 args = strchr(line,
' ');
909 for (tmp = commands; tmp->name != NULL; tmp++) {
910 if (strcmp(tmp->name, command) == 0)
911 return ((tmp->function)(args));
914 g_warning(
"No such command: '%s'. Type 'help' to see available commands.", command);
920 read_and_execute_command(
void)
923 char *command_line, *command, *next_command;
925 command = command_line = read_command();
928 next_command = strchr(command,
';');
929 if (next_command != NULL) {
930 *next_command =
'\0';
934 strip_unneeded_whitespace(command, 1024);
935 if (strlen(command) > 0) {
936 ret = execute_command(command);
938 g_warning(
"Command finished with error.");
941 command = next_command;
948 #ifdef HAVE_LIBREADLINE
951 smfsh_command_generator(
const char *text,
int state)
953 static struct command_struct *command = commands;
959 while (command->name != NULL) {
963 if (strncmp(tmp, text, strlen(text)) == 0)
964 return (strdup(tmp));
971 smfsh_completion(
const char *text,
int start,
int end)
977 for (i = 0; i < start; i++) {
978 if (!isspace(rl_line_buffer[i]))
983 return (rl_completion_matches(text, smfsh_command_generator));
991 fprintf(stderr,
"usage: smfsh [-V | file]\n");
1001 while ((ch = getopt(argc, argv,
"V")) != -1) {
1016 g_log_set_default_handler(log_handler, NULL);
1020 g_critical(
"Cannot initialize smf_t.");
1029 #ifdef HAVE_LIBREADLINE
1030 rl_readline_name =
"smfsh";
1031 rl_attempted_completion_function = smfsh_completion;
1035 read_and_execute_command();
void smf_rewind(smf_t *smf)
Rewinds the SMF.
smf_event_t * smf_track_get_event_by_number(const smf_track_t *track, int event_number)
double smf_get_length_seconds(const smf_t *smf)
int smf_event_is_valid(const smf_event_t *event) WARN_UNUSED_RESULT
int smf_set_ppqn(smf_t *smf, int ppqn)
Sets the PPQN ("Division") field of MThd header.
int smf_save(smf_t *smf, const char *file_name) WARN_UNUSED_RESULT
Writes the contents of SMF to the file given.
unsigned char * midi_buffer
Pointer to the buffer containing MIDI message.
Represents a "song", that is, collection of one or more tracks.
smf_t * smf_load(const char *file_name) WARN_UNUSED_RESULT
Loads SMF file.
Represents a single track.
void smf_add_track(smf_t *smf, smf_track_t *track)
Appends smf_track_t to smf.
smf_event_t * selected_event
int ppqn
These fields are extracted from "division" field of MThd header.
Describes a single tempo or time signature change.
int smf_event_is_metadata(const smf_event_t *event) WARN_UNUSED_RESULT
smf_tempo_t * smf_get_tempo_by_number(const smf_t *smf, int number) WARN_UNUSED_RESULT
void smf_event_delete(smf_event_t *event)
Detaches event from its track and frees it.
char * smf_event_decode(const smf_event_t *event) WARN_UNUSED_RESULT
int event_number
Number of this event in the track.
smf_track_t * selected_track
int time_pulses
Time, in pulses, since the start of the song.
void smf_delete(smf_t *smf)
Frees smf and all it's descendant structures.
const char * smf_get_version(void)
smf_track_t * smf_track_new(void)
Allocates new smf_track_t structure.
int midi_buffer_length
Length of the MIDI message in the buffer, in bytes.
smf_event_t * smf_event_new(void)
Allocates new smf_event_t structure.
Public interface declaration for libsmf, Standard MIDI File format library.
int smf_get_length_pulses(const smf_t *smf)
smf_event_t * smf_track_get_next_event(smf_track_t *track)
Returns next event from the track given and advances next event counter.
smf_track_t * smf_get_track_by_number(const smf_t *smf, int track_number)
double time_seconds
Time, in seconds, since the start of the song.
int microseconds_per_quarter_note
char * smf_decode(const smf_t *smf) WARN_UNUSED_RESULT
smf_event_t * smf_event_new_textual(int type, const char *text)
Represents a single MIDI event or metaevent.
void smf_track_add_event_seconds(smf_track_t *track, smf_event_t *event, double seconds)
Adds event to the track at the time "seconds" seconds from the start of song.
void smf_track_delete(smf_track_t *track)
Detaches track from its smf and frees it.
int smf_track_add_eot_seconds(smf_track_t *track, double seconds)
smf_t * smf_new(void)
Allocates new smf_t structure.
int delta_time_pulses
Note that the time fields are invalid, if event is not attached to a track.
int smf_set_format(smf_t *smf, int format)
Sets "Format" field of MThd header to the specified value.
int main(int argc, char *argv[])