📄 aplaymidi.c
字号:
/* * aplaymidi.c - play Standard MIDI Files to sequencer port(s) * * Copyright (c) 2004-2006 Clemens Ladisch <clemens@ladisch.de> * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *//* TODO: sequencer queue timer selection */#include <stdio.h>#include <stdlib.h>#include <stdarg.h>#include <string.h>#include <getopt.h>#include <unistd.h>#include <alsa/asoundlib.h>#include "aconfig.h"#include "version.h"#define MIDI_BYTES_PER_SEC 3125/* * A MIDI event after being parsed/loaded from the file. * There could be made a case for using snd_seq_event_t instead. */struct event { struct event *next; /* linked list */ unsigned char type; /* SND_SEQ_EVENT_xxx */ unsigned char port; /* port index */ unsigned int tick; union { unsigned char d[3]; /* channel and data bytes */ int tempo; unsigned int length; /* length of sysex data */ } data; unsigned char sysex[0];};struct track { struct event *first_event; /* list of all events in this track */ int end_tick; /* length of this track */ struct event *current_event; /* used while loading and playing */};static snd_seq_t *seq;static int client;static int port_count;static snd_seq_addr_t *ports;static int queue;static int end_delay = 2;static const char *file_name;static FILE *file;static int file_offset; /* current offset in input file */static int num_tracks;static struct track *tracks;static int smpte_timing;/* prints an error message to stderr */static void errormsg(const char *msg, ...){ va_list ap; va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); fputc('\n', stderr);}/* prints an error message to stderr, and dies */static void fatal(const char *msg, ...){ va_list ap; va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); fputc('\n', stderr); exit(EXIT_FAILURE);}/* memory allocation error handling */static void check_mem(void *p){ if (!p) fatal("Out of memory");}/* error handling for ALSA functions */static void check_snd(const char *operation, int err){ if (err < 0) fatal("Cannot %s - %s", operation, snd_strerror(err));}static void init_seq(void){ int err; /* open sequencer */ err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0); check_snd("open sequencer", err); /* set our name (otherwise it's "Client-xxx") */ err = snd_seq_set_client_name(seq, "aplaymidi"); check_snd("set client name", err); /* find out who we actually are */ client = snd_seq_client_id(seq); check_snd("get client id", client);}/* parses one or more port addresses from the string */static void parse_ports(const char *arg){ char *buf, *s, *port_name; int err; /* make a copy of the string because we're going to modify it */ buf = strdup(arg); check_mem(buf); for (port_name = s = buf; s; port_name = s + 1) { /* Assume that ports are separated by commas. We don't use * spaces because those are valid in client names. */ s = strchr(port_name, ','); if (s) *s = '\0'; ++port_count; ports = realloc(ports, port_count * sizeof(snd_seq_addr_t)); check_mem(ports); err = snd_seq_parse_address(seq, &ports[port_count - 1], port_name); if (err < 0) fatal("Invalid port %s - %s", port_name, snd_strerror(err)); } free(buf);}static void create_source_port(void){ snd_seq_port_info_t *pinfo; int err; snd_seq_port_info_alloca(&pinfo); /* the first created port is 0 anyway, but let's make sure ... */ snd_seq_port_info_set_port(pinfo, 0); snd_seq_port_info_set_port_specified(pinfo, 1); snd_seq_port_info_set_name(pinfo, "aplaymidi"); snd_seq_port_info_set_capability(pinfo, 0); /* sic */ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); err = snd_seq_create_port(seq, pinfo); check_snd("create port", err);}static void create_queue(void){ queue = snd_seq_alloc_named_queue(seq, "aplaymidi"); check_snd("create queue", queue); /* the queue is now locked, which is just fine */}static void connect_ports(void){ int i, err; /* * We send MIDI events with explicit destination addresses, so we don't * need any connections to the playback ports. But we connect to those * anyway to force any underlying RawMIDI ports to remain open while * we're playing - otherwise, ALSA would reset the port after every * event. */ for (i = 0; i < port_count; ++i) { err = snd_seq_connect_to(seq, 0, ports[i].client, ports[i].port); if (err < 0) fatal("Cannot connect to port %d:%d - %s", ports[i].client, ports[i].port, snd_strerror(err)); }}static int read_byte(void){ ++file_offset; return getc(file);}/* reads a little-endian 32-bit integer */static int read_32_le(void){ int value; value = read_byte(); value |= read_byte() << 8; value |= read_byte() << 16; value |= read_byte() << 24; return !feof(file) ? value : -1;}/* reads a 4-character identifier */static int read_id(void){ return read_32_le();}#define MAKE_ID(c1, c2, c3, c4) ((c1) | ((c2) << 8) | ((c3) << 16) | ((c4) << 24))/* reads a fixed-size big-endian number */static int read_int(int bytes){ int c, value = 0; do { c = read_byte(); if (c == EOF) return -1; value = (value << 8) | c; } while (--bytes); return value;}/* reads a variable-length number */static int read_var(void){ int value, c; c = read_byte(); value = c & 0x7f; if (c & 0x80) { c = read_byte(); value = (value << 7) | (c & 0x7f); if (c & 0x80) { c = read_byte(); value = (value << 7) | (c & 0x7f); if (c & 0x80) { c = read_byte(); value = (value << 7) | c; if (c & 0x80) return -1; } } } return !feof(file) ? value : -1;}/* allocates a new event */static struct event *new_event(struct track *track, int sysex_length){ struct event *event; event = malloc(sizeof(struct event) + sysex_length); check_mem(event); event->next = NULL; /* append at the end of the track's linked list */ if (track->current_event) track->current_event->next = event; else track->first_event = event; track->current_event = event; return event;}static void skip(int bytes){ while (bytes > 0) read_byte(), --bytes;}/* reads one complete track from the file */static int read_track(struct track *track, int track_end){ int tick = 0; unsigned char last_cmd = 0; unsigned char port = 0; /* the current file position is after the track ID and length */ while (file_offset < track_end) { unsigned char cmd; struct event *event; int delta_ticks, len, c; delta_ticks = read_var(); if (delta_ticks < 0) break; tick += delta_ticks; c = read_byte(); if (c < 0) break; if (c & 0x80) { /* have command */ cmd = c; if (cmd < 0xf0) last_cmd = cmd; } else { /* running status */ ungetc(c, file); file_offset--; cmd = last_cmd; if (!cmd) goto _error; } switch (cmd >> 4) { /* maps SMF events to ALSA sequencer events */ static const unsigned char cmd_type[] = { [0x8] = SND_SEQ_EVENT_NOTEOFF, [0x9] = SND_SEQ_EVENT_NOTEON, [0xa] = SND_SEQ_EVENT_KEYPRESS, [0xb] = SND_SEQ_EVENT_CONTROLLER, [0xc] = SND_SEQ_EVENT_PGMCHANGE, [0xd] = SND_SEQ_EVENT_CHANPRESS, [0xe] = SND_SEQ_EVENT_PITCHBEND }; case 0x8: /* channel msg with 2 parameter bytes */ case 0x9: case 0xa: case 0xb: case 0xe: event = new_event(track, 0); event->type = cmd_type[cmd >> 4]; event->port = port; event->tick = tick; event->data.d[0] = cmd & 0x0f; event->data.d[1] = read_byte() & 0x7f; event->data.d[2] = read_byte() & 0x7f; break; case 0xc: /* channel msg with 1 parameter byte */ case 0xd: event = new_event(track, 0); event->type = cmd_type[cmd >> 4]; event->port = port; event->tick = tick; event->data.d[0] = cmd & 0x0f; event->data.d[1] = read_byte() & 0x7f; break; case 0xf: switch (cmd) { case 0xf0: /* sysex */ case 0xf7: /* continued sysex, or escaped commands */ len = read_var(); if (len < 0) goto _error; if (cmd == 0xf0) ++len; event = new_event(track, len); event->type = SND_SEQ_EVENT_SYSEX; event->port = port; event->tick = tick; event->data.length = len; if (cmd == 0xf0) { event->sysex[0] = 0xf0; c = 1; } else { c = 0; } for (; c < len; ++c) event->sysex[c] = read_byte(); break; case 0xff: /* meta event */ c = read_byte(); len = read_var(); if (len < 0) goto _error; switch (c) { case 0x21: /* port number */ if (len < 1) goto _error; port = read_byte() % port_count; skip(len - 1); break; case 0x2f: /* end of track */ track->end_tick = tick; skip(track_end - file_offset); return 1; case 0x51: /* tempo */ if (len < 3) goto _error; if (smpte_timing) { /* SMPTE timing doesn't change */ skip(len); } else { event = new_event(track, 0); event->type = SND_SEQ_EVENT_TEMPO; event->port = port; event->tick = tick; event->data.tempo = read_byte() << 16; event->data.tempo |= read_byte() << 8; event->data.tempo |= read_byte(); skip(len - 3); } break; default: /* ignore all other meta events */ skip(len); break; } break; default: /* invalid Fx command */ goto _error; } break; default: /* cannot happen */ goto _error; } }_error: errormsg("%s: invalid MIDI data (offset %#x)", file_name, file_offset); return 0;}/* reads an entire MIDI file */static int read_smf(void){ int header_len, type, time_division, i, err; snd_seq_queue_tempo_t *queue_tempo; /* the curren position is immediately after the "MThd" id */ header_len = read_int(4); if (header_len < 6) {invalid_format: errormsg("%s: invalid file format", file_name); return 0; } type = read_int(2); if (type != 0 && type != 1) { errormsg("%s: type %d format is not supported", file_name, type); return 0; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -