nuclear@1: /*- nuclear@1: * Copyright (c) 2007, 2008 Edward Tomasz NapieraƂa nuclear@1: * All rights reserved. nuclear@1: * nuclear@1: * Redistribution and use in source and binary forms, with or without nuclear@1: * modification, are permitted provided that the following conditions nuclear@1: * are met: nuclear@1: * 1. Redistributions of source code must retain the above copyright nuclear@1: * notice, this list of conditions and the following disclaimer. nuclear@1: * 2. Redistributions in binary form must reproduce the above copyright nuclear@1: * notice, this list of conditions and the following disclaimer in the nuclear@1: * documentation and/or other materials provided with the distribution. nuclear@1: * nuclear@1: * ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE nuclear@1: * AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, nuclear@1: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY nuclear@1: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL nuclear@1: * THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, nuclear@1: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED nuclear@1: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, nuclear@1: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY nuclear@1: * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING nuclear@1: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS nuclear@1: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nuclear@1: * nuclear@1: */ nuclear@1: nuclear@1: /** nuclear@1: * \file nuclear@1: * nuclear@1: * "SMF shell", command line utility. nuclear@1: */ nuclear@1: nuclear@1: #include nuclear@1: #include nuclear@1: #include nuclear@1: #ifdef __MINGW32__ nuclear@1: #define EX_OK 0 nuclear@1: #define EX_USAGE 64 nuclear@1: #else /* ! __MINGW32__ */ nuclear@1: #include nuclear@1: #endif /* ! __MINGW32__ */ nuclear@1: #include nuclear@1: #include nuclear@1: #include nuclear@1: #include "smf.h" nuclear@1: #include "fake_glib.h" nuclear@1: nuclear@1: #ifdef HAVE_LIBREADLINE nuclear@1: #include nuclear@1: #include nuclear@1: #endif nuclear@1: nuclear@1: smf_track_t *selected_track = NULL; nuclear@1: smf_event_t *selected_event = NULL; nuclear@1: smf_t *smf = NULL; nuclear@1: char *last_file_name = NULL; nuclear@1: nuclear@1: #define COMMAND_LENGTH 10 nuclear@1: nuclear@1: /*static void nuclear@1: log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer notused) nuclear@1: { nuclear@1: if (strcmp(log_domain, "smfsh") == 0) nuclear@1: fprintf(stderr, "%s\n", message); nuclear@1: else nuclear@1: fprintf(stderr, "%s: %s\n", log_domain, message); nuclear@1: }*/ nuclear@1: nuclear@1: static int cmd_track(char *arg); nuclear@1: nuclear@1: static int nuclear@1: cmd_load(char *file_name) nuclear@1: { nuclear@1: char *decoded; nuclear@1: nuclear@1: if (file_name == NULL) { nuclear@1: if (last_file_name == NULL) { nuclear@1: fg_critical("Please specify file name."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: file_name = strdup(last_file_name); nuclear@1: } else { nuclear@1: file_name = strdup(file_name); nuclear@1: } nuclear@1: nuclear@1: selected_track = NULL; nuclear@1: selected_event = NULL; nuclear@1: nuclear@1: if (smf != NULL) { nuclear@1: smf_delete(smf); nuclear@1: smf = NULL; nuclear@1: } nuclear@1: nuclear@1: if (last_file_name != NULL) nuclear@1: free(last_file_name); nuclear@1: last_file_name = strdup(file_name); nuclear@1: nuclear@1: smf = smf_load(file_name); nuclear@1: if (smf == NULL) { nuclear@1: fg_critical("Couldn't load '%s'.", file_name); nuclear@1: nuclear@1: smf = smf_new(); nuclear@1: if (smf == NULL) { nuclear@1: fg_critical("Cannot initialize smf_t."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: return (-2); nuclear@1: } nuclear@1: nuclear@1: fg_message("File '%s' loaded.", file_name); nuclear@1: decoded = smf_decode(smf); nuclear@1: fg_message("%s.", decoded); nuclear@1: free(decoded); nuclear@1: nuclear@1: cmd_track("1"); nuclear@1: nuclear@1: free(file_name); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_save(char *file_name) nuclear@1: { nuclear@1: int ret; nuclear@1: nuclear@1: if (file_name == NULL) { nuclear@1: if (last_file_name == NULL) { nuclear@1: fg_critical("Please specify file name."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: file_name = strdup(last_file_name); nuclear@1: } else { nuclear@1: file_name = strdup(file_name); nuclear@1: } nuclear@1: nuclear@1: if (last_file_name != NULL) nuclear@1: free(last_file_name); nuclear@1: last_file_name = strdup(file_name); nuclear@1: nuclear@1: ret = smf_save(smf, file_name); nuclear@1: if (ret) { nuclear@1: fg_critical("Couldn't save '%s'", file_name); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: fg_message("File '%s' saved.", file_name); nuclear@1: nuclear@1: free(file_name); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_ppqn(char *new_ppqn) nuclear@1: { nuclear@1: int tmp; nuclear@1: char *end; nuclear@1: nuclear@1: if (new_ppqn == NULL) { nuclear@1: fg_message("Pulses Per Quarter Note (aka Division) is %d.", smf->ppqn); nuclear@1: } else { nuclear@1: tmp = strtol(new_ppqn, &end, 10); nuclear@1: if (end - new_ppqn != strlen(new_ppqn)) { nuclear@1: fg_critical("Invalid PPQN, garbage characters after the number."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: if (tmp <= 0) { nuclear@1: fg_critical("Invalid PPQN, valid values are greater than zero."); nuclear@1: return (-2); nuclear@1: } nuclear@1: nuclear@1: if (smf_set_ppqn(smf, tmp)) { nuclear@1: fg_message("smf_set_ppqn failed."); nuclear@1: return (-3); nuclear@1: } nuclear@1: nuclear@1: fg_message("Pulses Per Quarter Note changed to %d.", smf->ppqn); nuclear@1: } nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_format(char *new_format) nuclear@1: { nuclear@1: int tmp; nuclear@1: char *end; nuclear@1: nuclear@1: if (new_format == NULL) { nuclear@1: fg_message("Format is %d.", smf->format); nuclear@1: } else { nuclear@1: tmp = strtol(new_format, &end, 10); nuclear@1: if (end - new_format != strlen(new_format)) { nuclear@1: fg_critical("Invalid format value, garbage characters after the number."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: if (tmp < 0 || tmp > 2) { nuclear@1: fg_critical("Invalid format value, valid values are in range 0 - 2, inclusive."); nuclear@1: return (-2); nuclear@1: } nuclear@1: nuclear@1: if (smf_set_format(smf, tmp)) { nuclear@1: fg_critical("smf_set_format failed."); nuclear@1: return (-3); nuclear@1: } nuclear@1: nuclear@1: fg_message("Forma changed to %d.", smf->format); nuclear@1: } nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_tracks(char *notused) nuclear@1: { nuclear@1: if (smf->number_of_tracks > 0) nuclear@1: fg_message("There are %d tracks, numbered from 1 to %d.", smf->number_of_tracks, smf->number_of_tracks); nuclear@1: else nuclear@1: fg_message("There are no tracks."); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: parse_track_number(const char *arg) nuclear@1: { nuclear@1: int num; nuclear@1: char *end; nuclear@1: nuclear@1: if (arg == NULL) { nuclear@1: if (selected_track == NULL) { nuclear@1: fg_message("No track currently selected and no track number given."); nuclear@1: return (-1); nuclear@1: } else { nuclear@1: return (selected_track->track_number); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: num = strtol(arg, &end, 10); nuclear@1: if (end - arg != strlen(arg)) { nuclear@1: fg_critical("Invalid track number, garbage characters after the number."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: if (num < 1 || num > smf->number_of_tracks) { nuclear@1: if (smf->number_of_tracks > 0) { nuclear@1: fg_critical("Invalid track number specified; valid choices are 1 - %d.", smf->number_of_tracks); nuclear@1: } else { nuclear@1: fg_critical("There are no tracks."); nuclear@1: } nuclear@1: nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: return (num); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_track(char *arg) nuclear@1: { nuclear@1: int num; nuclear@1: nuclear@1: if (arg == NULL) { nuclear@1: if (selected_track == NULL) nuclear@1: fg_message("No track currently selected."); nuclear@1: else nuclear@1: fg_message("Currently selected is track number %d, containing %d events.", nuclear@1: selected_track->track_number, selected_track->number_of_events); nuclear@1: } else { nuclear@1: if (smf->number_of_tracks == 0) { nuclear@1: fg_message("There are no tracks."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: num = parse_track_number(arg); nuclear@1: if (num < 0) nuclear@1: return (-1); nuclear@1: nuclear@1: selected_track = smf_get_track_by_number(smf, num); nuclear@1: if (selected_track == NULL) { nuclear@1: fg_critical("smf_get_track_by_number() failed, track not selected."); nuclear@1: return (-3); nuclear@1: } nuclear@1: nuclear@1: selected_event = NULL; nuclear@1: nuclear@1: fg_message("Track number %d selected; it contains %d events.", nuclear@1: selected_track->track_number, selected_track->number_of_events); nuclear@1: } nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_trackadd(char *notused) nuclear@1: { nuclear@1: selected_track = smf_track_new(); nuclear@1: if (selected_track == NULL) { nuclear@1: fg_critical("smf_track_new() failed, track not created."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: smf_add_track(smf, selected_track); nuclear@1: nuclear@1: selected_event = NULL; nuclear@1: nuclear@1: fg_message("Created new track; track number %d selected.", selected_track->track_number); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_trackrm(char *arg) nuclear@1: { nuclear@1: int num = parse_track_number(arg); nuclear@1: nuclear@1: if (num < 0) nuclear@1: return (-1); nuclear@1: nuclear@1: if (selected_track != NULL && num == selected_track->track_number) { nuclear@1: selected_track = NULL; nuclear@1: selected_event = NULL; nuclear@1: } nuclear@1: nuclear@1: smf_track_delete(smf_get_track_by_number(smf, num)); nuclear@1: nuclear@1: fg_message("Track %d removed.", num); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: #define BUFFER_SIZE 1024 nuclear@1: nuclear@1: static int nuclear@1: show_event(smf_event_t *event) nuclear@1: { nuclear@1: int off = 0, i; nuclear@1: char *decoded, *type; nuclear@1: nuclear@1: if (smf_event_is_metadata(event)) nuclear@1: type = "Metadata"; nuclear@1: else nuclear@1: type = "Event"; nuclear@1: nuclear@1: decoded = smf_event_decode(event); nuclear@1: nuclear@1: if (decoded == NULL) { nuclear@1: decoded = malloc(BUFFER_SIZE); nuclear@1: if (decoded == NULL) { nuclear@1: fg_critical("show_event: malloc failed."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: off += snprintf(decoded + off, BUFFER_SIZE - off, "Unknown event:"); nuclear@1: nuclear@1: for (i = 0; i < event->midi_buffer_length && i < 5; i++) nuclear@1: off += snprintf(decoded + off, BUFFER_SIZE - off, " 0x%x", event->midi_buffer[i]); nuclear@1: } nuclear@1: nuclear@1: fg_message("%d: %s: %s, %f seconds, %d pulses, %d delta pulses", event->event_number, type, decoded, nuclear@1: event->time_seconds, event->time_pulses, event->delta_time_pulses); nuclear@1: nuclear@1: free(decoded); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_events(char *notused) nuclear@1: { nuclear@1: smf_event_t *event; nuclear@1: nuclear@1: if (selected_track == NULL) { nuclear@1: fg_critical("No track selected - please use 'track ' command first."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: if (selected_track->number_of_events == 0) { nuclear@1: fg_message("Selected track is empty."); nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: fg_message("List of events in track %d follows:", selected_track->track_number); nuclear@1: nuclear@1: smf_rewind(smf); nuclear@1: nuclear@1: while ((event = smf_track_get_next_event(selected_track)) != NULL) nuclear@1: show_event(event); nuclear@1: nuclear@1: smf_rewind(smf); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: parse_event_number(const char *arg) nuclear@1: { nuclear@1: int num; nuclear@1: char *end; nuclear@1: nuclear@1: if (selected_track == NULL) { nuclear@1: fg_critical("You need to select track first (using 'track ')."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: if (arg == NULL) { nuclear@1: if (selected_event == NULL) { nuclear@1: fg_message("No event currently selected and no event number given."); nuclear@1: return (-1); nuclear@1: } else { nuclear@1: return (selected_event->event_number); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: num = strtol(arg, &end, 10); nuclear@1: if (end - arg != strlen(arg)) { nuclear@1: fg_critical("Invalid event number, garbage characters after the number."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: if (num < 1 || num > selected_track->number_of_events) { nuclear@1: if (selected_track->number_of_events > 0) nuclear@1: fg_critical("Invalid event number specified; valid choices are 1 - %d.", selected_track->number_of_events); nuclear@1: else nuclear@1: fg_critical("There are no events in currently selected track."); nuclear@1: nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: return (num); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_event(char *arg) nuclear@1: { nuclear@1: int num; nuclear@1: nuclear@1: if (arg == NULL) { nuclear@1: if (selected_event == NULL) { nuclear@1: fg_message("No event currently selected."); nuclear@1: } else { nuclear@1: fg_message("Currently selected is event %d, track %d.", selected_event->event_number, selected_track->track_number); nuclear@1: show_event(selected_event); nuclear@1: } nuclear@1: } else { nuclear@1: num = parse_event_number(arg); nuclear@1: if (num < 0) nuclear@1: return (-1); nuclear@1: nuclear@1: selected_event = smf_track_get_event_by_number(selected_track, num); nuclear@1: if (selected_event == NULL) { nuclear@1: fg_critical("smf_get_event_by_number() failed, event not selected."); nuclear@1: return (-2); nuclear@1: } nuclear@1: nuclear@1: fg_message("Event number %d selected.", selected_event->event_number); nuclear@1: show_event(selected_event); nuclear@1: } nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: decode_hex(char *str, unsigned char **buffer, int *length) nuclear@1: { nuclear@1: int i, value, midi_buffer_length; nuclear@1: char buf[3]; nuclear@1: unsigned char *midi_buffer = NULL; nuclear@1: char *end = NULL; nuclear@1: nuclear@1: if ((strlen(str) % 2) != 0) { nuclear@1: fg_critical("Hex value should have even number of characters, you know."); nuclear@1: goto error; nuclear@1: } nuclear@1: nuclear@1: midi_buffer_length = strlen(str) / 2; nuclear@1: midi_buffer = malloc(midi_buffer_length); nuclear@1: if (midi_buffer == NULL) { nuclear@1: fg_critical("malloc() failed."); nuclear@1: goto error; nuclear@1: } nuclear@1: nuclear@1: for (i = 0; i < midi_buffer_length; i++) { nuclear@1: buf[0] = str[i * 2]; nuclear@1: buf[1] = str[i * 2 + 1]; nuclear@1: buf[2] = '\0'; nuclear@1: value = strtoll(buf, &end, 16); nuclear@1: nuclear@1: if (end - buf != 2) { nuclear@1: fg_critical("Garbage characters detected after hex."); nuclear@1: goto error; nuclear@1: } nuclear@1: nuclear@1: midi_buffer[i] = value; nuclear@1: } nuclear@1: nuclear@1: *buffer = midi_buffer; nuclear@1: *length = midi_buffer_length; nuclear@1: nuclear@1: return (0); nuclear@1: nuclear@1: error: nuclear@1: if (midi_buffer != NULL) nuclear@1: free(midi_buffer); nuclear@1: nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: static void nuclear@1: eventadd_usage(void) nuclear@1: { nuclear@1: fg_message("Usage: add - for example, 'add 1 903C7F' will add"); nuclear@1: fg_message("Note On event, note C4, velocity 127, channel 1, one second from the start of song, channel 1."); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_eventadd(char *str) nuclear@1: { nuclear@1: int midi_buffer_length; nuclear@1: double seconds; nuclear@1: unsigned char *midi_buffer; nuclear@1: char *time, *endtime; nuclear@1: nuclear@1: if (selected_track == NULL) { nuclear@1: fg_critical("Please select a track first, using 'track ' command."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: if (str == NULL) { nuclear@1: eventadd_usage(); nuclear@1: return (-2); nuclear@1: } nuclear@1: nuclear@1: /* Extract the time. Don't use strsep(3), it doesn't work on SunOS. */ nuclear@1: time = str; nuclear@1: str = strchr(str, ' '); nuclear@1: if (str != NULL) { nuclear@1: *str = '\0'; nuclear@1: str++; nuclear@1: } nuclear@1: nuclear@1: seconds = strtod(time, &endtime); nuclear@1: if (endtime - time != strlen(time)) { nuclear@1: fg_critical("Time is supposed to be a number, without trailing characters."); nuclear@1: return (-3); nuclear@1: } nuclear@1: nuclear@1: /* Called with one parameter? */ nuclear@1: if (str == NULL) { nuclear@1: eventadd_usage(); nuclear@1: return (-4); nuclear@1: } nuclear@1: nuclear@1: if (decode_hex(str, &midi_buffer, &midi_buffer_length)) { nuclear@1: eventadd_usage(); nuclear@1: return (-5); nuclear@1: } nuclear@1: nuclear@1: selected_event = smf_event_new(); nuclear@1: if (selected_event == NULL) { nuclear@1: fg_critical("smf_event_new() failed, event not created."); nuclear@1: return (-6); nuclear@1: } nuclear@1: nuclear@1: selected_event->midi_buffer = midi_buffer; nuclear@1: selected_event->midi_buffer_length = midi_buffer_length; nuclear@1: nuclear@1: if (smf_event_is_valid(selected_event) == 0) { nuclear@1: fg_critical("Event is invalid from the MIDI specification point of view, not created."); nuclear@1: smf_event_delete(selected_event); nuclear@1: selected_event = NULL; nuclear@1: return (-7); nuclear@1: } nuclear@1: nuclear@1: smf_track_add_event_seconds(selected_track, selected_event, seconds); nuclear@1: nuclear@1: fg_message("Event created."); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_text(char *str) nuclear@1: { nuclear@1: double seconds, type; nuclear@1: char *time, *typestr, *end; nuclear@1: nuclear@1: if (selected_track == NULL) { nuclear@1: fg_critical("Please select a track first, using 'track ' command."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: if (str == NULL) { nuclear@1: fg_critical("Usage: text "); nuclear@1: return (-2); nuclear@1: } nuclear@1: nuclear@1: /* Extract the time. Don't use strsep(3), it doesn't work on SunOS. */ nuclear@1: time = str; nuclear@1: str = strchr(str, ' '); nuclear@1: if (str != NULL) { nuclear@1: *str = '\0'; nuclear@1: str++; nuclear@1: } nuclear@1: nuclear@1: seconds = strtod(time, &end); nuclear@1: if (end - time != strlen(time)) { nuclear@1: fg_critical("Time is supposed to be a number, without trailing characters."); nuclear@1: return (-3); nuclear@1: } nuclear@1: nuclear@1: /* Called with one parameter? */ nuclear@1: if (str == NULL) { nuclear@1: fg_critical("Usage: text "); nuclear@1: return (-4); nuclear@1: } nuclear@1: nuclear@1: /* Extract the event type. */ nuclear@1: typestr = str; nuclear@1: str = strchr(str, ' '); nuclear@1: if (str != NULL) { nuclear@1: *str = '\0'; nuclear@1: str++; nuclear@1: } nuclear@1: nuclear@1: type = strtod(typestr, &end); nuclear@1: if (end - typestr != strlen(typestr)) { nuclear@1: fg_critical("Type is supposed to be a number, without trailing characters."); nuclear@1: return (-4); nuclear@1: } nuclear@1: nuclear@1: if (type < 1 || type > 9) { nuclear@1: fg_critical("Valid values for type are 1 - 9, inclusive."); nuclear@1: return (-5); nuclear@1: } nuclear@1: nuclear@1: /* Called with one parameter? */ nuclear@1: if (str == NULL) { nuclear@1: fg_critical("Usage: text "); nuclear@1: return (-4); nuclear@1: } nuclear@1: nuclear@1: selected_event = smf_event_new_textual(type, str); nuclear@1: if (selected_event == NULL) { nuclear@1: fg_critical("smf_event_new_textual() failed, event not created."); nuclear@1: return (-6); nuclear@1: } nuclear@1: nuclear@1: assert(smf_event_is_valid(selected_event)); nuclear@1: nuclear@1: smf_track_add_event_seconds(selected_track, selected_event, seconds); nuclear@1: nuclear@1: fg_message("Event created."); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: nuclear@1: static int nuclear@1: cmd_eventaddeot(char *time) nuclear@1: { nuclear@1: double seconds; nuclear@1: char *end; nuclear@1: nuclear@1: if (selected_track == NULL) { nuclear@1: fg_critical("Please select a track first, using 'track ' command."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: if (time == NULL) { nuclear@1: fg_critical("Please specify the time, in seconds."); nuclear@1: return (-2); nuclear@1: } nuclear@1: nuclear@1: seconds = strtod(time, &end); nuclear@1: if (end - time != strlen(time)) { nuclear@1: fg_critical("Time is supposed to be a number, without trailing characters."); nuclear@1: return (-3); nuclear@1: } nuclear@1: nuclear@1: if (smf_track_add_eot_seconds(selected_track, seconds)) { nuclear@1: fg_critical("smf_track_add_eot() failed."); nuclear@1: return (-4); nuclear@1: } nuclear@1: nuclear@1: fg_message("Event created."); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_eventrm(char *number) nuclear@1: { nuclear@1: int num = parse_event_number(number); nuclear@1: nuclear@1: if (num < 0) nuclear@1: return (-1); nuclear@1: nuclear@1: if (selected_event != NULL && num == selected_event->event_number) nuclear@1: selected_event = NULL; nuclear@1: nuclear@1: smf_event_delete(smf_track_get_event_by_number(selected_track, num)); nuclear@1: nuclear@1: fg_message("Event #%d removed.", num); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_tempo(char *notused) nuclear@1: { nuclear@1: int i; nuclear@1: smf_tempo_t *tempo; nuclear@1: nuclear@1: for (i = 0;; i++) { nuclear@1: tempo = smf_get_tempo_by_number(smf, i); nuclear@1: if (tempo == NULL) nuclear@1: break; nuclear@1: nuclear@1: fg_message("Tempo #%d: Starts at %d pulses, %f seconds, setting %d microseconds per quarter note, %.2f BPM.", nuclear@1: i, tempo->time_pulses, tempo->time_seconds, tempo->microseconds_per_quarter_note, nuclear@1: 60000000.0 / (double)tempo->microseconds_per_quarter_note); nuclear@1: fg_message("Time signature: %d/%d, %d clocks per click, %d 32nd notes per quarter note.", nuclear@1: tempo->numerator, tempo->denominator, tempo->clocks_per_click, tempo->notes_per_note); nuclear@1: } nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_length(char *notused) nuclear@1: { nuclear@1: fg_message("Length: %d pulses, %f seconds.", smf_get_length_pulses(smf), smf_get_length_seconds(smf)); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_version(char *notused) nuclear@1: { nuclear@1: fg_message("libsmf version %s.", smf_get_version()); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: cmd_exit(char *notused) nuclear@1: { nuclear@1: fg_debug("Good bye."); nuclear@1: exit(0); nuclear@1: } nuclear@1: nuclear@1: static int cmd_help(char *notused); nuclear@1: nuclear@1: static struct command_struct { nuclear@1: char *name; nuclear@1: int (*function)(char *command); nuclear@1: char *help; nuclear@1: } commands[] = {{"help", cmd_help, "Show this help."}, nuclear@1: {"?", cmd_help, NULL}, nuclear@1: {"load", cmd_load, "Load named file."}, nuclear@1: {"open", cmd_load}, nuclear@1: {"save", cmd_save, "Save to named file."}, nuclear@1: {"ppqn", cmd_ppqn, "Show ppqn (aka division), or set ppqn if used with parameter."}, nuclear@1: {"format", cmd_format, "Show format, or set format if used with parameter."}, nuclear@1: {"tracks", cmd_tracks, "Show number of tracks."}, nuclear@1: {"track", cmd_track, "Show number of currently selected track, or select a track."}, nuclear@1: {"trackadd", cmd_trackadd, "Add a track and select it."}, nuclear@1: {"trackrm", cmd_trackrm, "Remove currently selected track."}, nuclear@1: {"events", cmd_events, "Show events in the currently selected track."}, nuclear@1: {"event", cmd_event, "Show number of currently selected event, or select an event."}, nuclear@1: {"add", cmd_eventadd, "Add an event and select it."}, nuclear@1: {"text", cmd_text, "Add textual event and select it."}, nuclear@1: {"eventadd", cmd_eventadd, NULL}, nuclear@1: {"eot", cmd_eventaddeot, "Add an End Of Track event."}, nuclear@1: {"eventaddeot", cmd_eventaddeot, NULL}, nuclear@1: {"eventrm", cmd_eventrm, NULL}, nuclear@1: {"rm", cmd_eventrm, "Remove currently selected event."}, nuclear@1: {"tempo", cmd_tempo, "Show tempo map."}, nuclear@1: {"length", cmd_length, "Show length of the song."}, nuclear@1: {"version", cmd_version, "Show libsmf version."}, nuclear@1: {"exit", cmd_exit, "Exit to shell."}, nuclear@1: {"quit", cmd_exit, NULL}, nuclear@1: {"bye", cmd_exit, NULL}, nuclear@1: {NULL, NULL, NULL}}; nuclear@1: nuclear@1: static int nuclear@1: cmd_help(char *notused) nuclear@1: { nuclear@1: int i, padding_length; nuclear@1: char padding[COMMAND_LENGTH + 1]; nuclear@1: struct command_struct *tmp; nuclear@1: nuclear@1: fg_message("Available commands:"); nuclear@1: nuclear@1: for (tmp = commands; tmp->name != NULL; tmp++) { nuclear@1: /* Skip commands with no help string. */ nuclear@1: if (tmp->help == NULL) nuclear@1: continue; nuclear@1: nuclear@1: padding_length = COMMAND_LENGTH - strlen(tmp->name); nuclear@1: assert(padding_length >= 0); nuclear@1: for (i = 0; i < padding_length; i++) nuclear@1: padding[i] = ' '; nuclear@1: padding[i] = '\0'; nuclear@1: nuclear@1: fg_message("%s:%s%s", tmp->name, padding, tmp->help); nuclear@1: } nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: nuclear@1: /** nuclear@1: * Removes (in place) all whitespace characters before the first nuclear@1: * non-whitespace and all trailing whitespace characters. Replaces nuclear@1: * more than one consecutive whitespace characters with one. nuclear@1: */ nuclear@1: static void nuclear@1: strip_unneeded_whitespace(char *str, int len) nuclear@1: { nuclear@1: char *src, *dest; nuclear@1: int skip_white = 1; nuclear@1: nuclear@1: for (src = str, dest = str; src < dest + len; src++) { nuclear@1: if (*src == '\n' || *src == '\0') { nuclear@1: *dest = '\0'; nuclear@1: break; nuclear@1: } nuclear@1: nuclear@1: if (isspace(*src)) { nuclear@1: if (skip_white) nuclear@1: continue; nuclear@1: nuclear@1: skip_white = 1; nuclear@1: } else { nuclear@1: skip_white = 0; nuclear@1: } nuclear@1: nuclear@1: *dest = *src; nuclear@1: dest++; nuclear@1: } nuclear@1: nuclear@1: /* Remove trailing whitespace. */ nuclear@1: len = strlen(dest); nuclear@1: if (isspace(dest[len - 1])) nuclear@1: dest[len - 1] = '\0'; nuclear@1: } nuclear@1: nuclear@1: static char * nuclear@1: read_command(void) nuclear@1: { nuclear@1: char *buf; nuclear@1: int len; nuclear@1: nuclear@1: #ifdef HAVE_LIBREADLINE nuclear@1: buf = readline("smfsh> "); nuclear@1: #else nuclear@1: buf = malloc(1024); nuclear@1: if (buf == NULL) { nuclear@1: fg_critical("Malloc failed."); nuclear@1: return (NULL); nuclear@1: } nuclear@1: nuclear@1: fprintf(stdout, "smfsh> "); nuclear@1: fflush(stdout); nuclear@1: nuclear@1: buf = fgets(buf, 1024, stdin); nuclear@1: #endif nuclear@1: nuclear@1: if (buf == NULL) { nuclear@1: fprintf(stdout, "exit\n"); nuclear@1: return (strdup("exit")); nuclear@1: } nuclear@1: nuclear@1: strip_unneeded_whitespace(buf, 1024); nuclear@1: nuclear@1: len = strlen(buf); nuclear@1: nuclear@1: if (len == 0) nuclear@1: return (read_command()); nuclear@1: nuclear@1: #ifdef HAVE_LIBREADLINE nuclear@1: add_history(buf); nuclear@1: #endif nuclear@1: nuclear@1: return (buf); nuclear@1: } nuclear@1: nuclear@1: static int nuclear@1: execute_command(char *line) nuclear@1: { nuclear@1: char *command, *args; nuclear@1: struct command_struct *tmp; nuclear@1: nuclear@1: command = line; nuclear@1: args = strchr(line, ' '); nuclear@1: if (args != NULL) { nuclear@1: *args = '\0'; nuclear@1: args++; nuclear@1: } nuclear@1: nuclear@1: for (tmp = commands; tmp->name != NULL; tmp++) { nuclear@1: if (strcmp(tmp->name, command) == 0) nuclear@1: return ((tmp->function)(args)); nuclear@1: } nuclear@1: nuclear@1: fg_warning("No such command: '%s'. Type 'help' to see available commands.", command); nuclear@1: nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: static void nuclear@1: read_and_execute_command(void) nuclear@1: { nuclear@1: int ret; nuclear@1: char *command_line, *command, *next_command; nuclear@1: nuclear@1: command = command_line = read_command(); nuclear@1: nuclear@1: do { nuclear@1: next_command = strchr(command, ';'); nuclear@1: if (next_command != NULL) { nuclear@1: *next_command = '\0'; nuclear@1: next_command++; nuclear@1: } nuclear@1: nuclear@1: strip_unneeded_whitespace(command, 1024); nuclear@1: if (strlen(command) > 0) { nuclear@1: ret = execute_command(command); nuclear@1: if (ret) nuclear@1: fg_warning("Command finished with error."); nuclear@1: } nuclear@1: nuclear@1: command = next_command; nuclear@1: nuclear@1: } while (command); nuclear@1: nuclear@1: free(command_line); nuclear@1: } nuclear@1: nuclear@1: #ifdef HAVE_LIBREADLINE nuclear@1: nuclear@1: static char * nuclear@1: smfsh_command_generator(const char *text, int state) nuclear@1: { nuclear@1: static struct command_struct *command = commands; nuclear@1: char *tmp; nuclear@1: nuclear@1: if (state == 0) nuclear@1: command = commands; nuclear@1: nuclear@1: while (command->name != NULL) { nuclear@1: tmp = command->name; nuclear@1: command++; nuclear@1: nuclear@1: if (strncmp(tmp, text, strlen(text)) == 0) nuclear@1: return (strdup(tmp)); nuclear@1: } nuclear@1: nuclear@1: return (NULL); nuclear@1: } nuclear@1: nuclear@1: static char ** nuclear@1: smfsh_completion(const char *text, int start, int end) nuclear@1: { nuclear@1: int i; nuclear@1: nuclear@1: /* Return NULL if "text" is not the first word in the input line. */ nuclear@1: if (start != 0) { nuclear@1: for (i = 0; i < start; i++) { nuclear@1: if (!isspace(rl_line_buffer[i])) nuclear@1: return (NULL); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: return (rl_completion_matches(text, smfsh_command_generator)); nuclear@1: } nuclear@1: nuclear@1: #endif nuclear@1: nuclear@1: static void nuclear@1: usage(void) nuclear@1: { nuclear@1: fprintf(stderr, "usage: smfsh [-V | file]\n"); nuclear@1: nuclear@1: exit(EX_USAGE); nuclear@1: } nuclear@1: nuclear@1: int nuclear@1: main(int argc, char *argv[]) nuclear@1: { nuclear@1: int ch; nuclear@1: nuclear@1: while ((ch = getopt(argc, argv, "V")) != -1) { nuclear@1: switch (ch) { nuclear@1: case 'V': nuclear@1: cmd_version(NULL); nuclear@1: exit(EX_OK); nuclear@1: nuclear@1: case '?': nuclear@1: default: nuclear@1: usage(); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: if (argc > 2) nuclear@1: usage(); nuclear@1: nuclear@1: /*g_log_set_default_handler(log_handler, NULL);*/ nuclear@1: nuclear@1: smf = smf_new(); nuclear@1: if (smf == NULL) { nuclear@1: fg_critical("Cannot initialize smf_t."); nuclear@1: return (-1); nuclear@1: } nuclear@1: nuclear@1: if (argc == 2) nuclear@1: cmd_load(argv[1]); nuclear@1: else nuclear@1: cmd_trackadd(NULL); nuclear@1: nuclear@1: #ifdef HAVE_LIBREADLINE nuclear@1: rl_readline_name = "smfsh"; nuclear@1: rl_attempted_completion_function = smfsh_completion; nuclear@1: #endif nuclear@1: nuclear@1: for (;;) nuclear@1: read_and_execute_command(); nuclear@1: nuclear@1: return (0); nuclear@1: } nuclear@1: