49#ifdef HAVE_LIBREADLINE
50#include <readline/readline.h>
51#include <readline/history.h>
59#define COMMAND_LENGTH 10
62log_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);
70static int cmd_track(
char *arg);
73cmd_load(
char *file_name)
77 if (file_name == NULL) {
79 g_critical(
"Please specify file name.");
85 file_name = strdup(file_name);
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);
126cmd_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);
159cmd_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);
190cmd_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);
221cmd_tracks(
char *notused)
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);
226 g_message(
"There are no tracks.");
232parse_track_number(
const char *arg)
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.");
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);
256 g_critical(
"There are no tracks.");
272 g_message(
"No track currently selected.");
274 g_message(
"Currently selected is track number %d, containing %d events.",
277 if (
smf->number_of_tracks == 0) {
278 g_message(
"There are no tracks.");
282 num = parse_track_number(arg);
288 g_critical(
"smf_get_track_by_number() failed, track not selected.");
294 g_message(
"Track number %d selected; it contains %d events.",
302cmd_trackadd(
char *notused)
306 g_critical(
"smf_track_new() failed, track not created.");
314 g_message(
"Created new track; track number %d selected.",
selected_track->track_number);
320cmd_trackrm(
char *arg)
322 int num = parse_track_number(arg);
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,
376cmd_events(
char *notused)
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);
403parse_event_number(
const char *arg)
409 g_critical(
"You need to select track first (using 'track <number>').");
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.");
447 g_message(
"No event currently selected.");
453 num = parse_event_number(arg);
459 g_critical(
"smf_get_event_by_number() failed, event not selected.");
463 g_message(
"Event number %d selected.",
selected_event->event_number);
471decode_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.");
524cmd_eventadd(
char *str)
526 int midi_buffer_length;
528 unsigned char *midi_buffer;
529 char *time, *endtime;
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)) {
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.");
584 g_message(
"Event created.");
592 double seconds, type;
593 char *time, *typestr, *end;
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>");
652 g_critical(
"smf_event_new_textual() failed, event not created.");
660 g_message(
"Event created.");
667cmd_eventaddeot(
char *time)
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.");
699cmd_eventrm(
char *number)
701 int num = parse_event_number(number);
711 g_message(
"Event #%d removed.", num);
717cmd_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.",
738cmd_length(
char *notused)
746cmd_version(
char *notused)
754cmd_exit(
char *notused)
756 g_debug(
"Good bye.");
760static int cmd_help(
char *notused);
762static 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},
795cmd_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);
826strip_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
897execute_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);
920read_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
951smfsh_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));
971smfsh_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();
smf_event_t * smf_event_new(void)
Allocates new smf_event_t structure.
smf_event_t * smf_track_get_event_by_number(const smf_track_t *track, int event_number)
smf_track_t * smf_get_track_by_number(const smf_t *smf, int track_number)
int smf_track_add_eot_seconds(smf_track_t *track, double seconds)
void smf_delete(smf_t *smf)
Frees smf and all it's descendant structures.
double smf_get_length_seconds(const smf_t *smf)
smf_track_t * smf_track_new(void)
Allocates new smf_track_t structure.
smf_event_t * smf_track_get_next_event(smf_track_t *track)
Returns next event from the track given and advances next event counter.
void smf_add_track(smf_t *smf, smf_track_t *track)
Appends smf_track_t to smf.
int smf_get_length_pulses(const smf_t *smf)
void smf_track_delete(smf_track_t *track)
Detaches track from its smf and frees it.
int smf_set_format(smf_t *smf, int format)
Sets "Format" field of MThd header to the specified value.
smf_t * smf_new(void)
Allocates new smf_t structure.
void smf_rewind(smf_t *smf)
Rewinds the SMF.
void smf_event_delete(smf_event_t *event)
Detaches event from its track and frees it.
const char * smf_get_version(void)
int smf_set_ppqn(smf_t *smf, int ppqn)
Sets the PPQN ("Division") field of MThd header.
Public interface declaration for libsmf, Standard MIDI File format library.
char * smf_event_decode(const smf_event_t *event) WARN_UNUSED_RESULT
int smf_event_is_metadata(const smf_event_t *event) WARN_UNUSED_RESULT
int smf_save(smf_t *smf, const char *file_name) WARN_UNUSED_RESULT
Writes the contents of SMF to the file given.
struct smf_event_struct smf_event_t
struct smf_track_struct smf_track_t
struct smf_tempo_struct smf_tempo_t
char * smf_decode(const smf_t *smf) WARN_UNUSED_RESULT
smf_event_t * smf_event_new_textual(int type, const char *text)
int smf_event_is_valid(const smf_event_t *event) WARN_UNUSED_RESULT
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.
smf_t * smf_load(const char *file_name) WARN_UNUSED_RESULT
Loads SMF file.
smf_tempo_t * smf_get_tempo_by_number(const smf_t *smf, int number) WARN_UNUSED_RESULT
int main(int argc, char *argv[])
smf_event_t * selected_event
smf_track_t * selected_track
int time_pulses
Time, in pulses, since the start of the song.
int event_number
Number of this event in the track.
unsigned char * midi_buffer
Pointer to the buffer containing MIDI message.
double time_seconds
Time, in seconds, since the start of the song.
int delta_time_pulses
Note that the time fields are invalid, if event is not attached to a track.
int microseconds_per_quarter_note