From patchwork Sat Jul 6 07:42:27 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 810795 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 0A2A1C38150 for ; Sat, 6 Jul 2024 07:46:41 +0000 (UTC) Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 39E201549; Sat, 6 Jul 2024 09:46:30 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 39E201549 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1720252000; bh=NhEvh93pOnGX8YoFcZsj2dBpsd7HUF850HSyvQgJWGs=; h=From:To:Subject:Date:List-Id:List-Archive:List-Help:List-Owner: List-Post:List-Subscribe:List-Unsubscribe:From; b=jpGPz0kb/mMJxtycPgjCjL2mXJtuqwrxs/DTvzRtFFNmm+eZ+rtvuaX9K9NZ8ZRW/ 8H891w5V6YIkJSYOYmE7YyG0ohe7zL7ROhXiZsq55mUElhALYVbWGqbfZOYKf4aDlM Ii+Dz+2ws2/QFEgKgW5La4V4SYhl66eSuviTVhEQ= Received: by alsa1.perex.cz (Postfix, from userid 50401) id 29E76F805DA; Sat, 6 Jul 2024 09:45:50 +0200 (CEST) Received: from mailman-core.alsa-project.org (mailman-core.alsa-project.org [10.254.200.10]) by alsa1.perex.cz (Postfix) with ESMTP id 706A3F805BE; Sat, 6 Jul 2024 09:45:50 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id D5041F804FC; Sat, 6 Jul 2024 09:42:22 +0200 (CEST) Received: from smtp-out2.suse.de (smtp-out2.suse.de [IPv6:2a07:de40:b251:101:10:150:64:2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id F0D15F8025E for ; Sat, 6 Jul 2024 09:42:06 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz F0D15F8025E Authentication-Results: alsa1.perex.cz; dkim=pass (1024-bit key, unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=kjFFFpCN; dkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=bqhnj/YJ; dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=kjFFFpCN; dkim=neutral header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=bqhnj/YJ Received: from imap1.dmz-prg2.suse.org (imap1.dmz-prg2.suse.org [IPv6:2a07:de40:b281:104:10:150:64:97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 53B621F809; Sat, 6 Jul 2024 07:42:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=yPitnYP7MSxMArmODuHFAOIrr8B1SqGskEuui3xj6ZU=; b=kjFFFpCNCbIg9C1pwjXqq8TQrndYT7Sfs/MdogbbPHqrnVRZheMJwSutYYfJwoAwN+zk6s zB/QH48bXv2yADYfAb9vtwSq2o7DJOccwMpggU1kzuNEPhhvs3EV0c2EjTefARYL3GgwfP Z+IZNs7MBvcnyi+0m/D6gHoT9OLZXqg= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=yPitnYP7MSxMArmODuHFAOIrr8B1SqGskEuui3xj6ZU=; b=bqhnj/YJ984DNg+EvHRMC5T3TftQ+PRIV6qUAVLJR2lmhlkLnCrY8tmP0E7v1WVjZUFgZ3 C8ur/OdqOKIVwQCQ== Authentication-Results: smtp-out2.suse.de; dkim=pass header.d=suse.de header.s=susede2_rsa header.b=kjFFFpCN; dkim=pass header.d=suse.de header.s=susede2_ed25519 header.b="bqhnj/YJ" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=yPitnYP7MSxMArmODuHFAOIrr8B1SqGskEuui3xj6ZU=; b=kjFFFpCNCbIg9C1pwjXqq8TQrndYT7Sfs/MdogbbPHqrnVRZheMJwSutYYfJwoAwN+zk6s zB/QH48bXv2yADYfAb9vtwSq2o7DJOccwMpggU1kzuNEPhhvs3EV0c2EjTefARYL3GgwfP Z+IZNs7MBvcnyi+0m/D6gHoT9OLZXqg= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=yPitnYP7MSxMArmODuHFAOIrr8B1SqGskEuui3xj6ZU=; b=bqhnj/YJ984DNg+EvHRMC5T3TftQ+PRIV6qUAVLJR2lmhlkLnCrY8tmP0E7v1WVjZUFgZ3 C8ur/OdqOKIVwQCQ== Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id 336EA12FF6; Sat, 6 Jul 2024 07:42:05 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id 4V4KC031iGZRDgAAD6G6ig (envelope-from ); Sat, 06 Jul 2024 07:42:05 +0000 From: Takashi Iwai To: alsa-devel@alsa-project.org Subject: [PATCH alsa-utils 1/5] aplaymidi2: Add initial version Date: Sat, 6 Jul 2024 09:42:27 +0200 Message-ID: <20240706074232.6364-1-tiwai@suse.de> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 X-Rspamd-Queue-Id: 53B621F809 X-Spamd-Result: default: False [-3.01 / 50.00]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; R_DKIM_ALLOW(-0.20)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; MIME_GOOD(-0.10)[text/plain]; MX_GOOD(-0.01)[]; TO_DN_NONE(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; FROM_EQ_ENVFROM(0.00)[]; ARC_NA(0.00)[]; FROM_HAS_DN(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; TO_MATCH_ENVRCPT_ALL(0.00)[]; RCVD_TLS_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.de:email,suse.de:dkim]; DKIM_SIGNED(0.00)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; FUZZY_BLOCKED(0.00)[rspamd.com]; RCVD_COUNT_TWO(0.00)[2]; DWL_DNSWL_BLOCKED(0.00)[suse.de:dkim]; MIME_TRACE(0.00)[0:+]; DKIM_TRACE(0.00)[suse.de:+] X-Rspamd-Action: no action X-Rspamd-Server: rspamd1.dmz-prg2.suse.org Message-ID-Hash: HT7OSXXKTJNRUUTWQMH4DJFV34SHYN7B X-Message-ID-Hash: HT7OSXXKTJNRUUTWQMH4DJFV34SHYN7B X-MailFrom: tiwai@suse.de X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-alsa-devel.alsa-project.org-0; header-match-alsa-devel.alsa-project.org-1; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.9 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: aplaymidi2 is a program similar like aplaymidi, but intended for playing back a MIDI Clip file that was introduced for handling UMP. MIDI Clip file contains UMP packets, and its structure is much simpler than SMF. The options are mostly same as aplaymidi, but I omitted -l option for simplifying the code. Signed-off-by: Takashi Iwai --- configure.ac | 2 +- seq/Makefile.am | 2 +- seq/aplaymidi2/Makefile.am | 5 + seq/aplaymidi2/aplaymidi2.1 | 71 ++++++ seq/aplaymidi2/aplaymidi2.c | 447 ++++++++++++++++++++++++++++++++++++ 5 files changed, 525 insertions(+), 2 deletions(-) create mode 100644 seq/aplaymidi2/Makefile.am create mode 100644 seq/aplaymidi2/aplaymidi2.1 create mode 100644 seq/aplaymidi2/aplaymidi2.c diff --git a/configure.ac b/configure.ac index 173b9ed1644c..708380cf31dc 100644 --- a/configure.ac +++ b/configure.ac @@ -486,7 +486,7 @@ AC_OUTPUT(Makefile alsactl/Makefile alsactl/init/Makefile \ bat/Makefile bat/tests/Makefile bat/tests/asound_state/Makefile \ aplay/Makefile include/Makefile iecset/Makefile utils/Makefile \ utils/alsa-utils.spec seq/Makefile seq/aconnect/Makefile \ - seq/aplaymidi/Makefile seq/aseqdump/Makefile seq/aseqnet/Makefile \ + seq/aplaymidi/Makefile seq/aplaymidi2/Makefile seq/aseqdump/Makefile seq/aseqnet/Makefile \ seq/aseqsend/Makefile speaker-test/Makefile speaker-test/samples/Makefile \ alsaloop/Makefile alsa-info/Makefile \ axfer/Makefile axfer/test/Makefile \ diff --git a/seq/Makefile.am b/seq/Makefile.am index b0f628aac4da..a6171268186b 100644 --- a/seq/Makefile.am +++ b/seq/Makefile.am @@ -1 +1 @@ -SUBDIRS=aconnect aplaymidi aseqdump aseqnet aseqsend +SUBDIRS=aconnect aplaymidi aplaymidi2 aseqdump aseqnet aseqsend diff --git a/seq/aplaymidi2/Makefile.am b/seq/aplaymidi2/Makefile.am new file mode 100644 index 000000000000..0c5c743f7eb8 --- /dev/null +++ b/seq/aplaymidi2/Makefile.am @@ -0,0 +1,5 @@ +AM_CPPFLAGS = -I$(top_srcdir)/include +EXTRA_DIST = aplaymidi2.1 + +bin_PROGRAMS = aplaymidi2 +man_MANS = aplaymidi2.1 diff --git a/seq/aplaymidi2/aplaymidi2.1 b/seq/aplaymidi2/aplaymidi2.1 new file mode 100644 index 000000000000..99be608f107d --- /dev/null +++ b/seq/aplaymidi2/aplaymidi2.1 @@ -0,0 +1,71 @@ +.TH APLAYMIDI2 1 "4 July 2024" + +.SH NAME +aplaymidi2 \- play MIDI Clip Files + +.SH SYNOPSIS +.B aplaymidi2 +\-p client:port[,...] midi2file ... + +.SH DESCRIPTION +.B aplaymidi2 +is a command-line utility that plays the specified MIDI Clip file(s) to one +or more ALSA sequencer ports. + +.SH OPTIONS + +.TP +.I \-h, \-\-help +Prints a list of options. + +.TP +.I \-V, \-\-version +Prints the current version. + +.TP +.I \-p, \-\-port=client:port,... +Sets the sequencer port(s) to which the events in the MIDI Clip file(s) are +sent. + +A client can be specified by its number, its name, or a prefix of its +name. A port is specified by its number; for port 0 of a client, the +":0" part of the port specification can be omitted. + +Multiple ports can be specified to allow playback of MIDI Clip file(s) that +contain events for multiple devices (ports) corresponding to the +multiple UMP Groups. + +For compatibility with +.B pmidi(1), +the port specification is taken from the +.I ALSA_OUTPUT_PORTS +environment variable if none is given on the command line. + +.B aplaymidi2 +supports only basic UMP events: in addition to the standard MIDI1 and +MIDI2 CVMs and 7bit SysEx, only the following are supported: +DCTPQ, DC, Set Tempo, Start Clip and End Clip. +Lyrics and other meta data in Flex Data are skipped, so far. + +The multiple output ports are useful when the given MIDI Clip file +contains the UMP packets for multiple Groups. +When the destination port is a UMP MIDI 2.0 port, the single +connection should suffice, though, since a MIDI 2.0 port can process +the inputs for multiple Groups. For other cases (e.g. connecting to a +legacy MIDI port), you would need to specify the destination port per +Group. If undefined, it's sent to the first destination port as +default. + +.TP +.I \-d, \-\-delay=seconds +Specifies how long to wait after the end of each MIDI Clip file, +to allow the last notes to die away. +Default is 2 seconds. + +.SH SEE ALSO +pmidi(1) +.br +aplaymidi(1) + +.SH AUTHOR +Takashi Iwai diff --git a/seq/aplaymidi2/aplaymidi2.c b/seq/aplaymidi2/aplaymidi2.c new file mode 100644 index 000000000000..435b7de1aa95 --- /dev/null +++ b/seq/aplaymidi2/aplaymidi2.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * aplaymidi2.c - simple player of a MIDI Clip File over ALSA sequencer + */ + +#include "aconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "version.h" + +static snd_seq_t *seq; +static int client; +static int port_count; +static snd_seq_addr_t ports[16]; +static int queue; +static int end_delay = 2; + +static unsigned int _current_tempo = 50000000; /* default 120 bpm */ +static unsigned int tempo_base = 10; +static unsigned int current_tick; + +/* 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)); +} + +/* open and initialize the sequencer client */ +static void init_seq(void) +{ + int err; + + err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0); + check_snd("open sequencer", err); + + err = snd_seq_set_client_name(seq, "aplaymidi2"); + check_snd("set client name", err); + + client = snd_seq_client_id(seq); + check_snd("get client id", client); + + err = snd_seq_set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0); + check_snd("set midi version", 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; + if (port_count > 16) + fatal("Too many ports specified"); + + 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); +} + +/* create a source port to send from */ +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, "aplaymidi2"); + + 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); +} + +/* create a queue */ +static void create_queue(void) +{ + if (!snd_seq_has_queue_tempo_base(seq)) + tempo_base = 1000; + + queue = snd_seq_alloc_named_queue(seq, "aplaymidi2"); + check_snd("create queue", queue); +} + +/* connect to destination ports */ +static void connect_ports(void) +{ + int i, err; + + 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)); + } +} + +/* read 32bit word and convert to native endian: + * return 0 on success, -1 on error + */ +static int read_word(FILE *file, uint32_t *dest) +{ + uint32_t v; + + if (fread(&v, 4, 1, file) != 1) + return -1; + *dest = be32toh(v); + return 0; +} + +/* read a UMP packet: return the number of packets, -1 on error */ +static int read_ump_packet(FILE *file, uint32_t *buf) +{ + snd_ump_msg_hdr_t *h = (snd_ump_msg_hdr_t *)buf; + + int i, num; + + if (read_word(file, buf) < 0) + return -1; + num = snd_ump_packet_length(h->type); + for (i = 1; i < num; i++) { + if (read_word(file, buf + i) < 0) + return -1; + } + return num; +} + +/* read the file header and verify it's MIDI Clip File: return 0 on success */ +static int verify_file_header(FILE *file) +{ + unsigned char buf[8]; + + if (fread(buf, 1, 8, file) != 8) + return -1; + if (memcmp(buf, "SMF2CLIP", 8)) + return -1; + return 0; +} + +/* return the current tempo, corrected to be sent to host */ +static int current_tempo(void) +{ + if (tempo_base != 10) + return _current_tempo / 100; /* down to us */ + return _current_tempo; +} + +/* send a timer event */ +static void send_timer_event(unsigned int type, unsigned int val) +{ + snd_seq_ump_event_t ev = { + .type = type, + .flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_EVENT_LENGTH_FIXED, + }; + + ev.queue = queue; + ev.source.port = 0; + ev.time.tick = current_tick; + + 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 = val; + + snd_seq_ump_event_output(seq, &ev); +} + +/* set DCTPQ */ +static void set_dctpq(unsigned int ppq) +{ + snd_seq_queue_tempo_t *queue_tempo; + + snd_seq_queue_tempo_alloca(&queue_tempo); + snd_seq_queue_tempo_set_tempo(queue_tempo, current_tempo()); + snd_seq_queue_tempo_set_ppq(queue_tempo, ppq); + snd_seq_queue_tempo_set_tempo_base(queue_tempo, tempo_base); + + if (snd_seq_set_queue_tempo(seq, queue, queue_tempo) < 0) + errormsg("Cannot set queue tempo (%d)", queue); +} + +/* set DC */ +static void set_dc(unsigned int ticks) +{ + current_tick += ticks; +} + +/* set tempo event */ +static void set_tempo(unsigned int tempo) +{ + _current_tempo = tempo; + send_timer_event(SND_SEQ_EVENT_TEMPO, current_tempo()); +} + +/* start clip */ +static void start_clip(void) +{ + if (snd_seq_start_queue(seq, queue, NULL) < 0) + errormsg("Cannot start queue (%d)", queue); +} + +/* end clip */ +static void end_clip(void) +{ + send_timer_event(SND_SEQ_EVENT_STOP, 0); +} + +/* send a UMP packet */ +static void send_ump(const uint32_t *ump, int len) +{ + snd_seq_ump_event_t ev = { + .flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_EVENT_LENGTH_FIXED | + SND_SEQ_EVENT_UMP, + }; + int group; + + memcpy(ev.ump, ump, len * 4); + + ev.queue = queue; + ev.source.port = 0; + ev.time.tick = current_tick; + group = snd_ump_msg_group(ump); + if (group >= port_count) + ev.dest = ports[0]; + else + ev.dest = ports[group]; + + snd_seq_ump_event_output(seq, &ev); +} + +/* play the given MIDI Clip File content */ +static void play_midi(FILE *file) +{ + uint32_t ump[4]; + int len; + + current_tick = 0; + + while ((len = read_ump_packet(file, ump)) > 0) { + const snd_ump_msg_hdr_t *h = (snd_ump_msg_hdr_t *)ump; + + if (h->type == SND_UMP_MSG_TYPE_UTILITY) { + const snd_ump_msg_utility_t *uh = + (const snd_ump_msg_utility_t *)ump; + switch (h->status) { + case SND_UMP_UTILITY_MSG_STATUS_DCTPQ: + set_dctpq(uh->dctpq.ticks); + continue; + case SND_UMP_UTILITY_MSG_STATUS_DC: + set_dc(uh->dctpq.ticks); + continue; + } + } else if (h->type == SND_UMP_MSG_TYPE_FLEX_DATA) { + const snd_ump_msg_flex_data_t *fh = + (const snd_ump_msg_flex_data_t *)ump; + if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP && + fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_TEMPO) { + set_tempo(fh->set_tempo.tempo); + continue; + } + } else if (h->type == SND_UMP_MSG_TYPE_STREAM) { + const snd_ump_msg_stream_t *sh = + (const snd_ump_msg_stream_t *)ump; + switch (sh->gen.status) { + case SND_UMP_STREAM_MSG_STATUS_START_CLIP: + start_clip(); + continue; + case SND_UMP_STREAM_MSG_STATUS_END_CLIP: + end_clip(); + continue; + } + } else if (h->type == SND_UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE || + h->type == SND_UMP_MSG_TYPE_DATA || + h->type == SND_UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE) { + send_ump(ump, len); + } + } + + snd_seq_drain_output(seq); + snd_seq_sync_output_queue(seq); + + /* give the last notes time to die away */ + if (end_delay > 0) + sleep(end_delay); +} + +static void play_file(const char *file_name) +{ + FILE *file; + + if (!strcmp(file_name, "-")) + file = stdin; + else + file = fopen(file_name, "rb"); + if (!file) { + errormsg("Cannot open %s - %s", file_name, strerror(errno)); + return; + } + + if (verify_file_header(file) < 0) { + errormsg("%s is not a MIDI Clip File", file_name); + goto error; + } + + play_midi(file); + + error: + if (file != stdin) + fclose(file); +} + +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" + "-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("aplaymidi2 version " SND_UTIL_VERSION_STR); +} + +int main(int argc, char *argv[]) +{ + static const struct option long_options[] = { + {"help", 0, NULL, 'h'}, + {"version", 0, NULL, 'V'}, + {"port", 1, NULL, 'p'}, + {"delay", 1, NULL, 'd'}, + {0} + }; + int c; + + init_seq(); + + while ((c = getopt_long(argc, argv, "hVp:d:", + long_options, NULL)) != -1) { + switch (c) { + case 'h': + usage(argv[0]); + return 0; + case 'V': + version(); + return 0; + case 'p': + parse_ports(optarg); + break; + case 'd': + end_delay = atoi(optarg); + break; + default: + usage(argv[0]); + return 1; + } + } + + + 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++) + play_file(argv[optind]); + + snd_seq_close(seq); + return 0; +} From patchwork Sat Jul 6 07:42:28 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 811133 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 81BE5C2BD09 for ; Sat, 6 Jul 2024 07:46:57 +0000 (UTC) Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 84384F54; Sat, 6 Jul 2024 09:46:45 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 84384F54 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1720252015; bh=NhxqSolfEx3PxzgeuRE+cmdVyDaWHgQEDjzpe+aF4Eo=; h=From:To:Subject:Date:In-Reply-To:References:List-Id:List-Archive: List-Help:List-Owner:List-Post:List-Subscribe:List-Unsubscribe: From; b=nxpkVrFTMAqVqDIJQmbhIjrgEjgsmc8UBYZ07ynlDcSPT5o33Ht6VsM8X7kCd9gQi YlfCN6Ks+aQDaVdHUAmGmP61efmvHJqRGzg+213HBVIm4AgP3g7Hata+bJpZKF5ahu z2RLpJhKXz5RcfQjZm46yfNPczGYJlDorObxDBEE= Received: by alsa1.perex.cz (Postfix, from userid 50401) id 6C4AEF805F3; Sat, 6 Jul 2024 09:45:53 +0200 (CEST) Received: from mailman-core.alsa-project.org (mailman-core.alsa-project.org [10.254.200.10]) by alsa1.perex.cz (Postfix) with ESMTP id 8677AF80603; Sat, 6 Jul 2024 09:45:52 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id C3F3FF80272; Sat, 6 Jul 2024 09:42:27 +0200 (CEST) Received: from smtp-out1.suse.de (smtp-out1.suse.de [195.135.223.130]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 79D6AF800F8 for ; Sat, 6 Jul 2024 09:42:05 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 79D6AF800F8 Authentication-Results: alsa1.perex.cz; dkim=pass (1024-bit key, unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=XmDI8I1M; dkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=dxi0jqnR; dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=XmDI8I1M; dkim=neutral header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=dxi0jqnR Received: from imap1.dmz-prg2.suse.org (unknown [10.150.64.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out1.suse.de (Postfix) with ESMTPS id 738AA2115D; Sat, 6 Jul 2024 07:42:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=QDDLF9T0yXhO03nMxbF1t+nbgVY/qneMMu309/pau8A=; b=XmDI8I1M+khDOcZOQJ895ABL84KxE/pksxFp+mhqBCFO/QPOWWjchPsYuL3K6ZZYMXMpxT R3fXk80F8tR+bI8kuqX+OgMc63+KyrRU9iQJ7OY4bOLqnO1o4/sN7tmlq1s+UIwrS6MuUv zIIsV/Z9vxPoHcZ935QZa6Cr86WoCU0= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=QDDLF9T0yXhO03nMxbF1t+nbgVY/qneMMu309/pau8A=; b=dxi0jqnRSAFAnraWje/U80Gt2V0RpsrfJ8QQh9oD1n6WDdrEDGLqmXDxTTrVSuEnWd/bE0 z6hsfLKxMgxEjGDw== Authentication-Results: smtp-out1.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=QDDLF9T0yXhO03nMxbF1t+nbgVY/qneMMu309/pau8A=; b=XmDI8I1M+khDOcZOQJ895ABL84KxE/pksxFp+mhqBCFO/QPOWWjchPsYuL3K6ZZYMXMpxT R3fXk80F8tR+bI8kuqX+OgMc63+KyrRU9iQJ7OY4bOLqnO1o4/sN7tmlq1s+UIwrS6MuUv zIIsV/Z9vxPoHcZ935QZa6Cr86WoCU0= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=QDDLF9T0yXhO03nMxbF1t+nbgVY/qneMMu309/pau8A=; b=dxi0jqnRSAFAnraWje/U80Gt2V0RpsrfJ8QQh9oD1n6WDdrEDGLqmXDxTTrVSuEnWd/bE0 z6hsfLKxMgxEjGDw== Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id 570A913A7B; Sat, 6 Jul 2024 07:42:05 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id 4ELYE031iGZRDgAAD6G6ig (envelope-from ); Sat, 06 Jul 2024 07:42:05 +0000 From: Takashi Iwai To: alsa-devel@alsa-project.org Subject: [PATCH alsa-utils 2/5] arecordmidi2: Add initial version Date: Sat, 6 Jul 2024 09:42:28 +0200 Message-ID: <20240706074232.6364-2-tiwai@suse.de> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240706074232.6364-1-tiwai@suse.de> References: <20240706074232.6364-1-tiwai@suse.de> MIME-Version: 1.0 X-Spamd-Result: default: False [-2.80 / 50.00]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; FUZZY_BLOCKED(0.00)[rspamd.com]; RCVD_VIA_SMTP_AUTH(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; ARC_NA(0.00)[]; DKIM_SIGNED(0.00)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.de:email]; FROM_EQ_ENVFROM(0.00)[]; FROM_HAS_DN(0.00)[]; MIME_TRACE(0.00)[0:+]; RCVD_COUNT_TWO(0.00)[2]; TO_MATCH_ENVRCPT_ALL(0.00)[]; TO_DN_NONE(0.00)[]; RCVD_TLS_ALL(0.00)[] Message-ID-Hash: PSUV7M27PDQQWKI6OLAVUXZ4T5QL2BK2 X-Message-ID-Hash: PSUV7M27PDQQWKI6OLAVUXZ4T5QL2BK2 X-MailFrom: tiwai@suse.de X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-alsa-devel.alsa-project.org-0; header-match-alsa-devel.alsa-project.org-1; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.9 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: arecordmidi2 is a similar program like arecordmidi for recording the incoming MIDI events, but storing in a MIDI Clip file for MIDI 2.0. Most options are kept from arecordmidi, but some are dropped: namely, the -l, -m and -f options are dropped for code simplicity. Also -s option is dropped as well, as there is no need for split for MIDI Clip file unlike SMF. Signed-off-by: Takashi Iwai --- seq/aplaymidi2/Makefile.am | 6 +- seq/aplaymidi2/arecordmidi2.1 | 73 +++++ seq/aplaymidi2/arecordmidi2.c | 512 ++++++++++++++++++++++++++++++++++ 3 files changed, 588 insertions(+), 3 deletions(-) create mode 100644 seq/aplaymidi2/arecordmidi2.1 create mode 100644 seq/aplaymidi2/arecordmidi2.c diff --git a/seq/aplaymidi2/Makefile.am b/seq/aplaymidi2/Makefile.am index 0c5c743f7eb8..8985382efe82 100644 --- a/seq/aplaymidi2/Makefile.am +++ b/seq/aplaymidi2/Makefile.am @@ -1,5 +1,5 @@ AM_CPPFLAGS = -I$(top_srcdir)/include -EXTRA_DIST = aplaymidi2.1 +EXTRA_DIST = aplaymidi2.1 arecordmidi2.1 -bin_PROGRAMS = aplaymidi2 -man_MANS = aplaymidi2.1 +bin_PROGRAMS = aplaymidi2 arecordmidi2 +man_MANS = aplaymidi2.1 arecordmidi2.1 diff --git a/seq/aplaymidi2/arecordmidi2.1 b/seq/aplaymidi2/arecordmidi2.1 new file mode 100644 index 000000000000..0e41a300b553 --- /dev/null +++ b/seq/aplaymidi2/arecordmidi2.1 @@ -0,0 +1,73 @@ +.TH ARECORDMIDI2 1 "4 July 2024" + +.SH NAME +arecordmidi2 \- record a MIDI Clip file + +.SH SYNOPSIS +.B arecordmidi2 +\-p client:port[,...] [options] midi2file + +.SH DESCRIPTION +.B arecordmidi2 +is a command-line utility that records a MIDI Clip file from one or +more ALSA sequencer ports. + +To stop recording, press Ctrl+C. + +.SH OPTIONS + +.TP +.I \-h,\-\-help +Prints a list of options. + +.TP +.I \-V,\-\-version +Prints the current version. + +.TP +.I \-p,\-\-port=client:port,... +Sets the sequencer port(s) from which events are recorded. + +A client can be specified by its number, its name, or a prefix of its +name. A port is specified by its number; for port 0 of a client, the +":0" part of the port specification can be omitted. + +.TP +.I \-b,\-\-bpm=beats +Sets the musical tempo of the MIDI file, in beats per minute. +The default value is 120 BPM. + +.TP +.I \-t,\-\-ticks=ticks +Sets the resolution of timestamps (ticks) in the MIDI file, +in ticks per beat. +The default value is 384 ticks/beat. + +.TP +.I \-i,\-\-timesig=numerator:denominator +Sets the time signature for the MIDI file. + +The time signature is specified as usual with two numbers, representing +the numerator and denominator of the time signature as it would be +notated. The denominator must be a power of two. Both numbers should be +separated by a colon. The time signature is 4:4 by default. + +.TP +.I \-n,\-\-num-events=events +Stops the recording after receiving the given number of events. + +.TP +.I \-u,\-\-ump=version +Sets the UMP MIDI protocol version. Either 1 or 2 has to be given for +MIDI 1.0 and MIDI 2.0 protocol, respectively. +Default is 1. + +.SH SEE ALSO +arecordmidi(1) +.br +aplaymidi2(1) + +.SH AUTHOR +Takashi Iwai + + diff --git a/seq/aplaymidi2/arecordmidi2.c b/seq/aplaymidi2/arecordmidi2.c new file mode 100644 index 000000000000..32693854b7a8 --- /dev/null +++ b/seq/aplaymidi2/arecordmidi2.c @@ -0,0 +1,512 @@ +/* + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aconfig.h" +#include "version.h" + +static snd_seq_t *seq; +static int client; +static int port_count; +static snd_seq_addr_t *ports; +static int queue; +static int midi_version = 1; +static int beats = 120; +static int ticks = 384; +static int tempo_base = 10; +static volatile sig_atomic_t stop; +static int ts_num = 4; /* time signature: numerator */ +static int ts_div = 4; /* time signature: denominator */ +static int last_tick; + +/* Parse a decimal number from a command line argument. */ +static long arg_parse_decimal_num(const char *str, int *err) +{ + long val; + char *endptr; + + errno = 0; + val = strtol(str, &endptr, 0); + if (errno > 0) { + *err = -errno; + return 0; + } + if (*endptr != '\0') { + *err = -EINVAL; + return 0; + } + + return val; +} + +/* 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)); +} + +/* open a sequencer client */ +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 up UMP virtual client/port */ +static void create_ump_client(void) +{ + snd_ump_endpoint_info_t *ep; + snd_ump_block_info_t *blk; + snd_seq_port_info_t *pinfo; + int i, err; + + /* create a UMP Endpoint */ + snd_ump_endpoint_info_alloca(&ep); + snd_ump_endpoint_info_set_name(ep, "arecordmidi2"); + if (midi_version == 1) { + snd_ump_endpoint_info_set_protocol_caps(ep, SND_UMP_EP_INFO_PROTO_MIDI1); + snd_ump_endpoint_info_set_protocol(ep, SND_UMP_EP_INFO_PROTO_MIDI1); + } else { + snd_ump_endpoint_info_set_protocol_caps(ep, SND_UMP_EP_INFO_PROTO_MIDI2); + snd_ump_endpoint_info_set_protocol(ep, SND_UMP_EP_INFO_PROTO_MIDI2); + } + snd_ump_endpoint_info_set_num_blocks(ep, port_count); + + err = snd_seq_create_ump_endpoint(seq, ep, port_count); + check_snd("create UMP endpoint", err); + + /* create UMP Function Blocks */ + snd_ump_block_info_alloca(&blk); + for (i = 0; i < port_count; i++) { + char blkname[32]; + + sprintf(blkname, "Group %d", i + 1); + snd_ump_block_info_set_name(blk, blkname); + snd_ump_block_info_set_direction(blk, SND_UMP_DIR_INPUT); + snd_ump_block_info_set_first_group(blk, i); + snd_ump_block_info_set_num_groups(blk, 1); + snd_ump_block_info_set_ui_hint(blk, SND_UMP_BLOCK_UI_HINT_RECEIVER); + + err = snd_seq_create_ump_block(seq, i, blk); + check_snd("create UMP block", err); + } + + /* toggle timestamping for all input ports */ + snd_seq_port_info_alloca(&pinfo); + for (i = 0; i <= port_count; i++) { + err = snd_seq_get_port_info(seq, i, pinfo); + check_snd("get port info", err); + snd_seq_port_info_set_timestamping(pinfo, 1); + snd_seq_port_info_set_timestamp_queue(pinfo, queue); + snd_seq_set_port_info(seq, i, pinfo); + check_snd("set port info", 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 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; +} + +/* create a queue, set up the default tempo */ +static void create_queue(void) +{ + snd_seq_queue_tempo_t *tempo; + + if (!snd_seq_has_queue_tempo_base(seq)) + tempo_base = 1000; + + queue = snd_seq_alloc_named_queue(seq, "arecordmidi2"); + check_snd("create queue", queue); + + snd_seq_queue_tempo_alloca(&tempo); + if (tempo_base == 1000) + snd_seq_queue_tempo_set_tempo(tempo, 60000000 / beats); + else + snd_seq_queue_tempo_set_tempo(tempo, (unsigned int)(6000000000ULL / beats)); + snd_seq_queue_tempo_set_ppq(tempo, ticks); + snd_seq_queue_tempo_set_tempo_base(tempo, tempo_base); + if (snd_seq_set_queue_tempo(seq, queue, tempo) < 0) + fatal("Cannot set queue tempo (%d)", queue); +} + +/* connect to the input ports */ +static void connect_ports(void) +{ + int i, err; + + for (i = 0; i < port_count; ++i) { + err = snd_seq_connect_from(seq, i + 1, + ports[i].client, ports[i].port); + check_snd("port connection", err); + } +} + +/* write the given UMP packet */ +static void write_ump(FILE *file, const void *src) +{ + const snd_ump_msg_hdr_t *h = src; + const uint32_t *p = src; + uint32_t v; + int len; + + len = snd_ump_packet_length(h->type); + while (len-- > 0) { + v = htobe32(*p++); + fwrite(&v, 4, 1, file); + } +} + +/* write a DC message */ +static void write_dcs(FILE *file, unsigned int t) +{ + snd_ump_msg_dc_t d = {}; + + d.type = SND_UMP_MSG_TYPE_UTILITY; + d.status = SND_UMP_UTILITY_MSG_STATUS_DC; + d.ticks = t; + write_ump(file, &d); +} + +/* write a DCTPQ message */ +static void write_dctpq(FILE *file) +{ + snd_ump_msg_dctpq_t d = {}; + + d.type = SND_UMP_MSG_TYPE_UTILITY; + d.status = SND_UMP_UTILITY_MSG_STATUS_DCTPQ; + d.ticks = ticks; + write_ump(file, &d); +} + +/* write a Start Clip message */ +static void write_start_clip(FILE *file) +{ + snd_ump_msg_stream_gen_t d = {}; + + d.type = SND_UMP_MSG_TYPE_STREAM; + d.status = SND_UMP_STREAM_MSG_STATUS_START_CLIP; + write_ump(file, &d); +} + +/* write an End Clip message */ +static void write_end_clip(FILE *file) +{ + snd_ump_msg_stream_gen_t d = {}; + + d.type = SND_UMP_MSG_TYPE_STREAM; + d.status = SND_UMP_STREAM_MSG_STATUS_END_CLIP; + write_ump(file, &d); +} + +/* write a Set Tempo message */ +static void write_tempo(FILE *file) +{ + snd_ump_msg_set_tempo_t d = {}; + + d.type = SND_UMP_MSG_TYPE_FLEX_DATA; + d.group = 0; + d.format = SND_UMP_FLEX_DATA_MSG_FORMAT_SINGLE; + d.addrs = SND_UMP_FLEX_DATA_MSG_ADDR_GROUP; + d.status_bank = SND_UMP_FLEX_DATA_MSG_BANK_SETUP; + d.status = SND_UMP_FLEX_DATA_MSG_STATUS_SET_TEMPO; + d.tempo = (unsigned int)(6000000000ULL / beats); + write_ump(file, &d); +} + +/* write a Set Time Signature message */ +static void write_time_sig(FILE *file) +{ + snd_ump_msg_set_time_sig_t d = {}; + + d.type = SND_UMP_MSG_TYPE_FLEX_DATA; + d.group = 0; + d.format = SND_UMP_FLEX_DATA_MSG_FORMAT_SINGLE; + d.addrs = SND_UMP_FLEX_DATA_MSG_ADDR_GROUP; + d.status_bank = SND_UMP_FLEX_DATA_MSG_BANK_SETUP; + d.status = SND_UMP_FLEX_DATA_MSG_STATUS_SET_TIME_SIGNATURE; + d.numerator = ts_num; + d.denominator = ts_div; + d.num_notes = 8; + write_ump(file, &d); +} + +/* record the delta time from the last event */ +static void delta_time(FILE *file, const snd_seq_ump_event_t *ev) +{ + int diff = ev->time.tick - last_tick; + + if (diff <= 0) + return; + if (tempo_base == 1000) + diff *= 100; + write_dcs(file, diff); + last_tick = ev->time.tick; +} + +static void record_event(FILE *file, const snd_seq_ump_event_t *ev) +{ + /* ignore events without proper timestamps */ + if (ev->queue != queue || !snd_seq_ev_is_tick(ev) || + !snd_seq_ev_is_ump(ev)) + return; + + delta_time(file, ev); + write_ump(file, ev->ump); +} + +/* write MIDI Clip file header and the configuration packets */ +static void write_file_header(FILE *file) +{ + /* header id */ + fwrite("SMF2CLIP", 1, 8, file); + + /* clip configuration header */ + /* FIXME: add profiles */ + + /* first DCS */ + write_dcs(file, 0); + write_dctpq(file); + + /* start bar */ + write_start_clip(file); + write_tempo(file); + write_time_sig(file); +} + +static void help(const char *argv0) +{ + fprintf(stderr, "Usage: %s [options] outputfile\n" + "\nAvailable options:\n" + " -h,--help this help\n" + " -V,--version show version\n" + " -p,--port=client:port,... source port(s)\n" + " -b,--bpm=beats tempo in beats per minute\n" + " -t,--ticks=ticks resolution in ticks per beat or frame\n" + " -i,--timesig=nn:dd time signature\n" + " -n,--num-events=events fixed number of events to record, then exit\n" + " -u,--ump=version UMP MIDI version (1 or 2)\n", + argv0); +} + +static void version(void) +{ + fputs("arecordmidi version " SND_UTIL_VERSION_STR "\n", stderr); +} + +static void sighandler(int sig ATTRIBUTE_UNUSED) +{ + stop = 1; +} + +int main(int argc, char *argv[]) +{ + static const char short_options[] = "hVp:b:t:n:u:"; + static const struct option long_options[] = { + {"help", 0, NULL, 'h'}, + {"version", 0, NULL, 'V'}, + {"port", 1, NULL, 'p'}, + {"bpm", 1, NULL, 'b'}, + {"ticks", 1, NULL, 't'}, + {"timesig", 1, NULL, 'i'}, + {"num-events", 1, NULL, 'n'}, + {"ump", 1, NULL, 'u'}, + {0} + }; + + char *filename; + FILE *file; + struct pollfd *pfds; + int npfds; + int c, err; + /* If |num_events| isn't specified, leave it at 0. */ + long num_events = 0; + long events_received = 0; + + init_seq(); + + while ((c = getopt_long(argc, argv, short_options, + long_options, NULL)) != -1) { + switch (c) { + case 'h': + help(argv[0]); + return 0; + case 'V': + version(); + return 0; + case 'p': + parse_ports(optarg); + break; + case 'b': + beats = atoi(optarg); + if (beats < 4 || beats > 6000) + fatal("Invalid tempo"); + break; + case 't': + ticks = atoi(optarg); + if (ticks < 1 || ticks > 0x7fff) + fatal("Invalid number of ticks"); + break; + case 'i': + time_signature(optarg); + break; + case 'n': + err = 0; + num_events = arg_parse_decimal_num(optarg, &err); + if (err != 0) { + fatal("Couldn't parse num_events argument: %s\n", + strerror(-err)); + } + if (num_events <= 0) + fatal("num_events must be greater than 0"); + break; + case 'u': + midi_version = atoi(optarg); + if (midi_version != 1 && midi_version != 2) + fatal("Invalid MIDI version %d\n", midi_version); + break; + default: + help(argv[0]); + return 1; + } + } + + if (port_count < 1) { + fputs("Pleast specify a source port with --port.\n", stderr); + return 1; + } + + if (optind >= argc) { + fputs("Please specify a file to record to.\n", stderr); + return 1; + } + + create_queue(); + create_ump_client(); + connect_ports(); + + filename = argv[optind]; + + file = fopen(filename, "wb"); + if (!file) + fatal("Cannot open %s - %s", filename, strerror(errno)); + + write_file_header(file); + + err = snd_seq_start_queue(seq, queue, NULL); + check_snd("start queue", err); + snd_seq_drain_output(seq); + + err = snd_seq_nonblock(seq, 1); + check_snd("set nonblock mode", err); + + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); + + npfds = snd_seq_poll_descriptors_count(seq, POLLIN); + pfds = alloca(sizeof(*pfds) * npfds); + for (;;) { + snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN); + if (poll(pfds, npfds, -1) < 0) + break; + do { + snd_seq_ump_event_t *event; + + err = snd_seq_ump_event_input(seq, &event); + if (err < 0) + break; + if (event) { + record_event(file, event); + events_received++; + } + } while (err > 0); + if (stop) + break; + if (num_events && (events_received >= num_events)) + break; + } + + if (num_events && events_received < num_events) + fputs("Warning: Received signal before num_events\n", stdout); + + write_end_clip(file); + fclose(file); + snd_seq_close(seq); + return 0; +} From patchwork Sat Jul 6 07:42:29 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 811134 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 1B29AC2BD09 for ; Sat, 6 Jul 2024 07:46:21 +0000 (UTC) Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id D92E414E4; Sat, 6 Jul 2024 09:46:08 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz D92E414E4 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1720251978; bh=+TDC+jSyLd3VvBm9rpggztCVQ2zEigPAgZKKinH2UJE=; h=From:To:Subject:Date:In-Reply-To:References:List-Id:List-Archive: List-Help:List-Owner:List-Post:List-Subscribe:List-Unsubscribe: From; b=mT9wWvAM2x2oIP3h2NnXvdc4KLEB9H5vPNUlhemsY/mCiJOBfsXO8EAb0GYyNa552 Cxj5KnavjVNAV8Ss3MoeHSveWipHECXdbWAM8MWtEzJVow0TKDWWwaTfexNGuk8XgV kaYP735/7jcgd8SFnTEnvSssS8UKejEVvBnfZFnU= Received: by alsa1.perex.cz (Postfix, from userid 50401) id EB3E1F805AD; Sat, 6 Jul 2024 09:45:47 +0200 (CEST) Received: from mailman-core.alsa-project.org (mailman-core.alsa-project.org [10.254.200.10]) by alsa1.perex.cz (Postfix) with ESMTP id 25C2AF805B6; Sat, 6 Jul 2024 09:45:47 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 6F8DDF80508; Sat, 6 Jul 2024 09:42:10 +0200 (CEST) Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id AD02CF8014C for ; Sat, 6 Jul 2024 09:42:06 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz AD02CF8014C Authentication-Results: alsa1.perex.cz; dkim=pass (1024-bit key, unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=hZgwdmCz; dkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=IkhEDCrR; dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=hZgwdmCz; dkim=neutral header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=IkhEDCrR Received: from imap1.dmz-prg2.suse.org (imap1.dmz-prg2.suse.org [IPv6:2a07:de40:b281:104:10:150:64:97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 9DD2E1F80E; Sat, 6 Jul 2024 07:42:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=51sz6ZyXGAZRWYBQOVTmB2dfKX8jPgSH4LF1j9sfPyM=; b=hZgwdmCz77i6TPvd/RaojjIzr18cf4oNtwn/dcbXjVWDRxzL5hLM3obUrkajmvqRLyAJde DsKnArkZLkJFrswNJPHD75Lr8X+32maQ/cDwwD8+nmNzcTUQbA/jxUbrM1zFahtEXEosdg Ijg3P8JxCt79OIvYLs6Teqahou/kQlo= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=51sz6ZyXGAZRWYBQOVTmB2dfKX8jPgSH4LF1j9sfPyM=; b=IkhEDCrR33IbyiBLwHCQId3AN7ulqjZcmgo22pOCJcp/7YEC7NpAkgysPbcqyatzaKae+l YYoK7YKwZtetOtCA== Authentication-Results: smtp-out2.suse.de; dkim=pass header.d=suse.de header.s=susede2_rsa header.b=hZgwdmCz; dkim=pass header.d=suse.de header.s=susede2_ed25519 header.b=IkhEDCrR DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=51sz6ZyXGAZRWYBQOVTmB2dfKX8jPgSH4LF1j9sfPyM=; b=hZgwdmCz77i6TPvd/RaojjIzr18cf4oNtwn/dcbXjVWDRxzL5hLM3obUrkajmvqRLyAJde DsKnArkZLkJFrswNJPHD75Lr8X+32maQ/cDwwD8+nmNzcTUQbA/jxUbrM1zFahtEXEosdg Ijg3P8JxCt79OIvYLs6Teqahou/kQlo= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=51sz6ZyXGAZRWYBQOVTmB2dfKX8jPgSH4LF1j9sfPyM=; b=IkhEDCrR33IbyiBLwHCQId3AN7ulqjZcmgo22pOCJcp/7YEC7NpAkgysPbcqyatzaKae+l YYoK7YKwZtetOtCA== Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id 76F1413A82; Sat, 6 Jul 2024 07:42:05 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id +CCtG031iGZRDgAAD6G6ig (envelope-from ); Sat, 06 Jul 2024 07:42:05 +0000 From: Takashi Iwai To: alsa-devel@alsa-project.org Subject: [PATCH alsa-utils 3/5] .gitignore: Add aplaymidi2 and arecordmidi2 Date: Sat, 6 Jul 2024 09:42:29 +0200 Message-ID: <20240706074232.6364-3-tiwai@suse.de> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240706074232.6364-1-tiwai@suse.de> References: <20240706074232.6364-1-tiwai@suse.de> MIME-Version: 1.0 X-Rspamd-Queue-Id: 9DD2E1F80E X-Spamd-Result: default: False [-1.91 / 50.00]; BAYES_HAM(-1.90)[94.43%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; R_DKIM_ALLOW(-0.20)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; MIME_GOOD(-0.10)[text/plain]; MX_GOOD(-0.01)[]; TO_DN_NONE(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; FROM_EQ_ENVFROM(0.00)[]; ARC_NA(0.00)[]; FROM_HAS_DN(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; TO_MATCH_ENVRCPT_ALL(0.00)[]; RCVD_TLS_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.de:email,suse.de:dkim]; DKIM_SIGNED(0.00)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; FUZZY_BLOCKED(0.00)[rspamd.com]; RCVD_COUNT_TWO(0.00)[2]; DWL_DNSWL_BLOCKED(0.00)[suse.de:dkim]; MIME_TRACE(0.00)[0:+]; DKIM_TRACE(0.00)[suse.de:+] X-Rspamd-Action: no action X-Rspamd-Server: rspamd1.dmz-prg2.suse.org Message-ID-Hash: NZOMW6D7WZ5L5GC77H2QNK7K4IVHN5RW X-Message-ID-Hash: NZOMW6D7WZ5L5GC77H2QNK7K4IVHN5RW X-MailFrom: tiwai@suse.de X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-alsa-devel.alsa-project.org-0; header-match-alsa-devel.alsa-project.org-1; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.9 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Signed-off-by: Takashi Iwai --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b61e1df34b11..7a5817f253d7 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,8 @@ iecset/iecset seq/aconnect/aconnect seq/aplaymidi/aplaymidi seq/aplaymidi/arecordmidi +seq/aplaymidi2/aplaymidi2 +seq/aplaymidi2/arecordmidi2 seq/aseqdump/aseqdump seq/aseqsend/aseqsend seq/aseqnet/aseqnet From patchwork Sat Jul 6 07:42:30 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 811132 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 72AD9C38150 for ; Sat, 6 Jul 2024 07:47:28 +0000 (UTC) Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 6DCB3E85; Sat, 6 Jul 2024 09:47:16 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 6DCB3E85 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1720252046; bh=z1sximtOxEql/sQW6cyI5na33cauJ8UaPU3ne9B7JBw=; h=From:To:Subject:Date:In-Reply-To:References:List-Id:List-Archive: List-Help:List-Owner:List-Post:List-Subscribe:List-Unsubscribe: From; b=mnAQAL2xF6/HLRhHH0FqBBTWatzRWIu5OrgQaN/PNgft51q2Bsmne83JU2ppE4zwl mebJ5xmZmXl9IXjJQ5FlWI6l6b1tRZsy/VaR0k93qMltH+7dvRO8qkSiFjhDUuC7SW 09PrnfHnf0djRz7n3AYcl2jpauS53pn2dnnNXkTk= Received: by alsa1.perex.cz (Postfix, from userid 50401) id 9B957F80587; Sat, 6 Jul 2024 09:45:58 +0200 (CEST) Received: from mailman-core.alsa-project.org (mailman-core.alsa-project.org [10.254.200.10]) by alsa1.perex.cz (Postfix) with ESMTP id E2F74F8063C; Sat, 6 Jul 2024 09:45:57 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id D066FF8014C; Sat, 6 Jul 2024 09:44:47 +0200 (CEST) Received: from smtp-out1.suse.de (smtp-out1.suse.de [IPv6:2a07:de40:b251:101:10:150:64:1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 8D53EF80301 for ; Sat, 6 Jul 2024 09:42:07 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 8D53EF80301 Authentication-Results: alsa1.perex.cz; dkim=pass (1024-bit key, unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=q28uTP0R; dkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=iWGgh23+; dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=q28uTP0R; dkim=neutral header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=iWGgh23+ Received: from imap1.dmz-prg2.suse.org (imap1.dmz-prg2.suse.org [IPv6:2a07:de40:b281:104:10:150:64:97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out1.suse.de (Postfix) with ESMTPS id D6FCE21A5A; Sat, 6 Jul 2024 07:42:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=5k1S2jNYdelkOuiI1PFEudxF5ceVrh3wcMIgj/IqBqc=; b=q28uTP0Rcfu6YOMz1oS9mR9I1mlOa0nOdtqxrWlTVrp/wjE9qDJ0+kSplofsLz6xxlIpcb 8B2GaMTAY+4zHvivwsZSPuHJFkUyLRSF5J9p+6WczEYYCRg4NrrZQDZ0Ve8jUheyN8kkxv Fzl7aypcrl1braZLfRWPr6j0UD1vMz4= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=5k1S2jNYdelkOuiI1PFEudxF5ceVrh3wcMIgj/IqBqc=; b=iWGgh23+vsx7CQkMyzIt1YEGql7b1DKYylFous8HeAOkNfnfdfs3kb83flGPLSETs+Y2sQ u4E3iQXXr49vPfBg== Authentication-Results: smtp-out1.suse.de; dkim=pass header.d=suse.de header.s=susede2_rsa header.b=q28uTP0R; dkim=pass header.d=suse.de header.s=susede2_ed25519 header.b=iWGgh23+ DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=5k1S2jNYdelkOuiI1PFEudxF5ceVrh3wcMIgj/IqBqc=; b=q28uTP0Rcfu6YOMz1oS9mR9I1mlOa0nOdtqxrWlTVrp/wjE9qDJ0+kSplofsLz6xxlIpcb 8B2GaMTAY+4zHvivwsZSPuHJFkUyLRSF5J9p+6WczEYYCRg4NrrZQDZ0Ve8jUheyN8kkxv Fzl7aypcrl1braZLfRWPr6j0UD1vMz4= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1720251725; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=5k1S2jNYdelkOuiI1PFEudxF5ceVrh3wcMIgj/IqBqc=; b=iWGgh23+vsx7CQkMyzIt1YEGql7b1DKYylFous8HeAOkNfnfdfs3kb83flGPLSETs+Y2sQ u4E3iQXXr49vPfBg== Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id A26E112FF6; Sat, 6 Jul 2024 07:42:05 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id 8JleJk31iGZRDgAAD6G6ig (envelope-from ); Sat, 06 Jul 2024 07:42:05 +0000 From: Takashi Iwai To: alsa-devel@alsa-project.org Subject: [PATCH alsa-utils 4/5] .gitignore: Add stale files for topology builds Date: Sat, 6 Jul 2024 09:42:30 +0200 Message-ID: <20240706074232.6364-4-tiwai@suse.de> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240706074232.6364-1-tiwai@suse.de> References: <20240706074232.6364-1-tiwai@suse.de> MIME-Version: 1.0 X-Spamd-Result: default: False [-2.21 / 50.00]; BAYES_HAM(-2.20)[96.18%]; NEURAL_HAM_LONG(-1.00)[-1.000]; MID_CONTAINS_FROM(1.00)[]; R_MISSING_CHARSET(0.50)[]; R_DKIM_ALLOW(-0.20)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; MX_GOOD(-0.01)[]; TO_DN_NONE(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; FROM_EQ_ENVFROM(0.00)[]; ARC_NA(0.00)[]; FROM_HAS_DN(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; TO_MATCH_ENVRCPT_ALL(0.00)[]; FUZZY_BLOCKED(0.00)[rspamd.com]; RCVD_TLS_ALL(0.00)[]; RCVD_COUNT_TWO(0.00)[2]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.de:email,suse.de:dkim]; DKIM_SIGNED(0.00)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; MIME_TRACE(0.00)[0:+]; DKIM_TRACE(0.00)[suse.de:+] X-Rspamd-Action: no action X-Rspamd-Server: rspamd2.dmz-prg2.suse.org X-Rspamd-Queue-Id: D6FCE21A5A Message-ID-Hash: C4QLT326XLAFWZ6RPBFBO4EV5FPZQ3QF X-Message-ID-Hash: C4QLT326XLAFWZ6RPBFBO4EV5FPZQ3QF X-MailFrom: tiwai@suse.de X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-alsa-devel.alsa-project.org-0; header-match-alsa-devel.alsa-project.org-1; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.9 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Signed-off-by: Takashi Iwai --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7a5817f253d7..1763f60a8376 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ ABOUT-NLS *.o *~ .deps +.libs +.dirstamp alsactl/alsactl alsactl/alsactl_init.7 From patchwork Sat Jul 6 07:42:31 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Takashi Iwai X-Patchwork-Id: 810794 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id CAF19C2BD09 for ; Sat, 6 Jul 2024 07:47:14 +0000 (UTC) Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id EB60715FA; Sat, 6 Jul 2024 09:47:02 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz EB60715FA DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1720252033; bh=e+BwI6fyR/Z1Gzf02y/1MLRK9tnD1x01n2dR3l8a+RM=; h=From:To:Subject:Date:In-Reply-To:References:List-Id:List-Archive: List-Help:List-Owner:List-Post:List-Subscribe:List-Unsubscribe: From; b=N/3Q0G0Q2Kbq2foQ4lB+jL9WwYdgqZe8qhx2vxW1yJlDj8gjYQQqDL4+W5E9uSK7L s1AdtXiKsU7B1ZDqSyGuTdRTw4mnYyPemzSv5yw4ydAcqvC8+KvUgu5FsvV9wfdSli iR14sQ5tZdpSMK0jPrtuTCbv1nabyA5ROlC7d37w= Received: by alsa1.perex.cz (Postfix, from userid 50401) id 5B121F8061F; Sat, 6 Jul 2024 09:45:56 +0200 (CEST) Received: from mailman-core.alsa-project.org (mailman-core.alsa-project.org [10.254.200.10]) by alsa1.perex.cz (Postfix) with ESMTP id AF15CF80609; Sat, 6 Jul 2024 09:45:55 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id CF71FF8025E; Sat, 6 Jul 2024 09:44:46 +0200 (CEST) Received: from smtp-out2.suse.de (smtp-out2.suse.de [IPv6:2a07:de40:b251:101:10:150:64:2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 3ABD3F800FE for ; Sat, 6 Jul 2024 09:42:06 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 3ABD3F800FE Authentication-Results: alsa1.perex.cz; dkim=pass (1024-bit key, unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=KXorJ6wM; dkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=6jCkh5pu; dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=KXorJ6wM; dkim=neutral header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=6jCkh5pu Received: from imap1.dmz-prg2.suse.org (imap1.dmz-prg2.suse.org [IPv6:2a07:de40:b281:104:10:150:64:97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 03EF31F810; Sat, 6 Jul 2024 07:42:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1720251726; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3fKggnObg8PtyhU8ULSvdeZ8B1JPeYI2t4LteJrZ/sE=; b=KXorJ6wMS5MXfHLK5rArI6ygfqMLHYIvPFCLcEZtBqJtnoUulD8SNZFq+/tstnFUGvU5nL yEHlphZwi80osEjGA6MDnzVLBzXsPbmmVM5W9uiUTmF5LRSyuyiaView12Da3dVTW6Xjpo wkxdZ5yg6qzufCa6OtfSammjBzmOIlA= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1720251726; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3fKggnObg8PtyhU8ULSvdeZ8B1JPeYI2t4LteJrZ/sE=; b=6jCkh5pucxzxZa8id9hOiE8p5GIPsCVIK8kSHz4/43wX5n++tRTKfsqaYwksIeYluaIblh 6Cz5QW36pgaleyDw== Authentication-Results: smtp-out2.suse.de; dkim=pass header.d=suse.de header.s=susede2_rsa header.b=KXorJ6wM; dkim=pass header.d=suse.de header.s=susede2_ed25519 header.b=6jCkh5pu DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1720251726; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3fKggnObg8PtyhU8ULSvdeZ8B1JPeYI2t4LteJrZ/sE=; b=KXorJ6wMS5MXfHLK5rArI6ygfqMLHYIvPFCLcEZtBqJtnoUulD8SNZFq+/tstnFUGvU5nL yEHlphZwi80osEjGA6MDnzVLBzXsPbmmVM5W9uiUTmF5LRSyuyiaView12Da3dVTW6Xjpo wkxdZ5yg6qzufCa6OtfSammjBzmOIlA= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1720251726; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3fKggnObg8PtyhU8ULSvdeZ8B1JPeYI2t4LteJrZ/sE=; b=6jCkh5pucxzxZa8id9hOiE8p5GIPsCVIK8kSHz4/43wX5n++tRTKfsqaYwksIeYluaIblh 6Cz5QW36pgaleyDw== Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id D363C13A7B; Sat, 6 Jul 2024 07:42:05 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id EOw/Mk31iGZRDgAAD6G6ig (envelope-from ); Sat, 06 Jul 2024 07:42:05 +0000 From: Takashi Iwai To: alsa-devel@alsa-project.org Subject: [PATCH alsa-utils 5/5] arecordmidi2: Add passive mode and interactive mode Date: Sat, 6 Jul 2024 09:42:31 +0200 Message-ID: <20240706074232.6364-5-tiwai@suse.de> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240706074232.6364-1-tiwai@suse.de> References: <20240706074232.6364-1-tiwai@suse.de> MIME-Version: 1.0 X-Rspamd-Queue-Id: 03EF31F810 X-Spamd-Result: default: False [-3.01 / 50.00]; BAYES_HAM(-3.00)[100.00%]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_MISSING_CHARSET(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; R_DKIM_ALLOW(-0.20)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; MIME_GOOD(-0.10)[text/plain]; MX_GOOD(-0.01)[]; TO_DN_NONE(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; FROM_EQ_ENVFROM(0.00)[]; ARC_NA(0.00)[]; FROM_HAS_DN(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; TO_MATCH_ENVRCPT_ALL(0.00)[]; RCVD_TLS_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.de:email,suse.de:dkim]; DKIM_SIGNED(0.00)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; FUZZY_BLOCKED(0.00)[rspamd.com]; RCVD_COUNT_TWO(0.00)[2]; DWL_DNSWL_BLOCKED(0.00)[suse.de:dkim]; MIME_TRACE(0.00)[0:+]; DKIM_TRACE(0.00)[suse.de:+] X-Rspamd-Action: no action X-Rspamd-Server: rspamd1.dmz-prg2.suse.org Message-ID-Hash: 47TXE4GXKADOQA6JR46KOR7CLSXVNAJH X-Message-ID-Hash: 47TXE4GXKADOQA6JR46KOR7CLSXVNAJH X-MailFrom: tiwai@suse.de X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-alsa-devel.alsa-project.org-0; header-match-alsa-devel.alsa-project.org-1; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.9 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Allow arecordmidi2 running without specifying the source ports via -p option. This will create a UMP Endpoint with the full 16 FBs, and simply reads from the input ports via subscribers. User needs to connect to the ports manually, though. Also, add -r option to run in the interactive mode. In the interactive mode, arecordmidi2 waits for the RETURN key entered from the terminal to start the recording, and the recording ends after another RETURN key. Signed-off-by: Takashi Iwai --- seq/aplaymidi2/arecordmidi2.1 | 20 +++++++++- seq/aplaymidi2/arecordmidi2.c | 75 +++++++++++++++++++++++++++-------- 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/seq/aplaymidi2/arecordmidi2.1 b/seq/aplaymidi2/arecordmidi2.1 index 0e41a300b553..a9cfb00d0318 100644 --- a/seq/aplaymidi2/arecordmidi2.1 +++ b/seq/aplaymidi2/arecordmidi2.1 @@ -5,7 +5,7 @@ arecordmidi2 \- record a MIDI Clip file .SH SYNOPSIS .B arecordmidi2 -\-p client:port[,...] [options] midi2file +[options] midi2file .SH DESCRIPTION .B arecordmidi2 @@ -32,6 +32,16 @@ A client can be specified by its number, its name, or a prefix of its name. A port is specified by its number; for port 0 of a client, the ":0" part of the port specification can be omitted. +\fBarecordmidi2\fP creates a UMP Endpoint containing the same number +of Function Blocks as specified by this option, each of which is +connected to the specified port as a source. + +When no source ports are specified with \fI\-p\fP option, +\fBarecordmidi2\fP creates a UMP Endpoint with full 16 Function Blocks +and records from those inputs. User can connect the sequencer ports +freely via \fBaconnect\fP, for example. This mode can be used +together with the interactive mode via \fI\-r\fP option. + .TP .I \-b,\-\-bpm=beats Sets the musical tempo of the MIDI file, in beats per minute. @@ -62,6 +72,14 @@ Sets the UMP MIDI protocol version. Either 1 or 2 has to be given for MIDI 1.0 and MIDI 2.0 protocol, respectively. Default is 1. +.TP +.I \-r,\-\-interactive +Run in the interactive mode. \fBarecordmidi2\fP waits for a RETURN +key input from the terminal to start the recording. After starting, +the recording ends when another RETURN key is input from the +terminal. The received events before the start of recording are +discarded. + .SH SEE ALSO arecordmidi(1) .br diff --git a/seq/aplaymidi2/arecordmidi2.c b/seq/aplaymidi2/arecordmidi2.c index 32693854b7a8..cad5851c48ea 100644 --- a/seq/aplaymidi2/arecordmidi2.c +++ b/seq/aplaymidi2/arecordmidi2.c @@ -93,8 +93,15 @@ static void create_ump_client(void) snd_ump_endpoint_info_t *ep; snd_ump_block_info_t *blk; snd_seq_port_info_t *pinfo; + int num_groups; int i, err; + /* in passive mode, create full 16 groups */ + if (port_count) + num_groups = port_count; + else + num_groups = 16; + /* create a UMP Endpoint */ snd_ump_endpoint_info_alloca(&ep); snd_ump_endpoint_info_set_name(ep, "arecordmidi2"); @@ -105,14 +112,14 @@ static void create_ump_client(void) snd_ump_endpoint_info_set_protocol_caps(ep, SND_UMP_EP_INFO_PROTO_MIDI2); snd_ump_endpoint_info_set_protocol(ep, SND_UMP_EP_INFO_PROTO_MIDI2); } - snd_ump_endpoint_info_set_num_blocks(ep, port_count); + snd_ump_endpoint_info_set_num_blocks(ep, num_groups); - err = snd_seq_create_ump_endpoint(seq, ep, port_count); + err = snd_seq_create_ump_endpoint(seq, ep, num_groups); check_snd("create UMP endpoint", err); /* create UMP Function Blocks */ snd_ump_block_info_alloca(&blk); - for (i = 0; i < port_count; i++) { + for (i = 0; i < num_groups; i++) { char blkname[32]; sprintf(blkname, "Group %d", i + 1); @@ -128,7 +135,7 @@ static void create_ump_client(void) /* toggle timestamping for all input ports */ snd_seq_port_info_alloca(&pinfo); - for (i = 0; i <= port_count; i++) { + for (i = 0; i <= num_groups; i++) { err = snd_seq_get_port_info(seq, i, pinfo); check_snd("get port info", err); snd_seq_port_info_set_timestamping(pinfo, 1); @@ -343,8 +350,11 @@ static void write_file_header(FILE *file) /* first DCS */ write_dcs(file, 0); write_dctpq(file); +} - /* start bar */ +/* write start bar */ +static void start_bar(FILE *file) +{ write_start_clip(file); write_tempo(file); write_time_sig(file); @@ -361,7 +371,8 @@ static void help(const char *argv0) " -t,--ticks=ticks resolution in ticks per beat or frame\n" " -i,--timesig=nn:dd time signature\n" " -n,--num-events=events fixed number of events to record, then exit\n" - " -u,--ump=version UMP MIDI version (1 or 2)\n", + " -u,--ump=version UMP MIDI version (1 or 2)\n" + " -r,--interactive Interactive mode\n", argv0); } @@ -377,7 +388,7 @@ static void sighandler(int sig ATTRIBUTE_UNUSED) int main(int argc, char *argv[]) { - static const char short_options[] = "hVp:b:t:n:u:"; + static const char short_options[] = "hVp:b:t:n:u:r"; static const struct option long_options[] = { {"help", 0, NULL, 'h'}, {"version", 0, NULL, 'V'}, @@ -387,6 +398,7 @@ int main(int argc, char *argv[]) {"timesig", 1, NULL, 'i'}, {"num-events", 1, NULL, 'n'}, {"ump", 1, NULL, 'u'}, + {"interactive", 0, NULL, 'r'}, {0} }; @@ -398,6 +410,8 @@ int main(int argc, char *argv[]) /* If |num_events| isn't specified, leave it at 0. */ long num_events = 0; long events_received = 0; + int start = 0; + int interactive = 0; init_seq(); @@ -441,17 +455,15 @@ int main(int argc, char *argv[]) if (midi_version != 1 && midi_version != 2) fatal("Invalid MIDI version %d\n", midi_version); break; + case 'r': + interactive = 1; + break; default: help(argv[0]); return 1; } } - if (port_count < 1) { - fputs("Pleast specify a source port with --port.\n", stderr); - return 1; - } - if (optind >= argc) { fputs("Please specify a file to record to.\n", stderr); return 1; @@ -459,7 +471,8 @@ int main(int argc, char *argv[]) create_queue(); create_ump_client(); - connect_ports(); + if (port_count) + connect_ports(); filename = argv[optind]; @@ -468,6 +481,13 @@ int main(int argc, char *argv[]) fatal("Cannot open %s - %s", filename, strerror(errno)); write_file_header(file); + if (interactive) { + printf("Press RETURN to start recording:"); + fflush(stdout); + } else { + start_bar(file); + start = 1; + } err = snd_seq_start_queue(seq, queue, NULL); check_snd("start queue", err); @@ -480,18 +500,39 @@ int main(int argc, char *argv[]) signal(SIGTERM, sighandler); npfds = snd_seq_poll_descriptors_count(seq, POLLIN); - pfds = alloca(sizeof(*pfds) * npfds); + pfds = alloca(sizeof(*pfds) * (npfds + 1)); for (;;) { snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN); - if (poll(pfds, npfds, -1) < 0) - break; + if (interactive) { + pfds[npfds].fd = STDIN_FILENO; + pfds[npfds].events = POLLIN | POLLERR | POLLNVAL; + if (poll(pfds, npfds + 1, -1) < 0) + break; + if (pfds[npfds].revents & POLLIN) { + while (!feof(stdin) && getchar() != '\n') + ; + if (!start) { + start_bar(file); + start = 1; + printf("Press RETURN to stop recording:"); + fflush(stdout); + continue; + } else { + stop = 1; + } + } + } else { + if (poll(pfds, npfds, -1) < 0) + break; + } + do { snd_seq_ump_event_t *event; err = snd_seq_ump_event_input(seq, &event); if (err < 0) break; - if (event) { + if (start && event) { record_event(file, event); events_received++; }