📄 aplaymidi.c
字号:
num_tracks = read_int(2); if (num_tracks < 1 || num_tracks > 1000) { errormsg("%s: invalid number of tracks (%d)", file_name, num_tracks); num_tracks = 0; return 0; } tracks = calloc(num_tracks, sizeof(struct track)); if (!tracks) { errormsg("out of memory"); num_tracks = 0; return 0; } time_division = read_int(2); if (time_division < 0) goto invalid_format; /* interpret and set tempo */ snd_seq_queue_tempo_alloca(&queue_tempo); smpte_timing = !!(time_division & 0x8000); if (!smpte_timing) { /* time_division is ticks per quarter */ snd_seq_queue_tempo_set_tempo(queue_tempo, 500000); /* default: 120 bpm */ snd_seq_queue_tempo_set_ppq(queue_tempo, time_division); } else { /* upper byte is negative frames per second */ i = 0x80 - ((time_division >> 8) & 0x7f); /* lower byte is ticks per frame */ time_division &= 0xff; /* now pretend that we have quarter-note based timing */ switch (i) { case 24: snd_seq_queue_tempo_set_tempo(queue_tempo, 500000); snd_seq_queue_tempo_set_ppq(queue_tempo, 12 * time_division); break; case 25: snd_seq_queue_tempo_set_tempo(queue_tempo, 400000); snd_seq_queue_tempo_set_ppq(queue_tempo, 10 * time_division); break; case 29: /* 30 drop-frame */ snd_seq_queue_tempo_set_tempo(queue_tempo, 100000000); snd_seq_queue_tempo_set_ppq(queue_tempo, 2997 * time_division); break; case 30: snd_seq_queue_tempo_set_tempo(queue_tempo, 500000); snd_seq_queue_tempo_set_ppq(queue_tempo, 15 * time_division); break; default: errormsg("%s: invalid number of SMPTE frames per second (%d)", file_name, i); return 0; } } err = snd_seq_set_queue_tempo(seq, queue, queue_tempo); if (err < 0) { errormsg("Cannot set queue tempo (%u/%i)", snd_seq_queue_tempo_get_tempo(queue_tempo), snd_seq_queue_tempo_get_ppq(queue_tempo)); return 0; } /* read tracks */ for (i = 0; i < num_tracks; ++i) { int len; /* search for MTrk chunk */ for (;;) { int id = read_id(); len = read_int(4); if (feof(file)) { errormsg("%s: unexpected end of file", file_name); return 0; } if (len < 0 || len >= 0x10000000) { errormsg("%s: invalid chunk length %d", file_name, len); return 0; } if (id == MAKE_ID('M', 'T', 'r', 'k')) break; skip(len); } if (!read_track(&tracks[i], file_offset + len)) return 0; } return 1;}static int read_riff(void){ /* skip file length */ read_byte(); read_byte(); read_byte(); read_byte(); /* check file type ("RMID" = RIFF MIDI) */ if (read_id() != MAKE_ID('R', 'M', 'I', 'D')) {invalid_format: errormsg("%s: invalid file format", file_name); return 0; } /* search for "data" chunk */ for (;;) { int id = read_id(); int len = read_32_le(); if (feof(file)) {data_not_found: errormsg("%s: data chunk not found", file_name); return 0; } if (id == MAKE_ID('d', 'a', 't', 'a')) break; if (len < 0) goto data_not_found; skip((len + 1) & ~1); } /* the "data" chunk must contain data in SMF format */ if (read_id() != MAKE_ID('M', 'T', 'h', 'd')) goto invalid_format; return read_smf();}static void cleanup_file_data(void){ int i; struct event *event; for (i = 0; i < num_tracks; ++i) { event = tracks[i].first_event; while (event) { struct event *next = event->next; free(event); event = next; } } num_tracks = 0; free(tracks); tracks = NULL;}static void handle_big_sysex(snd_seq_event_t *ev){ unsigned int length; ssize_t event_size; int err; length = ev->data.ext.len; if (length > MIDI_BYTES_PER_SEC) ev->data.ext.len = MIDI_BYTES_PER_SEC; event_size = snd_seq_event_length(ev); if (event_size + 1 > snd_seq_get_output_buffer_size(seq)) { err = snd_seq_drain_output(seq); check_snd("drain output", err); err = snd_seq_set_output_buffer_size(seq, event_size + 1); check_snd("set output buffer size", err); } while (length > MIDI_BYTES_PER_SEC) { err = snd_seq_event_output(seq, ev); check_snd("output event", err); err = snd_seq_drain_output(seq); check_snd("drain output", err); err = snd_seq_sync_output_queue(seq); check_snd("sync output", err); if (sleep(1)) fatal("aborted"); ev->data.ext.ptr += MIDI_BYTES_PER_SEC; length -= MIDI_BYTES_PER_SEC; } ev->data.ext.len = length;}static void play_midi(void){ snd_seq_event_t ev; int i, max_tick, err; /* calculate length of the entire file */ max_tick = -1; for (i = 0; i < num_tracks; ++i) { if (tracks[i].end_tick > max_tick) max_tick = tracks[i].end_tick; } /* initialize current position in each track */ for (i = 0; i < num_tracks; ++i) tracks[i].current_event = tracks[i].first_event; /* common settings for all our events */ snd_seq_ev_clear(&ev); ev.queue = queue; ev.source.port = 0; ev.flags = SND_SEQ_TIME_STAMP_TICK; err = snd_seq_start_queue(seq, queue, NULL); check_snd("start queue", err); /* The queue won't be started until the START_QUEUE event is * actually drained to the kernel, which is exactly what we want. */ for (;;) { struct event* event = NULL; struct track* event_track = NULL; int i, min_tick = max_tick + 1; /* search next event */ for (i = 0; i < num_tracks; ++i) { struct track *track = &tracks[i]; struct event *e2 = track->current_event; if (e2 && e2->tick < min_tick) { min_tick = e2->tick; event = e2; event_track = track; } } if (!event) break; /* end of song reached */ /* advance pointer to next event */ event_track->current_event = event->next; /* output the event */ ev.type = event->type; ev.time.tick = event->tick; ev.dest = ports[event->port]; switch (ev.type) { case SND_SEQ_EVENT_NOTEON: case SND_SEQ_EVENT_NOTEOFF: case SND_SEQ_EVENT_KEYPRESS: snd_seq_ev_set_fixed(&ev); ev.data.note.channel = event->data.d[0]; ev.data.note.note = event->data.d[1]; ev.data.note.velocity = event->data.d[2]; break; case SND_SEQ_EVENT_CONTROLLER: snd_seq_ev_set_fixed(&ev); ev.data.control.channel = event->data.d[0]; ev.data.control.param = event->data.d[1]; ev.data.control.value = event->data.d[2]; break; case SND_SEQ_EVENT_PGMCHANGE: case SND_SEQ_EVENT_CHANPRESS: snd_seq_ev_set_fixed(&ev); ev.data.control.channel = event->data.d[0]; ev.data.control.value = event->data.d[1]; break; case SND_SEQ_EVENT_PITCHBEND: snd_seq_ev_set_fixed(&ev); ev.data.control.channel = event->data.d[0]; ev.data.control.value = ((event->data.d[1]) | ((event->data.d[2]) << 7)) - 0x2000; break; case SND_SEQ_EVENT_SYSEX: snd_seq_ev_set_variable(&ev, event->data.length, event->sysex); handle_big_sysex(&ev); break; case SND_SEQ_EVENT_TEMPO: snd_seq_ev_set_fixed(&ev); ev.dest.client = SND_SEQ_CLIENT_SYSTEM; ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER; ev.data.queue.queue = queue; ev.data.queue.param.value = event->data.tempo; break; default: fatal("Invalid event type %d!", ev.type); } /* this blocks when the output pool has been filled */ err = snd_seq_event_output(seq, &ev); check_snd("output event", err); } /* schedule queue stop at end of song */ snd_seq_ev_set_fixed(&ev); ev.type = SND_SEQ_EVENT_STOP; ev.time.tick = max_tick; ev.dest.client = SND_SEQ_CLIENT_SYSTEM; ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER; ev.data.queue.queue = queue; err = snd_seq_event_output(seq, &ev); check_snd("output event", err); /* make sure that the sequencer sees all our events */ err = snd_seq_drain_output(seq); check_snd("drain output", err); /* * There are three possibilities how to wait until all events have * been played: * 1) send an event back to us (like pmidi does), and wait for it; * 2) wait for the EVENT_STOP notification for our queue which is sent * by the system timer port (this would require a subscription); * 3) wait until the output pool is empty. * The last is the simplest. */ err = snd_seq_sync_output_queue(seq); check_snd("sync output", err); /* give the last notes time to die away */ if (end_delay > 0) sleep(end_delay);}static void play_file(void){ int ok; if (!strcmp(file_name, "-")) file = stdin; else file = fopen(file_name, "rb"); if (!file) { errormsg("Cannot open %s - %s", file_name, strerror(errno)); return; } file_offset = 0; ok = 0; switch (read_id()) { case MAKE_ID('M', 'T', 'h', 'd'): ok = read_smf(); break; case MAKE_ID('R', 'I', 'F', 'F'): ok = read_riff(); break; default: errormsg("%s is not a Standard MIDI File", file_name); break; } if (file != stdin) fclose(file); if (ok) play_midi(); cleanup_file_data();}static void list_ports(void){ snd_seq_client_info_t *cinfo; snd_seq_port_info_t *pinfo; snd_seq_client_info_alloca(&cinfo); snd_seq_port_info_alloca(&pinfo); puts(" Port Client name Port name"); snd_seq_client_info_set_client(cinfo, -1); while (snd_seq_query_next_client(seq, cinfo) >= 0) { int client = snd_seq_client_info_get_client(cinfo); snd_seq_port_info_set_client(pinfo, client); snd_seq_port_info_set_port(pinfo, -1); while (snd_seq_query_next_port(seq, pinfo) >= 0) { /* port must understand MIDI messages */ if (!(snd_seq_port_info_get_type(pinfo) & SND_SEQ_PORT_TYPE_MIDI_GENERIC)) continue; /* we need both WRITE and SUBS_WRITE */ if ((snd_seq_port_info_get_capability(pinfo) & (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)) != (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)) continue; printf("%3d:%-3d %-32.32s %s\n", snd_seq_port_info_get_client(pinfo), snd_seq_port_info_get_port(pinfo), snd_seq_client_info_get_name(cinfo), snd_seq_port_info_get_name(pinfo)); } }}static void usage(const char *argv0){ printf( "Usage: %s -p client:port[,...] [-d delay] midifile ...\n" "-h, --help this help\n" "-V, --version print current version\n" "-l, --list list all possible output ports\n" "-p, --port=client:port,... set port(s) to play to\n" "-d, --delay=seconds delay after song ends\n", argv0);}static void version(void){ puts("aplaymidi version " SND_UTIL_VERSION_STR);}int main(int argc, char *argv[]){ static const char short_options[] = "hVlp:d:"; static const struct option long_options[] = { {"help", 0, NULL, 'h'}, {"version", 0, NULL, 'V'}, {"list", 0, NULL, 'l'}, {"port", 1, NULL, 'p'}, {"delay", 1, NULL, 'd'}, {} }; int c; int do_list = 0; init_seq(); while ((c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (c) { case 'h': usage(argv[0]); return 0; case 'V': version(); return 0; case 'l': do_list = 1; break; case 'p': parse_ports(optarg); break; case 'd': end_delay = atoi(optarg); break; default: usage(argv[0]); return 1; } } if (do_list) { list_ports(); } else { if (port_count < 1) { /* use env var for compatibility with pmidi */ const char *ports_str = getenv("ALSA_OUTPUT_PORTS"); if (ports_str) parse_ports(ports_str); if (port_count < 1) { errormsg("Please specify at least one port with --port."); return 1; } } if (optind >= argc) { errormsg("Please specify a file to play."); return 1; } create_source_port(); create_queue(); connect_ports(); for (; optind < argc; ++optind) { file_name = argv[optind]; play_file(); } } snd_seq_close(seq); return 0;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -