📄 arecordmidi.c
字号:
/* * arecordmidi.c - record standard MIDI files from sequencer ports * * Copyright (c) 2004-2005 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 <signal.h>#include <getopt.h>#include <sys/poll.h>#include <alsa/asoundlib.h>#include "aconfig.h"#include "version.h"#define BUFFER_SIZE 4088/* linked list of buffers, stores data as in the .mid file */struct buffer { struct buffer *next; unsigned char buf[BUFFER_SIZE];};struct smf_track { int size; /* size of entire data */ int cur_buf_size; /* size of cur_buf */ struct buffer *cur_buf; snd_seq_tick_time_t last_tick; /* end of track */ unsigned char last_command; /* used for running status */ int used; /* anything record on this track */ struct buffer first_buf; /* list head */};/* timing/sysex + 16 channels */#define TRACKS_PER_PORT 17/* metronome settings *//* TODO: create options for this */#define METRONOME_CHANNEL 9#define METRONOME_STRONG_NOTE 34#define METRONOME_WEAK_NOTE 33#define METRONOME_VELOCITY 100#define METRONOME_PROGRAM 0static snd_seq_t *seq;static int client;static int port_count;static snd_seq_addr_t *ports;static int queue;static int smpte_timing = 0;static int beats = 120;static int frames;static int ticks = 0;static FILE *file;static int channel_split;static int num_tracks;static struct smf_track *tracks;static volatile sig_atomic_t stop = 0;static int use_metronome = 0;static snd_seq_addr_t metronome_port;static int metronome_weak_note = METRONOME_WEAK_NOTE;static int metronome_strong_note = METRONOME_STRONG_NOTE;static int metronome_velocity = METRONOME_VELOCITY;static int metronome_program = METRONOME_PROGRAM;static int metronome_channel = METRONOME_CHANNEL;static int ts_num = 4; /* time signature: numerator */static int ts_div = 4; /* time signature: denominator */static int ts_dd = 2; /* time signature: denominator as a power of two *//* 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); /* find out our client's id */ client = snd_seq_client_id(seq); check_snd("get client id", client); /* set our client's name */ err = snd_seq_set_client_name(seq, "arecordmidi"); check_snd("set client name", err);}/* 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);}/* parses the metronome port address */static void init_metronome(const char *arg){ int err; err = snd_seq_parse_address(seq, &metronome_port, arg); if (err < 0) fatal("Invalid port %s - %s", arg, snd_strerror(err)); use_metronome = 1;}/* parses time signature specification */static void time_signature(const char *arg){ long x = 0; char *sep; x = strtol(arg, &sep, 10); if (x < 1 || x > 64 || *sep != ':') fatal("Invalid time signature (%s)", arg); ts_num = x; x = strtol(++sep, NULL, 10); if (x < 1 || x > 64) fatal("Invalid time signature (%s)", arg); ts_div = x; for (ts_dd = 0; x > 1; x /= 2) ++ts_dd;}/* * Metronome implementation */static void metronome_note(unsigned char note, unsigned int tick){ snd_seq_event_t ev; snd_seq_ev_clear(&ev); snd_seq_ev_set_note(&ev, metronome_channel, note, metronome_velocity, 1); snd_seq_ev_schedule_tick(&ev, queue, 0, tick); snd_seq_ev_set_source(&ev, port_count); snd_seq_ev_set_subs(&ev); snd_seq_event_output(seq, &ev);}static void metronome_echo(unsigned int tick){ snd_seq_event_t ev; snd_seq_ev_clear(&ev); ev.type = SND_SEQ_EVENT_USR0; snd_seq_ev_schedule_tick(&ev, queue, 0, tick); snd_seq_ev_set_source(&ev, port_count); snd_seq_ev_set_dest(&ev, client, port_count); snd_seq_event_output(seq, &ev);}static void metronome_pattern(unsigned int tick){ int j, t, duration; t = tick; duration = ticks * 4 / ts_div; for (j = 0; j < ts_num; j++) { metronome_note(j ? metronome_weak_note : metronome_strong_note, t); t += duration; } metronome_echo(t); snd_seq_drain_output(seq);}static void metronome_set_program(void){ snd_seq_event_t ev; snd_seq_ev_clear(&ev); snd_seq_ev_set_pgmchange(&ev, metronome_channel, metronome_program); snd_seq_ev_set_source(&ev, port_count); snd_seq_ev_set_subs(&ev); snd_seq_event_output(seq, &ev);}static void init_tracks(void){ int i; /* MIDI RP-019 says we need at least one track per port */ num_tracks = port_count; /* Allocate one track for each possible channel. * Empty tracks won't be written to the file. */ if (channel_split) num_tracks *= TRACKS_PER_PORT; tracks = calloc(num_tracks, sizeof(struct smf_track)); check_mem(tracks); for (i = 0; i < num_tracks; ++i) tracks[i].cur_buf = &tracks[i].first_buf;}static void create_queue(void){ snd_seq_queue_tempo_t *tempo; int err; queue = snd_seq_alloc_named_queue(seq, "arecordmidi"); check_snd("create queue", queue); snd_seq_queue_tempo_alloca(&tempo); if (!smpte_timing) { snd_seq_queue_tempo_set_tempo(tempo, 60000000 / beats); snd_seq_queue_tempo_set_ppq(tempo, ticks); } else { /* * ALSA doesn't know about the SMPTE time divisions, so * we pretend to have a musical tempo with the equivalent * number of ticks/s. */ switch (frames) { case 24: snd_seq_queue_tempo_set_tempo(tempo, 500000); snd_seq_queue_tempo_set_ppq(tempo, 12 * ticks); break; case 25: snd_seq_queue_tempo_set_tempo(tempo, 400000); snd_seq_queue_tempo_set_ppq(tempo, 10 * ticks); break; case 29: snd_seq_queue_tempo_set_tempo(tempo, 100000000); snd_seq_queue_tempo_set_ppq(tempo, 2997 * ticks); break; case 30: snd_seq_queue_tempo_set_tempo(tempo, 500000); snd_seq_queue_tempo_set_ppq(tempo, 15 * ticks); break; default: fatal("Invalid SMPTE frames %d", frames); } } err = snd_seq_set_queue_tempo(seq, queue, tempo); if (err < 0) fatal("Cannot set queue tempo (%u/%i)", snd_seq_queue_tempo_get_tempo(tempo), snd_seq_queue_tempo_get_ppq(tempo));}static void create_ports(void){ snd_seq_port_info_t *pinfo; int i, err; char name[32]; snd_seq_port_info_alloca(&pinfo); /* common information for all our ports */ snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); snd_seq_port_info_set_midi_channels(pinfo, 16); /* we want to know when the events got delivered to us */ snd_seq_port_info_set_timestamping(pinfo, 1); snd_seq_port_info_set_timestamp_queue(pinfo, queue); /* our port number is the same as our port index */ snd_seq_port_info_set_port_specified(pinfo, 1); for (i = 0; i < port_count; ++i) { snd_seq_port_info_set_port(pinfo, i); sprintf(name, "arecordmidi port %i", i); snd_seq_port_info_set_name(pinfo, name); err = snd_seq_create_port(seq, pinfo); check_snd("create port", err); } /* create an optional metronome port */ if (use_metronome) { snd_seq_port_info_set_port(pinfo, port_count); snd_seq_port_info_set_name(pinfo, "arecordmidi metronome"); snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE); snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_APPLICATION); snd_seq_port_info_set_midi_channels(pinfo, 0); snd_seq_port_info_set_timestamping(pinfo, 0); err = snd_seq_create_port(seq, pinfo); check_snd("create metronome port", err); }}static void connect_ports(void){ int i, err; for (i = 0; i < port_count; ++i) { err = snd_seq_connect_from(seq, i, ports[i].client, ports[i].port); if (err < 0) fatal("Cannot connect from port %d:%d - %s", ports[i].client, ports[i].port, snd_strerror(err)); } /* subscribe the metronome port */ if (use_metronome) { err = snd_seq_connect_to(seq, port_count, metronome_port.client, metronome_port.port); if (err < 0) fatal("Cannot connect to port %d:%d - %s", metronome_port.client, metronome_port.port, snd_strerror(err)); }}/* records a byte to be written to the .mid file */static void add_byte(struct smf_track *track, unsigned char byte){ /* make sure we have enough room in the current buffer */ if (track->cur_buf_size >= BUFFER_SIZE) { track->cur_buf->next = calloc(1, sizeof(struct buffer)); if (!track->cur_buf->next) fatal("out of memory"); track->cur_buf = track->cur_buf->next; track->cur_buf_size = 0; } track->cur_buf->buf[track->cur_buf_size++] = byte; track->size++;}/* record a variable-length quantity */static void var_value(struct smf_track *track, int v){ if (v >= (1 << 28)) add_byte(track, 0x80 | ((v >> 28) & 0x03)); if (v >= (1 << 21)) add_byte(track, 0x80 | ((v >> 21) & 0x7f)); if (v >= (1 << 14)) add_byte(track, 0x80 | ((v >> 14) & 0x7f)); if (v >= (1 << 7)) add_byte(track, 0x80 | ((v >> 7) & 0x7f)); add_byte(track, v & 0x7f);}/* record the delta time from the last event */static void delta_time(struct smf_track *track, const snd_seq_event_t *ev){ int diff = ev->time.tick - track->last_tick; if (diff < 0) diff = 0; var_value(track, diff); track->last_tick = ev->time.tick;}/* record a status byte (or not if we can use running status) */static void command(struct smf_track *track, unsigned char cmd){ if (cmd != track->last_command) add_byte(track, cmd); track->last_command = cmd < 0xf0 ? cmd : 0;}/* put port numbers into all tracks */static void record_port_numbers(void){ int i; for (i = 0; i < num_tracks; ++i) { var_value(&tracks[i], 0); add_byte(&tracks[i], 0xff); add_byte(&tracks[i], 0x21); var_value(&tracks[i], 1); if (channel_split) add_byte(&tracks[i], i / TRACKS_PER_PORT); else add_byte(&tracks[i], i); }}static void record_event(const snd_seq_event_t *ev){ unsigned int i; struct smf_track *track;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -