Compare commits

...

58 commits

Author SHA1 Message Date
Seppo Ingalsuo
a02ab41e09
Merge d8e8f75537 into fbde4f8c10 2024-09-19 16:39:34 +03:00
Jaroslav Kysela
fbde4f8c10 configure: bumb required alsa-lib version to 1.2.13
Code requires new sequencer API extensions.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-09-19 12:43:43 +02:00
Jaroslav Kysela
dbe0583874 github: use upload-artifacts@v4
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-09-06 12:54:14 +02:00
Jaroslav Kysela
3a8074ceb8 topology: pre_process_create_items - remove useless class_id_local
This variable was not freed correctly on function return.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-08-20 12:56:51 +02:00
Takashi Iwai
09a89f0139 aseqdump: Add missing dump of UMP Set Key Signature Message
The handling of Set Key Signature message of the Flex Data type was
missing by some reason.  Now the definition was added in alsa-lib
ump_msg.h, so let's show the contents.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-08-20 09:45:06 +02:00
Marc Herbert
5b511024e9 gitcompile: restore ACLOCAL_FLAGS ability to use
Fixes cryptic "error macro 'AM_PATH_ALSA' not found in library
configure.ac:22" when attempting a local install as described at
https://thesofproject.github.io/latest/getting_started/build-guide/build-from-scratch.html#build-alsa-lib-and-alsa-utils-from-source

Fixes giant commit f9e6010d5e ("topology: plugins - add Intel nhlt
encoder plugin") which added a second invocation of `aclocal` in 2021
but forgot the extra `$alsa_m4_flags`.

To avoid duplication, do not fix by adding the missing `$alsa_m4_flags`
to the second aclocal invocation. Instead, drop that variable and append
`-I ../alsa-lib/utils` directly to $ACLOCAL_FLAGS

Closes: https://github.com/alsa-project/alsa-utils/pull/272
Signed-off-by: Marc Herbert <marc.herbert@intel.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-08-06 18:39:20 +02:00
Peter Ujfalusi
cbebb86a56 aplay: Print '=== PAUSE ===' only if it is supported
Instead of printing the '=== PAUSE ===' unconditionally before calling
do_pause(), move it to the function and only print it if the stream can
be paused.

If the stream cannot be paused that the '=== PAUSE ===' will be replaced
by `PAUSE command ignored (no hw support)` immediately, which is not
observable by users but automation scripts will catch the '=== PAUSE ==='
and might think that the stream is indeed got paused.

Move the print into do_pause() function after the snd_pcm_pause() have
returned without error to make sure it is only printed if the stream is
paused and we are waiting for the pause release from user to proceed.

Closes: https://github.com/alsa-project/alsa-utils/pull/271
Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-08-06 18:37:51 +02:00
Ranjani Sridharan
6e3fc0433e topology: pre-processor: Introduce a new feature for subtree
Introduce a new kyword "SubTreeCopy" for extneding existing conf nodes
with additional nodes. This feature is useful for extending previous
pipeline class definitions with the addition of one or more widgets
without having to duplicate everything in the new class definition.

For example: Consider a pipeline class definition as below. Note that
only the widgets & routes are shown here.

Class.Pipeline.mixout-gain-dai-copier-playback {
	Object.Widget {
		mixout."1" {}
		dai-copier."1" {}
		gain."1" {}
		pipeline."1" {}
	}

	Object.Base {
		!route [
			{
				source mixout.$index.1
				sink	gain.$index.1
			}
		]
	}
}

If we want to extend this pipeline with the addition of an eqiir/eqfir,
we can create a SubTreeCopy node with type extend as follows:

Class.Pipeline.mixout-gain-eqiir-eqfir-dai-copier-playback {
	SubTreeCopy.baseclass {
		source "Class.Pipeline.mixout-gain-dai-copier-playback"
		type extend

                tree {
			Object.Widget {
				eqiir.1 {}
				eqfir.1 {}
			}

			Object.Base {
				!route [
					{
						source gain.$index.1
						sink   eqiir.$index.1
					}
					{
						source eqiir.$index.1
						sink   eqfir.$index.1
					}
				]
			}
		}
	}
}

Note that the target is left undefined, which means that the newly
created subtree will be merged to the parent node that contains the
"SubTreeCopy" node i.e. in this case
Class.Pipeline.mixout-gain-eqiir-eqfir-dai-copier-playback".

But if we want to modify an existing pipeline class while modifying the
order of widgets and/or inserting new widgets, we should use the type
"override" instead. This allows for adding new widgets to the list of
widgets in the base class definition while also allowing overriding the
routes to allow inserting the new widgets and reordering the widgets in
the base class. For example, if we want to add a drc widget between the
gain and the eqiir modules in the above class, we can do the following:

Class.Pipeline.mixout-efx-dai-copier-playback {
	# This copy will override all widgets/routes in the base class
	SubTreeCopy.baseclass {
		source "Class.Pipeline.mixout-gain-eqiir-eqfir-dai-copier-playback"
		target "Class.Pipeline"
		type override

		tree {
			Object.Widget {
				drc.1 {}
			}

			Object.Base {
				!route [
					{
						source mixout.$index.1
						sink	gain.$index.1
					}
					{
						source gain.$index.1
						sink	drc.$index.1
					}
					{
						source	drc.$index.1
						sink	eqiir.$index.1
					}
					{
						source	eqiir.$index.1
						sink	eqfir.$index.1
					}
				]
			}
		}
	}

	# Explicitly copy the widgets from the base class now
	SubTreeCopy.widgets {
		source "Class.Pipeline.mixout-gain-eqiir-eqfir-dai-copier-playback.Object.Widget"
		target "Class.Pipeline.mixout-efx-dai-copier-playback.Object.Widget"
	}
}

Closes: https://github.com/alsa-project/alsa-utils/pull/268
Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-08-06 18:32:52 +02:00
Takashi Iwai
21e0adfa3b aseqdump: Add dump of UMP Mixed Data Set messages
Add the support for yet more UMP messages.  UMP Mixed Data Set
messages are the generic data containers withe the message type 5
(shared with 8-bit SysEx).

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-28 11:24:06 +02:00
Takashi Iwai
095b064af6 aseqdump: Use snd_ump_get_byte() helper
For simplifying code, use the new helper function snd_ump_get_byte()
for extracting a byte data for SysEx and co.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-28 10:45:18 +02:00
Takashi Iwai
e26aa680aa aplaymidi2: Use snd_ump_get_byte() helper
For simplifying code, use snd_ump_get_byte() to retrieve the meta text
data.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-28 10:45:11 +02:00
Takashi Iwai
0188f93f02 aseqdump: Correct the limit of UMP 7-bit SysEx bytes
UMP 7-bit SysEx can hold up to 6 bytes, not 14 bytes.

Fixes: 02b0c3af56 ("aseqdump: Avoid OOB access with broken SysEx UMP packets")
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-26 14:46:58 +02:00
Takashi Iwai
02b0c3af56 aseqdump: Avoid OOB access with broken SysEx UMP packets
UMP SysEx messages have length field to specify the contained data
bytes, but they can be over the actual packet size.  Add the proper
size limit checks for avoiding the access overflow.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-24 14:05:55 +02:00
Takashi Iwai
df736ad67a aseqdump: Support of UMP 8-bit SysEx messages
Add the support to dump UMP 8-bit SysEx messages.
A slight code refactoring to share the code snippet between 7bit and
8bit SysEx handling, too.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-24 13:59:05 +02:00
Takashi Iwai
7de3cd3b8d aseqsend: Simplify using the standard helper function
Use the standard MIDI event encoder function provided in alsa-lib and
simplify the code.  We can reduce a lot of lines.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-23 15:50:57 +02:00
Takashi Iwai
580ea3c85e aseqsend: Move snd_seq_set_client_midi_version() call out of main()
Move the low-level API function call to init_seq() instead of calling
directly from main().  Just a code refactoring.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-23 13:51:18 +02:00
Takashi Iwai
dec1ef064a aseqsend: Refine man page
Make man page a bit nicer; reformatting and setting bold/italic
properly, typo fixes, and adding a few more words and sections.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-23 13:44:05 +02:00
Takashi Iwai
df68ec3343 aseqsend: Update the help texts for long options
The help text was forgotten to be updated for the new long options.
Updated now.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-23 13:41:07 +02:00
Takashi Iwai
d480eac6f2 aseqsend: Support long options
Add the support for long-style options such as --verbose.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-22 19:48:56 +02:00
Takashi Iwai
c95db638c0 aseqsend: Support UMP mode
Add a new option -u to specify the UMP MIDI1 or MIDI2 mode.  As
default (-u 0), the program reads the legacy MIDI 1.0 byte stream,
while in UMP mode, it reads as UMP packets and send to the target.
The UMP packet bytes are encoded in big endian.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-22 19:47:25 +02:00
Takashi Iwai
5703d27773 aseqdump: Show SysEx prefix to UMP SysEx data dump
Show the event prefix "SysEx" for UMP SysEx data.  Otherwise it's
difficult to know what it is.

Fixes: 506097ebb1 ("aseqdump: Show UMP SysEx messages")
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-22 19:47:21 +02:00
Takashi Iwai
39053b90d5 configure: Drop unused conditionals
Since the required alsa-lib version was bumped and the relevant code
cleanup, some conditionals are no longer referred.  Drop them.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-22 19:47:10 +02:00
Takashi Iwai
397c198955 aseqdump: Check the -u option value properly
Instead of passing the value as is, check the value passed to -u
option and bail out for bad values.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-22 19:47:02 +02:00
Takashi Iwai
946ea467cf aseqdump: Drop ifdef for UMP support
Now that the latest alsa-lib 1.2.12 is mandatory, drop the ugly
ifdefs.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-22 19:46:52 +02:00
Takashi Iwai
c7b342db82 aconnect: Drop superfluous ifdefs
Now that the latest alsa-lib 1.2.12 became mandatory, all ugly ifdefs
can be dropped.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-22 19:46:45 +02:00
Takashi Iwai
d26b66f881 aplaymidi: Allow to pass 0 to -u option, too
Specifying -u 0 shouldn't be treated as an error but it should mean
the legacy MIDI 1.0 handling.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-22 19:46:32 +02:00
Takashi Iwai
cac4935ba2 aplaymidi: Drop ifdef for UMP support
Now that the latest alsa-lib 1.2.12 became mandatory, all ugly ifdefs
can be dropped.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-22 19:46:00 +02:00
Takashi Iwai
4aae5a770f configure: Requires the latest ALSA-lib release 1.2.12
New features such as MIDI 2.0 should be always enabled for the
builds.  Update the dependency to alsa-lib 1.2.12.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-22 17:31:50 +02:00
Takashi Iwai
cdcfcddd95 aseqdump: Fix bogus velocity value output in UMP MIDI2 mode
The printf format for a normalized velocity in MIDI2 mode had a typo,
resulting in a bogus value.  Fix it.

Fixes: 7e9bebad0b199 ("aseqdump: Add options to switch view mode")
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-20 19:19:54 +02:00
Takashi Iwai
176c94591c aseqdump: Support of UMP Stream and Flex Data message types
Enhance aseqdump to interpret more UMP messages.  Now it includes the
standard  Stream messages and Flex Data messages.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-19 14:29:00 +02:00
Takashi Iwai
6a676f4a46 aplaymidi2: Add -a option to pass all UMP packets
So far, aplaymidi2 passes the MIDI1/MIDI2 channel voice UMP messages
to the target while processing other UMP messages internally.  But
sometimes we'd like to pass all UMP messages as is and let the
receiver processes.

This patch adds a new option -a (or --passall) to pass the all UMP
packets included in the given MIDI Clip file to the target as-is.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-19 14:28:44 +02:00
Takashi Iwai
47931000fd aplaymidi2: Fix --silent option handling
The --silent option takes no argument.  Correct the wrong setup.

Fixes: b1269eefdd ("aplaymidi2: Add --silent option")
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-19 14:27:15 +02:00
Takashi Iwai
33f6870f56 arecordmidi2: Fix truncated text in meta data text handling
The current code didn't proceed the text position buffer and the text
was always truncated in 12 bytes.  Let's fix it.

Fixes: 74daf3a93a ("arecordmidi2: Add options to put meta data texts")
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-11 17:08:11 +02:00
Takashi Iwai
74daf3a93a arecordmidi2: Add options to put meta data texts
For convenience, add more options to embed the meta data texts given
via command line.  The song name etc can be given directly via the
respective option directly, e.g. --song="text..."

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-09 11:52:39 +02:00
Takashi Iwai
274f1b9ebf arecordmidi2: Add --profile option
Allow to add arbitrary profile UMP data to be put into the
configuration of the recorded stream via --profile option.
The file must contain valid UMP data encoded in big-endian.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-09 08:09:45 +02:00
Takashi Iwai
f0df4b4cfe arecordmidi2: Add stdout output and --silent option
When the output file is '-', it's recorded to stdout.

For avoiding the corruption, this mode also suppresses the messages to
stdout, too, which can be enabled also via -s / --silent option.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-09 08:03:27 +02:00
Takashi Iwai
b1269eefdd aplaymidi2: Add --silent option
For suppressing the messages, add -s / --silent option.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-09 07:48:05 +02:00
Takashi Iwai
99ce4b1d8a Revert "arecordmidi2: Correct the MIDI FB direction"
This reverts commit e609d66807.

It turned out that the failure was rather in alsa-lib API; the
input and output have been incorrectly implemented.
Now that the alsa-lib code got fixed, let's revert the bad fix.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-09 07:32:52 +02:00
Takashi Iwai
68491dd464 aplaymidi2: Show meta data texts
Now aplaymidi2 shows the meta data texts embedded in Flex Data
messages such as copyright and lyrics.  The text output isn't
synchronized yet with the actual position, though.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-08 18:32:18 +02:00
Takashi Iwai
330741d523 aconnect: Fix the indication of inactive ports
The inactive port should have been shown in each port line instead of
the client name line.

Fixes: 64b1d486b1 ("aconnect: Add UMP support")
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-08 17:21:13 +02:00
Takashi Iwai
2ee6c170a8 arecordmidi2: Fix the tick in 1us tempo-base
The recorded tick is incorrectly converted for 1us tempo-base on the
old kernels.  Since we correct the queue tempo, we don't have to
adjust the returned tick value any longer.  The current code applies
it doubly, resulting in 100 times slower.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-08 17:14:06 +02:00
Takashi Iwai
e609d66807 arecordmidi2: Correct the MIDI FB direction
The direction was wrongly passed to the FB setup.  It has to be
"OUTPUT" instead of "INPUT, so that other applications can write to
arecordmidi2 port.

Fixes: 2cdf5ebedb ("arecordmidi2: Add initial version")
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-08 16:26:31 +02:00
Takashi Iwai
16533f81de arecordmidi2: Start queue at starting the stream
The queue should be started at the very same time of the start of the
stream itself in the interactive mode.  Otherwise it'll get bogus long
waits until the start of the clip.

Move the code to start the queue in start_bar(), so that it's always
tied with the start sequence.

Fixes: 1205dd5f6c ("arecordmidi2: Add passive mode and interactive mode")
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-08 16:26:09 +02:00
Takashi Iwai
1205dd5f6c arecordmidi2: Add passive mode and interactive mode
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 <tiwai@suse.de>
2024-07-05 17:20:54 +02:00
Takashi Iwai
7911d63370 .gitignore: Add stale files for topology builds
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-05 17:20:54 +02:00
Takashi Iwai
b0f3126dbc .gitignore: Add aplaymidi2 and arecordmidi2
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-07-05 17:20:54 +02:00
Takashi Iwai
2cdf5ebedb arecordmidi2: Add initial version
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 <tiwai@suse.de>
2024-07-05 17:20:51 +02:00
Takashi Iwai
aa5e949403 aplaymidi2: Add initial version
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 <tiwai@suse.de>
2024-07-05 17:20:33 +02:00
Takashi Iwai
ca82cf8ed6 aseqsend: Support realtime / system messages
The realtime / system messages (0xFx) wasn't handled properly.
Add the code to support those messages.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-06-27 14:06:25 +02:00
Takashi Iwai
7e7a53f0c9 aseqdump: White-space and slight code refactoring
Fix white spaces and applied a slight code refactoring to reduce the
indentation levels.  No code functionality changes.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-06-27 13:42:11 +02:00
Takashi Iwai
be6356d847 aseqdump: Refactor UMP SysEx dump
A slightly better version.  The extraction of a SysEx byte from a UMP
packet is more complicated than wished, in anyway.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-06-27 13:24:11 +02:00
Takashi Iwai
506097ebb1 aseqdump: Show UMP SysEx messages
Dump the 7bit SysEx messages from UMP packets as well.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-06-26 18:12:17 +02:00
Seppo Ingalsuo
5db60d5e85 Topology: NHLT: Intel: Fix compile warning in dmic-process.c
This patch fixes warnings like:

intel/dmic/dmic-process.c: In function 'select_mode':
intel/dmic/dmic-process.c:498:35: warning:
format '%s' expects a matching 'char *' argument [-Wformat=]

The intended __func__ string is missing from the two fprintf()
prints for errors.

Closes: https://github.com/alsa-project/alsa-utils/pull/270
Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-06-21 16:04:23 +02:00
Jaroslav Kysela
f417c1acfc Release v1.2.12
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-06-10 11:18:36 +02:00
Takashi Iwai
cecc383aeb aseqdump: Add dump for UMP Utility and System messages
Add the dump functions for UMP Type 0 (Utility) and Type 1 (System)
messages.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-05-31 14:30:43 +02:00
Miroslav Kovac
7547242d0f aseqsend: initial version
aseqsend is a command-line utility which allows one to send SysEx
(system exclusive) data to ALSA MIDI seqencer port. It can also send
any other MIDI commands.

Closes: https://github.com/alsa-project/alsa-utils/pull/257
Signed-off-by: Miroslav Kovac <mixxoo@gmail.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-05-23 13:22:20 +02:00
Masatake YAMATO
ba2bc072dc alsactl: don't free a card pointing NULL
alsactl distributed as part of Fedora 40 got a SEGV:

    # journalctl
    ...
    May 17 00:55:58 dev64.localdomain kernel: alsactl[1923]: segfault at 28 ip 00005600705b3373 sp 00007ffd9712bef0 error 4 in alsactl[5600705af000+13000] likely on CPU 5 (core 8, socket 0)
    ...

As the following output of the debug session, card_free() tried a card
pointing NULL:

    $ sudo coredumpctl debug alsactl
	       PID: 1923 (alsactl)
	       UID: 0 (root)
	       GID: 0 (root)
	    Signal: 11 (SEGV)
	 Timestamp: Fri 2024-05-17 00:55:58 JST (3h 34min ago)
      Command Line: /usr/sbin/alsactl -s -n 19 -c -E ALSA_CONFIG_PATH=/etc/alsa/alsactl.conf --initfile=/lib/alsa/init/00main rdaemon
	Executable: /usr/sbin/alsactl
     Control Group: /system.slice/alsa-state.service
	      Unit: alsa-state.service
	     Slice: system.slice
	   Boot ID: 241b5a2ef86f4940bb3d340583c80d88
	Machine ID: 437365709a8c488c9481ee4b6651c2ec
	  Hostname: dev64.localdomain
	   Storage: /var/lib/systemd/coredump/core.alsactl.0.241b5a2ef86f4940bb3d340583c80d88.1923.1715874958000000.zst (present)
      Size on Disk: 81.7K
	   Package: alsa-utils/1.2.11-1.fc40
	  build-id: 3b6fec58b3566d666d6e9fd48e8fcf04f03f0152
	   Message: Process 1923 (alsactl) of user 0 dumped core.

		    Module libasound.so.2 from rpm alsa-lib-1.2.11-2.fc40.x86_64
		    Module alsactl from rpm alsa-utils-1.2.11-1.fc40.x86_64
		    Stack trace of thread 1923:
		    #0  0x00005600705b3373 card_free (alsactl + 0xa373)
		    #1  0x00005600705c0e54 state_daemon (alsactl + 0x17e54)
		    #2  0x00005600705b2339 main (alsactl + 0x9339)
		    #3  0x00007f4c0b9b7088 __libc_start_call_main (libc.so.6 + 0x2a088)
		    #4  0x00007f4c0b9b714b __libc_start_main_impl (libc.so.6 + 0x2a14b)
		    #5  0x00005600705b2df5 _start (alsactl + 0x9df5)
		    ELF object binary architecture: AMD x86-64

    GNU gdb (Fedora Linux) 14.2-1.fc40
    Copyright (C) 2023 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    Type "show copying" and "show warranty" for details.
    This GDB was configured as "x86_64-redhat-linux-gnu".
    Type "show configuration" for configuration details.
    For bug reporting instructions, please see:
    <https://www.gnu.org/software/gdb/bugs/>.
    Find the GDB manual and other documentation resources online at:
	<http://www.gnu.org/software/gdb/documentation/>.

    For help, type "help".
    Type "apropos word" to search for commands related to "word"...
    Reading symbols from /usr/sbin/alsactl...
    Reading symbols from /usr/lib/debug/usr/sbin/alsactl-1.2.11-1.fc40.x86_64.debug...
    [New LWP 1923]
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib64/libthread_db.so.1".
    Core was generated by `/usr/sbin/alsactl -s -n 19 -c -E ALSA_CONFIG_PATH=/etc/alsa/alsactl.conf --init'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  free_list (list=0x20) at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/daemon.c:73
    73		for (i = 0; i < list->size; i++)
    (gdb) where
    #0  free_list (list=0x20) at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/daemon.c:73
    #1  card_free (card=card@entry=0x5600707455f0) at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/daemon.c:82
    #2  0x00005600705c0e54 in state_daemon (file=file@entry=0x5600705c31a1 "/var/lib/alsa/asound.state", cardname=cardname@entry=0x0, period=period@entry=300,
	pidfile=pidfile@entry=0x5600705c3170 "/var/run/alsactl.pid") at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/daemon.c:455
    #3  0x00005600705b2339 in main (argc=<optimized out>, argv=<optimized out>) at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/alsactl.c:459
    (gdb) list
    68
    69	static void free_list(struct id_list *list)
    70	{
    71		int i;
    72
    73		for (i = 0; i < list->size; i++)
    74			free(list->list[i]);
    75		free(list->list);
    76	}
    77
    (gdb) up
    #1  card_free (card=card@entry=0x5600707455f0) at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/daemon.c:82
    82		free_list(&c->blacklist);
    (gdb) p c
    $1 = (struct card *) 0x0
    (gdb)

Closes: https://github.com/alsa-project/alsa-utils/pull/267
Signed-off-by: Masatake YAMATO <yamato@redhat.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-05-23 13:17:59 +02:00
Jaroslav Kysela
84d0a91f11 alsamixer: fix calculation in set_normalized_volume (overflow)
Lowering volume below 0 causes overflow spike to 100% volume (volume goes
below 0 and back to 100 repeatedly). 0 overflows past infinity when holding
down z,x,c.

> value = lrint_dir(6000.0 * log10(volume), dir) + max;
   (where volume = 0 , and dir = -1 . min = -9999999 , and max = -6)
> log10(0) is negative infinity = error

Fixes: https://github.com/alsa-project/alsa-utils/pull/266
Reported-by: genr8eofl <genBTC@gmx.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-05-23 13:14:20 +02:00
21 changed files with 2683 additions and 111 deletions

View file

@ -109,7 +109,7 @@ jobs:
cd alsa-utils-$(cat version)
make install
- name: Archive package
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v4
with:
name: alsa-utils-test-package
path: artifacts/

5
.gitignore vendored
View file

@ -29,6 +29,8 @@ ABOUT-NLS
*.o
*~
.deps
.libs
.dirstamp
alsactl/alsactl
alsactl/alsactl_init.7
@ -48,7 +50,10 @@ 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
speaker-test/speaker-test
topology/alsatplg.1

View file

@ -79,6 +79,8 @@ static void card_free(struct card **card)
{
struct card *c = *card;
if (c == NULL)
return;
free_list(&c->blacklist);
free_list(&c->whitelist);
if (c->handle)

View file

@ -146,6 +146,8 @@ static int set_normalized_volume(snd_mixer_elem_t *elem,
min_norm = pow(10, (min - max) / 6000.0);
volume = volume * (1 - min_norm) + min_norm;
}
if (volume <= 0)
volume = 1e-36;
value = lrint_dir(6000.0 * log10(volume), dir) + max;
return set_dB[ctl_dir](elem, channel, value, dir);
}

View file

@ -1618,6 +1618,8 @@ static void do_pause(void)
error(_("pause push error: %s"), snd_strerror(err));
return;
}
fprintf(stderr, _("\r=== PAUSE === "));
fflush(stderr);
while (1) {
b = wait_for_input();
if (b == ' ' || b == '\r') {
@ -1642,8 +1644,6 @@ static void check_stdin(void)
while (read(fileno(stdin), &b, 1) == 1) {
if (b == ' ' || b == '\r') {
while (read(fileno(stdin), &b, 1) == 1);
fprintf(stderr, _("\r=== PAUSE === "));
fflush(stderr);
do_pause();
fprintf(stderr, " \r");
fflush(stderr);

View file

@ -1,6 +1,6 @@
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(alsa-utils, 1.2.11)
AC_INIT(alsa-utils, 1.2.12)
AC_CONFIG_SRCDIR([aplay/aplay.c])
AC_PREFIX_DEFAULT(/usr)
AM_INIT_AUTOMAKE([subdir-objects])
@ -21,7 +21,7 @@ AC_PROG_SED
AC_DISABLE_STATIC
AM_PROG_LIBTOOL
PKG_PROG_PKG_CONFIG
AM_PATH_ALSA(1.2.5)
AM_PATH_ALSA(1.2.13)
if test "x$enable_alsatest" = "xyes"; then
AC_CHECK_FUNC([snd_ctl_elem_add_enumerated],
, [AC_ERROR([No user enum control support in alsa-lib])])
@ -47,17 +47,6 @@ AC_CHECK_HEADERS([samplerate.h], [have_samplerate="yes"], [have_samplerate="no"]
[#include <samplerate.h>])
AC_CHECK_LIB([asound], [snd_seq_client_info_get_card], [HAVE_SEQ_CLIENT_INFO_GET_CARD="yes"])
if test "$HAVE_SEQ_CLIENT_INFO_GET_CARD" = "yes" ; then
AC_DEFINE([HAVE_SEQ_CLIENT_INFO_GET_CARD], 1, [alsa-lib supports snd_seq_client_info_get_card])
fi
AC_CHECK_LIB([asound], [snd_seq_client_info_get_pid], [HAVE_SEQ_CLIENT_INFO_GET_PID="yes"])
if test "$HAVE_SEQ_CLIENT_INFO_GET_PID" = "yes" ; then
AC_DEFINE([HAVE_SEQ_CLIENT_INFO_GET_PID], 1, [alsa-lib supports snd_seq_client_info_get_pid])
fi
AC_CHECK_LIB([asound], [snd_seq_client_info_get_midi_version], [HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION="yes"])
if test "$HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION" = "yes" -a "$have_rawmidi" = "yes"; then
AC_DEFINE([HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION], 1, [alsa-lib supports snd_seq_client_info_get_midi_version])
fi
AC_CHECK_LIB([atopology], [snd_tplg_save], [have_topology="yes"], [have_topology="no"])
#
@ -486,8 +475,8 @@ 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 \
speaker-test/Makefile speaker-test/samples/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 \
nhlt/Makefile)

View file

@ -1,9 +1,9 @@
#!/bin/bash
if test -d ../alsa-lib/utils && ! test -r `aclocal --print-ac-dir`/alsa.m4; then
alsa_m4_flags="-I ../alsa-lib/utils"
ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I ../alsa-lib/utils"
fi
aclocal $alsa_m4_flags $ACLOCAL_FLAGS
aclocal $ACLOCAL_FLAGS
# save original files to avoid stupid modifications by gettextize
cp Makefile.am Makefile.am.ok
cp configure.ac configure.ac.ok

View file

@ -1 +1 @@
SUBDIRS=aconnect aplaymidi aseqdump aseqnet
SUBDIRS=aconnect aplaymidi aplaymidi2 aseqdump aseqnet aseqsend

View file

@ -29,12 +29,7 @@
#include <alsa/asoundlib.h>
#include "gettext.h"
#ifdef SND_SEQ_PORT_CAP_INACTIVE
#define HANDLE_SHOW_ALL
static int show_all;
#else
#define show_all 0
#endif
static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
{
@ -67,9 +62,7 @@ static void usage(void)
printf(_(" aconnect -i|-o [-options]\n"));
printf(_(" -i,--input list input (readable) ports\n"));
printf(_(" -o,--output list output (writable) ports\n"));
#ifdef HANDLE_SHOW_ALL
printf(_(" -a,--all show inactive ports, too\n"));
#endif
printf(_(" -l,--list list current connections of each port\n"));
printf(_(" * Remove all exported connections\n"));
printf(_(" -x, --removeall\n"));
@ -84,15 +77,11 @@ static void usage(void)
#define perm_ok(cap,bits) (((cap) & (bits)) == (bits))
#ifdef SND_SEQ_PORT_DIR_INPUT
static int check_direction(snd_seq_port_info_t *pinfo, int bit)
{
int dir = snd_seq_port_info_get_direction(pinfo);
return !dir || (dir & bit);
}
#else
#define check_direction(x, y) 1
#endif
static int check_permission(snd_seq_port_info_t *pinfo, int perm)
{
@ -174,20 +163,16 @@ static void do_search_port(snd_seq_t *seq, int perm, action_func_t do_action)
/* reset query info */
snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
snd_seq_port_info_set_port(pinfo, -1);
#ifdef HANDLE_SHOW_ALL
if (show_all)
snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_INACTIVE);
#endif
count = 0;
while (snd_seq_query_next_port(seq, pinfo) >= 0) {
if (check_permission(pinfo, perm)) {
do_action(seq, cinfo, pinfo, count);
count++;
}
#ifdef HANDLE_SHOW_ALL
if (show_all)
snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_INACTIVE);
#endif
}
}
}
@ -205,7 +190,6 @@ static void print_port(snd_seq_t *seq ATTRIBUTE_UNUSED,
snd_seq_client_info_get_name(cinfo),
(snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT ?
_("user") : _("kernel")));
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
switch (snd_seq_client_info_get_midi_version(cinfo)) {
case SND_SEQ_CLIENT_UMP_MIDI_1_0:
printf(",UMP-MIDI1");
@ -214,27 +198,21 @@ static void print_port(snd_seq_t *seq ATTRIBUTE_UNUSED,
printf(",UMP-MIDI2");
break;
}
#endif
#ifdef HANDLE_SHOW_ALL
if (snd_seq_port_info_get_capability(pinfo) & SND_SEQ_PORT_CAP_INACTIVE)
printf(",INACTIVE");
#endif
#ifdef HAVE_SEQ_CLIENT_INFO_GET_CARD
card = snd_seq_client_info_get_card(cinfo);
#endif
if (card != -1)
printf(",card=%d", card);
#ifdef HAVE_SEQ_CLIENT_INFO_GET_PID
pid = snd_seq_client_info_get_pid(cinfo);
#endif
if (pid != -1)
printf(",pid=%d", pid);
printf("]\n");
}
printf(" %3d '%-16s'\n",
printf(" %3d '%-16s'",
snd_seq_port_info_get_port(pinfo),
snd_seq_port_info_get_name(pinfo));
if (snd_seq_port_info_get_capability(pinfo) & SND_SEQ_PORT_CAP_INACTIVE)
printf(" [INACTIVE]");
printf("\n");
}
static void print_port_and_subs(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
@ -298,11 +276,7 @@ enum {
SUBSCRIBE, UNSUBSCRIBE, LIST, REMOVE_ALL
};
#ifdef HANDLE_SHOW_ALL
#define ACONNECT_OPTS "dior:t:elxa"
#else
#define ACONNECT_OPTS "dior:t:elx"
#endif
static const struct option long_option[] = {
{"disconnect", 0, NULL, 'd'},
@ -313,9 +287,7 @@ static const struct option long_option[] = {
{"exclusive", 0, NULL, 'e'},
{"list", 0, NULL, 'l'},
{"removeall", 0, NULL, 'x'},
#ifdef HANDLE_SHOW_ALL
{"all", 0, NULL, 'a'},
#endif
{NULL, 0, NULL, 0},
};
@ -369,12 +341,10 @@ int main(int argc, char **argv)
case 'x':
command = REMOVE_ALL;
break;
#ifdef HANDLE_SHOW_ALL
case 'a':
command = LIST;
show_all = 1;
break;
#endif
default:
usage();
exit(1);

View file

@ -30,9 +30,7 @@
#include <unistd.h>
#include <alsa/asoundlib.h>
#include "version.h"
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
#include <alsa/ump_msg.h>
#endif
/*
* 31.25 kbaud, one start bit, eight data bits, two stop bits.
@ -78,9 +76,7 @@ static int file_offset; /* current offset in input file */
static int num_tracks;
static struct track *tracks;
static int smpte_timing;
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
static int ump_mode;
#endif
/* prints an error message to stderr */
static void errormsg(const char *msg, ...)
@ -685,7 +681,6 @@ static int fill_legacy_event(struct event* event, snd_seq_event_t *ev)
return 0;
}
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
static unsigned char to_ump_status(unsigned char ev_type)
{
switch (ev_type) {
@ -762,13 +757,10 @@ static int fill_ump_event(struct event* event, snd_seq_ump_event_t *ump_ev,
snd_seq_ev_set_ump_data(ump_ev, &ump, sizeof(ump));
return 0;
}
#endif /* HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION */
static void play_midi(void)
{
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
snd_seq_ump_event_t ump_ev;
#endif
snd_seq_event_t ev;
int i, max_tick, err;
@ -830,7 +822,7 @@ static void play_midi(void)
if (err < 0)
continue;
}
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
if (ump_mode) {
err = fill_ump_event(event, &ump_ev, &ev);
if (err < 0)
@ -839,7 +831,6 @@ static void play_midi(void)
check_snd("output event", err);
continue;
}
#endif
/* this blocks when the output pool has been filled */
err = snd_seq_event_output(seq, &ev);
@ -957,9 +948,7 @@ static void usage(const char *argv0)
"-V, --version print current version\n"
"-l, --list list all possible output ports\n"
"-p, --port=client:port,... set port(s) to play to\n"
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
"-u, --ump=version UMP output (only version=1 is supported)\n"
#endif
"-d, --delay=seconds delay after song ends\n",
argv0);
}
@ -969,12 +958,7 @@ static void version(void)
puts("aplaymidi version " SND_UTIL_VERSION_STR);
}
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
#define OPTIONS "hVlp:d:u:"
#else
#define OPTIONS "hVlp:d:"
#endif
int main(int argc, char *argv[])
{
@ -984,9 +968,7 @@ int main(int argc, char *argv[])
{"version", 0, NULL, 'V'},
{"list", 0, NULL, 'l'},
{"port", 1, NULL, 'p'},
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
{"ump", 1, NULL, 'u'},
#endif
{"delay", 1, NULL, 'd'},
{0}
};
@ -1013,15 +995,11 @@ int main(int argc, char *argv[])
case 'd':
end_delay = atoi(optarg);
break;
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
case 'u':
if (strcmp(optarg, "1")) {
errormsg("Only MIDI 1.0 is supported");
return 1;
}
ump_mode = 1;
ump_mode = atoi(optarg);
if (ump_mode < 0 || ump_mode > 1)
fatal("Only MIDI 1.0 is supported");
break;
#endif
default:
usage(argv[0]);
return 1;
@ -1029,13 +1007,11 @@ int main(int argc, char *argv[])
}
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
if (ump_mode) {
int err;
err = snd_seq_set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_1_0);
check_snd("set midi version", err);
}
#endif
if (do_list) {
list_ports();

View file

@ -0,0 +1,5 @@
AM_CPPFLAGS = -I$(top_srcdir)/include
EXTRA_DIST = aplaymidi2.1 arecordmidi2.1
bin_PROGRAMS = aplaymidi2 arecordmidi2
man_MANS = aplaymidi2.1 arecordmidi2.1

View file

@ -0,0 +1,84 @@
.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, End Clip.
Lyrics and other meta data in Flex Data are printed, too, unless
\fI\-s\fP option is given.
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.
.TP
.I \-s, \-\-silent
Don't show message texts.
.TP
.I \-a, \-\-passall
Pass all UMP packets as is.
As default, \fBaplaymidi2\fP passes only MIDI1 and MIDI2 channel voice
messages and process other UMP packets internally.
With this option, it passes all UMP packets to the target.
.SH SEE ALSO
pmidi(1)
.br
aplaymidi(1)
.SH AUTHOR
Takashi Iwai <tiwai@suse.de>

581
seq/aplaymidi2/aplaymidi2.c Normal file
View file

@ -0,0 +1,581 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* aplaymidi2.c - simple player of a MIDI Clip File over ALSA sequencer
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <alsa/asoundlib.h>
#include <alsa/ump_msg.h>
#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 int silent;
static int passall;
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);
}
struct flexdata_text_prefix {
unsigned char status_bank;
unsigned char status;
const char *prefix;
};
static struct flexdata_text_prefix text_prefix[] = {
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PROJECT_NAME,
.prefix = "Project" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_SONG_NAME,
.prefix = "Song" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_MIDI_CLIP_NAME,
.prefix = "MIDI Clip" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_COPYRIGHT_NOTICE,
.prefix = "Copyright" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_COMPOSER_NAME,
.prefix = "Composer" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICIST_NAME,
.prefix = "Lyricist" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_ARRANGER_NAME,
.prefix = "Arranger" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PUBLISHER_NAME,
.prefix = "Publisher" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PRIMARY_PERFORMER,
.prefix = "Performer" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_ACCOMPANY_PERFORMAER,
.prefix = "Accompany Performer" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RECORDING_DATE,
.prefix = "Recording Date" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RECORDING_LOCATION,
.prefix = "Recording Location" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICS,
.prefix = "Lyrics" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICS_LANGUAGE,
.prefix = "Lyrics Language" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RUBY,
.prefix = "Ruby" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RUBY_LANGUAGE,
.prefix = "Ruby Language" },
{}
};
static void show_text(const uint32_t *ump)
{
static unsigned char textbuf[256];
static int len;
const snd_ump_msg_flex_data_t *fh =
(const snd_ump_msg_flex_data_t *)ump;
const char *prefix;
int i;
if (fh->meta.format == SND_UMP_FLEX_DATA_MSG_FORMAT_SINGLE ||
fh->meta.format == SND_UMP_FLEX_DATA_MSG_FORMAT_START)
len = 0;
for (i = 0; i < 12 && len < (int)sizeof(textbuf); i++) {
textbuf[len] = snd_ump_get_byte(ump, 4 + i);
if (!textbuf[len])
break;
switch (textbuf[len]) {
case 0x0a: /* end of paragraph */
case 0x0d: /* end of line */
textbuf[len] = '\n';
break;
}
len++;
}
if (fh->meta.format != SND_UMP_FLEX_DATA_MSG_FORMAT_SINGLE &&
fh->meta.format != SND_UMP_FLEX_DATA_MSG_FORMAT_END)
return;
if (len >= (int)sizeof(textbuf))
len = sizeof(textbuf) - 1;
textbuf[len] = 0;
prefix = NULL;
for (i = 0; text_prefix[i].status_bank; i++) {
if (text_prefix[i].status_bank == fh->meta.status_bank &&
text_prefix[i].status == fh->meta.status) {
prefix = text_prefix[i].prefix;
break;
}
}
if (prefix) {
printf("%s: %s\n", prefix, textbuf);
} else {
printf("(%d:%d): %s\n", fh->meta.status_bank, fh->meta.status,
textbuf);
}
len = 0;
}
/* 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 (passall)
send_ump(ump, len);
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;
}
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_METADATA ||
fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT) {
if (!silent)
show_text(ump);
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 (!passall &&
(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"
"-s, --silent don't show texts\n"
"-a, --passall pass all UMP packets as-is\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'},
{"silent", 0, NULL, 's'},
{"passall", 0, NULL, 'a'},
{0}
};
int c;
init_seq();
while ((c = getopt_long(argc, argv, "hVp:d:sa",
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;
case 's':
silent = 1;
break;
case 'a':
passall = 1;
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;
}

View file

@ -0,0 +1,110 @@
.TH ARECORDMIDI2 1 "4 July 2024"
.SH NAME
arecordmidi2 \- record a MIDI Clip file
.SH SYNOPSIS
.B arecordmidi2
[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.
When \fB\-\fP is passed to the MIDI Clip file argument,
it's recorded to stdout. It implies \fI\-s\fP option, too.
.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.
\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.
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.
.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.
.TP
.I \-s,\-\-silent
Don't print messages to stdout.
.TP
.I \-P,\-\-profile=file
Read the UMP data from the given file and put them into the
configuration section of the recorded output.
The file must contain only valid UMP data encoded in big-endian.
.TP
.I \-\-song=text, \-\-clip=text, \-\-copyright=text, \-\-composer=text, \
\-\-lyricist=text, \-\-arranger=text, \-\-publisher=text, \
\-\-performer=text \-\-accompany=text, \-\-date=text, \-\-location=text
Put the given meta data text in the configuration section.
.SH SEE ALSO
arecordmidi(1)
.br
aplaymidi2(1)
.SH AUTHOR
Takashi Iwai <tiwai@suse.de>

View file

@ -0,0 +1,717 @@
/*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>
#include <poll.h>
#include <alsa/asoundlib.h>
#include <alsa/ump_msg.h>
#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;
static int silent;
static const char *profile_ump_file;
#define MAX_METADATA 16
static int metadata_num;
static unsigned int metadata_types[MAX_METADATA];
static const char *metadata_texts[MAX_METADATA];
/* 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 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");
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, num_groups);
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 < num_groups; 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 <= 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);
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;
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);
}
/* read a UMP raw (big-endian) packet, return the packet length in words */
static int read_ump_raw(FILE *file, uint32_t *buf)
{
uint32_t v;
int i, num;
if (fread(buf, 4, 1, file) != 1)
return 0;
v = be32toh(v);
num = snd_ump_packet_length(snd_ump_msg_hdr_type(v));
for (i = 1; i < num; i++) {
if (fread(buf + i, 4, 1, file) != 1)
return 0;
}
return num;
}
/* read the profile UMP data and write to the configuration */
static void write_profiles(FILE *file)
{
FILE *fp;
uint32_t ump[4];
int len;
if (!profile_ump_file)
return;
fp = fopen(profile_ump_file, "rb");
if (!fp)
fatal("cannot open the profile '%s'", profile_ump_file);
while (!feof(fp)) {
len = read_ump_raw(fp, ump);
if (!len)
break;
fwrite(ump, 4, len, file);
}
fclose(fp);
}
/* write Flex Data metadata text given by command lines */
static void write_metadata(FILE *file, unsigned int type, const char *text)
{
int len = strlen(text), size;
unsigned int format = SND_UMP_FLEX_DATA_MSG_FORMAT_START;
while (len > 0) {
snd_ump_msg_flex_data_t d = {};
if (len <= 12) {
if (format == SND_UMP_FLEX_DATA_MSG_FORMAT_CONTINUE)
format = SND_UMP_FLEX_DATA_MSG_FORMAT_END;
else
format = SND_UMP_FLEX_DATA_MSG_FORMAT_SINGLE;
size = len;
} else {
size = 12;
}
d.meta.type = SND_UMP_MSG_TYPE_FLEX_DATA;
d.meta.addrs = SND_UMP_FLEX_DATA_MSG_ADDR_GROUP;
d.meta.status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA;
d.meta.status = type;
d.meta.format = format;
/* keep the data in big endian */
d.raw[0] = htobe32(d.raw[0]);
/* strings are copied as-is in big-endian */
memcpy(d.meta.data, text, size);
fwrite(d.raw, 4, 4, file);
len -= size;
text += size;
format = SND_UMP_FLEX_DATA_MSG_FORMAT_CONTINUE;
}
}
/* write MIDI Clip file header and the configuration packets */
static void write_file_header(FILE *file)
{
int i;
/* header id */
fwrite("SMF2CLIP", 1, 8, file);
/* clip configuration header */
write_profiles(file);
for (i = 0; i < metadata_num; i++)
write_metadata(file, metadata_types[i], metadata_texts[i]);
/* first DCS */
write_dcs(file, 0);
write_dctpq(file);
}
/* write start bar */
static void start_bar(FILE *file)
{
int err;
/* start the queue */
err = snd_seq_start_queue(seq, queue, NULL);
check_snd("start queue", err);
snd_seq_drain_output(seq);
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"
" -r,--interactive Interactive mode\n"
" -s,--silent don't print messages\n"
" -P,--profile=file configuration profile UMP\n"
" --project=text put project name meta data text\n"
" --song=text put song name meta data text\n"
" --clip=text put MIDI clip name meta data text\n"
" --copyright=text put copyright notice meta data text\n"
" --composer=text put composer name meta data text\n"
" --lyricist=text put lyricist name meta data text\n"
" --arranger=text put arranger name meta data text\n"
" --publisher=text put publisher name meta data text\n"
" --publisher=text put publisher name meta data text\n"
" --publisher=text put publisher name meta data text\n"
" --performer=text put performer name meta data text\n"
" --accompany=text put accompany performer name meta data text\n"
" --date=text put recording date meta data text\n"
" --location=text put recording location meta data text\n",
argv0);
}
static void version(void)
{
fputs("arecordmidi version " SND_UTIL_VERSION_STR "\n", stderr);
}
static void sighandler(int sig ATTRIBUTE_UNUSED)
{
stop = 1;
}
#define OPT_META_BIT 0x1000
enum {
OPT_META_PROJECT = 0x1001,
OPT_META_SONG = 0x1002,
OPT_META_CLIP = 0x1003,
OPT_META_COPYRIGHT = 0x1004,
OPT_META_COMPOSER = 0x1005,
OPT_META_LYRICIST = 0x1006,
OPT_META_ARRANGER = 0x1007,
OPT_META_PUBLISHER = 0x1008,
OPT_META_PERFORMER = 0x1009,
OPT_META_ACCOMPANY = 0x100a,
OPT_META_DATE = 0x100b,
OPT_META_LOCATION = 0x100c,
};
int main(int argc, char *argv[])
{
static const char short_options[] = "hVp:b:t:n:u:rsP:";
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'},
{"interactive", 0, NULL, 'r'},
{"silent", 0, NULL, 's'},
{"profile", 1, NULL, 'P'},
/* meta data texts */
{"project", 1, NULL, OPT_META_PROJECT},
{"song", 1, NULL, OPT_META_SONG},
{"clip", 1, NULL, OPT_META_CLIP},
{"copyright", 1, NULL, OPT_META_COPYRIGHT},
{"composer", 1, NULL, OPT_META_COMPOSER},
{"lyricist", 1, NULL, OPT_META_LYRICIST},
{"arranger", 1, NULL, OPT_META_ARRANGER},
{"publisher", 1, NULL, OPT_META_PUBLISHER},
{"performer", 1, NULL, OPT_META_PERFORMER},
{"accompany", 1, NULL, OPT_META_ACCOMPANY},
{"date", 1, NULL, OPT_META_DATE},
{"location", 1, NULL, OPT_META_LOCATION},
{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;
int start = 0;
int interactive = 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;
case 'r':
interactive = 1;
break;
case 's':
silent = 1;
break;
case 'P':
profile_ump_file = optarg;
break;
default:
if (c & OPT_META_BIT) {
if (metadata_num >= MAX_METADATA)
fatal("Too many metadata given");
metadata_types[metadata_num] = c & 0x0f;
metadata_texts[metadata_num] = optarg;
metadata_num++;
break;
}
help(argv[0]);
return 1;
}
}
if (optind >= argc) {
fputs("Please specify a file to record to.\n", stderr);
return 1;
}
create_queue();
create_ump_client();
if (port_count)
connect_ports();
filename = argv[optind];
if (!strcmp(filename, "-")) {
file = stdout;
silent = 1; // imply silent mode
} else {
file = fopen(filename, "wb");
if (!file)
fatal("Cannot open %s - %s", filename, strerror(errno));
}
write_file_header(file);
if (interactive) {
if (!silent) {
printf("Press RETURN to start recording:");
fflush(stdout);
}
} else {
start_bar(file);
start = 1;
}
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 + 1));
for (;;) {
snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN);
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;
if (!silent) {
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 (start && 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) {
if (!silent)
fputs("Warning: Received signal before num_events\n", stdout);
}
write_end_clip(file);
if (file != stdout)
fclose(file);
snd_seq_close(seq);
return 0;
}

View file

@ -29,9 +29,7 @@
#include <poll.h>
#include <alsa/asoundlib.h>
#include "version.h"
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
#include <alsa/ump_msg.h>
#endif
enum {
VIEW_RAW, VIEW_NORMALIZED, VIEW_PERCENT
@ -41,11 +39,7 @@ static snd_seq_t *seq;
static int port_count;
static snd_seq_addr_t *ports;
static volatile sig_atomic_t stop = 0;
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
static int ump_version;
#else
#define ump_version 0
#endif
static int view_mode = VIEW_RAW;
/* prints an error message to stderr, and dies */
@ -368,7 +362,6 @@ static void dump_event(const snd_seq_event_t *ev)
}
}
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
static int group_number(unsigned char c)
{
if (view_mode != VIEW_RAW)
@ -442,7 +435,7 @@ static const char *midi2_velocity(unsigned int v)
snprintf(tmp, sizeof(tmp), "%.2f",
((double)v * 64.0) / 0x8000);
else
snprintf(tmp, sizeof(tmp), ".2%f",
snprintf(tmp, sizeof(tmp), "%.2f",
((double)(v - 0x8000) * 63.0) / 0x7fff + 64.0);
return tmp;
} else if (view_mode == VIEW_PERCENT) {
@ -595,6 +588,427 @@ static void dump_ump_midi2_event(const unsigned int *ump)
printf("\n");
}
static void dump_ump_utility_event(const unsigned int *ump)
{
unsigned char status = snd_ump_msg_status(ump);
unsigned int val = *ump & 0xfffff;
printf(" ");
switch (status) {
case SND_UMP_UTILITY_MSG_STATUS_NOOP:
printf("Noop\n");
break;
case SND_UMP_UTILITY_MSG_STATUS_JR_CLOCK:
printf("JR Clock value %d\n", val);
break;
case SND_UMP_UTILITY_MSG_STATUS_JR_TSTAMP:
printf("JR Timestamp value %d\n", val);
break;
case SND_UMP_UTILITY_MSG_STATUS_DCTPQ:
printf("DCTPQ value %d\n", val);
break;
case SND_UMP_UTILITY_MSG_STATUS_DC:
printf("DC Ticks value %d\n", val);
break;
default:
printf("UMP Utility event: status = %d, 0x%08x\n",
status, *ump);
break;
}
}
static void dump_ump_system_event(const unsigned int *ump)
{
const snd_ump_msg_system_t *m = (const snd_ump_msg_system_t *)ump;
printf("Group %2d, ", group_number(m->group));
switch (m->status) {
case SND_UMP_MSG_MIDI_TIME_CODE:
printf("MIDI Time Code value %d\n", m->parm1);
break;
case SND_UMP_MSG_SONG_POSITION:
printf("Song position pointer value %d\n",
((unsigned int)m->parm2 << 7) | m->parm1);
break;
case SND_UMP_MSG_SONG_SELECT:
printf("Song select value %d\n", m->parm1);
break;
case SND_UMP_MSG_TUNE_REQUEST:
printf("Tune request\n");
break;
case SND_UMP_MSG_TIMING_CLOCK:
printf("Timing clock\n");
break;
case SND_UMP_MSG_START:
printf("Start\n");
break;
case SND_UMP_MSG_CONTINUE:
printf("Continue\n");
break;
case SND_UMP_MSG_STOP:
printf("Stop\n");
break;
case SND_UMP_MSG_ACTIVE_SENSING:
printf("Active sensing\n");
break;
case SND_UMP_MSG_RESET:
printf("Reset\n");
break;
default:
printf("UMP System event: status = %d, 0x%08x\n",
m->status, *ump);
break;
}
}
static unsigned char ump_sysex7_data(const unsigned int *ump,
unsigned int offset)
{
return snd_ump_get_byte(ump, offset + 2);
}
static void dump_ump_sysex_status(const char *prefix, unsigned int status)
{
printf("%s ", prefix);
switch (status) {
case SND_UMP_SYSEX_STATUS_SINGLE:
printf("Single ");
break;
case SND_UMP_SYSEX_STATUS_START:
printf("Start ");
break;
case SND_UMP_SYSEX_STATUS_CONTINUE:
printf("Continue");
break;
case SND_UMP_SYSEX_STATUS_END:
printf("End ");
break;
default:
printf("(0x%04x)", status);
break;
}
}
static void dump_ump_sysex_event(const unsigned int *ump)
{
int i, length;
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
dump_ump_sysex_status("SysEx", snd_ump_sysex_msg_status(ump));
length = snd_ump_sysex_msg_length(ump);
printf(" length %d ", length);
if (length > 6)
length = 6;
for (i = 0; i < length; i++)
printf("%s%02x", i ? ":" : "", ump_sysex7_data(ump, i));
printf("\n");
}
static unsigned char ump_sysex8_data(const unsigned int *ump,
unsigned int offset)
{
return snd_ump_get_byte(ump, offset + 3);
}
static void dump_ump_sysex8_event(const unsigned int *ump)
{
int i, length;
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
dump_ump_sysex_status("SysEx8", snd_ump_sysex_msg_status(ump));
length = snd_ump_sysex_msg_length(ump);
printf(" length %d ", length);
printf(" stream %d ", (ump[0] >> 8) & 0xff);
if (length > 13)
length = 13;
for (i = 0; i < length; i++)
printf("%s%02x", i ? ":" : "", ump_sysex8_data(ump, i));
printf("\n");
}
static void dump_ump_mixed_data_event(const unsigned int *ump)
{
const snd_ump_msg_mixed_data_t *m =
(const snd_ump_msg_mixed_data_t *)ump;
int i;
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
switch (snd_ump_sysex_msg_status(ump)) {
case SND_UMP_MIXED_DATA_SET_STATUS_HEADER:
printf("MDS Header id=0x%x, bytes=%d, chunk=%d/%d, manufacturer=0x%04x, device=0x%04x, sub_id=0x%04x 0x%04x\n",
m->header.mds_id, m->header.bytes,
m->header.chunk, m->header.chunks,
m->header.manufacturer, m->header.device,
m->header.sub_id_1, m->header.sub_id_2);
break;
case SND_UMP_MIXED_DATA_SET_STATUS_PAYLOAD:
printf("MDS Payload id=0x%x, ", m->payload.mds_id);
for (i = 0; i < 14; i++)
printf("%s%02x", i ? ":" : "",
snd_ump_get_byte(ump, i + 2));
printf("\n");
break;
default:
printf("Extended Data (status 0x%x)\n",
snd_ump_sysex_msg_status(ump));
break;
}
}
static void dump_ump_extended_data_event(const unsigned int *ump)
{
unsigned char status = snd_ump_sysex_msg_status(ump);
if (status < 4)
dump_ump_sysex8_event(ump);
else
dump_ump_mixed_data_event(ump);
}
static void print_ump_string(const unsigned int *ump, unsigned int fmt,
unsigned int offset, int maxlen)
{
static const char *fmtstr[4] = { "Single", "Start", "Cont", "End" };
unsigned char buf[32];
int i = 0;
do {
buf[i] = snd_ump_get_byte(ump, offset);
if (!buf[i])
break;
if (buf[i] < 0x20)
buf[i] = '.';
offset++;
} while (++i < maxlen);
buf[i] = 0;
printf("%6s: %s", fmtstr[fmt], buf);
}
static void dump_ump_stream_event(const unsigned int *ump)
{
const snd_ump_msg_stream_t *s = (const snd_ump_msg_stream_t *)ump;
printf(" "); /* stream message is groupless */
switch (s->gen.status) {
case SND_UMP_STREAM_MSG_STATUS_EP_DISCOVERY:
printf("EP Discovery ver=%d/%d, filter=0x%x\n",
(ump[0] >> 8) & 0xff, ump[0] & 0xff, ump[1] & 0xff);
break;
case SND_UMP_STREAM_MSG_STATUS_EP_INFO:
printf("EP Info ver=%d/%d, static=%d, fb#=%d, midi2=%d, midi1=%d, rxjr=%d, txjr=%d\n",
(ump[0] >> 8) & 0xff, ump[0] & 0xff, (ump[1] >> 31),
(ump[1] >> 24) & 0x7f,
(ump[1] >> 9) & 1, (ump[1] >> 8) & 1,
(ump[1] >> 1) & 1, ump[1] & 1);
break;
case SND_UMP_STREAM_MSG_STATUS_DEVICE_INFO:
printf("Device Info sysid=%02x:%02x:%02x, family=%02x:%02x, model=%02x:%02x, rev=%02x:%02x:%02x:%02x\n",
(ump[1] >> 16) & 0x7f, (ump[1] >> 8) & 0x7f, ump[1] & 0x7f,
(ump[2] >> 16) & 0x7f, (ump[2] >> 24) & 0x7f,
ump[2] & 0x7f, (ump[2] >> 8) & 0x7f,
(ump[3] >> 24) & 0x7f, (ump[3] >> 16) & 0x7f,
(ump[3] >> 8) & 0x7f, ump[3] & 0x7f);
break;
case SND_UMP_STREAM_MSG_STATUS_EP_NAME:
printf("EP Name ");
print_ump_string(ump, (ump[0] >> 26) & 3, 2, 14);
printf("\n");
break;
case SND_UMP_STREAM_MSG_STATUS_PRODUCT_ID:
printf("Product Id ");
print_ump_string(ump, (ump[0] >> 26) & 3, 2, 14);
printf("\n");
break;
case SND_UMP_STREAM_MSG_STATUS_STREAM_CFG_REQUEST:
printf("Stream Cfg Req protocl=%d, rxjr=%d, txjr=%d\n",
(ump[0] >> 8) & 0xff, (ump[0] >> 1) & 1, ump[0] & 1);
break;
case SND_UMP_STREAM_MSG_STATUS_STREAM_CFG:
printf("Stream Cfg protocl=%d, rxjr=%d, txjr=%d\n",
(ump[0] >> 8) & 0xff, (ump[0] >> 1) & 1, ump[0] & 1);
break;
case SND_UMP_STREAM_MSG_STATUS_FB_DISCOVERY:
printf("FB Discovery fb#=%d, filter=0x%x\n",
(ump[0] >> 8) & 0xff, ump[0] & 0xff);
break;
case SND_UMP_STREAM_MSG_STATUS_FB_INFO:
printf("FB Info fb#=%d, active=%d, ui=%d, MIDI1=%d, dir=%d, group=%d-%d, MIDI-CI=%d, SysEx8=%d\n",
(ump[0] >> 8) & 0x7f, (ump[0] >> 15) & 1,
(ump[0] >> 4) & 3, (ump[0] >> 2) & 3, ump[0] & 3,
(ump[1] >> 24) & 0xff, (ump[1] >> 16) & 0xff,
(ump[1] >> 8) * 0xff, ump[1] & 0xff);
break;
case SND_UMP_STREAM_MSG_STATUS_FB_NAME:
printf("Product Id ");
printf("FB Name #%02d ", (ump[0] >> 8) & 0xff);
print_ump_string(ump, (ump[0] >> 26) & 3, 3, 13);
printf("\n");
break;
case SND_UMP_STREAM_MSG_STATUS_START_CLIP:
printf("Start Clip\n");
break;
case SND_UMP_STREAM_MSG_STATUS_END_CLIP:
printf("End Clip\n");
break;
default:
printf("UMP Stream event: status = %d, 0x%08x:0x%08x:0x%08x:0x%08x\n",
s->gen.status, ump[0], ump[1], ump[2], ump[3]);
break;
}
}
struct flexdata_text_prefix {
unsigned char status_bank;
unsigned char status;
const char *prefix;
};
static struct flexdata_text_prefix text_prefix[] = {
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PROJECT_NAME,
.prefix = "Project" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_SONG_NAME,
.prefix = "Song" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_MIDI_CLIP_NAME,
.prefix = "MIDI Clip" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_COPYRIGHT_NOTICE,
.prefix = "Copyright" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_COMPOSER_NAME,
.prefix = "Composer" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICIST_NAME,
.prefix = "Lyricist" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_ARRANGER_NAME,
.prefix = "Arranger" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PUBLISHER_NAME,
.prefix = "Publisher" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PRIMARY_PERFORMER,
.prefix = "Performer" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_ACCOMPANY_PERFORMAER,
.prefix = "Accompany Performer" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RECORDING_DATE,
.prefix = "Recording Date" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RECORDING_LOCATION,
.prefix = "Recording Location" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICS,
.prefix = "Lyrics" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICS_LANGUAGE,
.prefix = "Lyrics Language" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RUBY,
.prefix = "Ruby" },
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RUBY_LANGUAGE,
.prefix = "Ruby Language" },
{}
};
static const char *ump_meta_prefix(const snd_ump_msg_flex_data_t *fh)
{
static char buf[32];
int i;
for (i = 0; text_prefix[i].status_bank; i++) {
if (text_prefix[i].status_bank == fh->meta.status_bank &&
text_prefix[i].status == fh->meta.status)
return text_prefix[i].prefix;
}
sprintf(buf, "(%d:%d)", fh->meta.status_bank, fh->meta.status);
return buf;
}
static void dump_ump_flex_data_event(const unsigned int *ump)
{
const snd_ump_msg_flex_data_t *fh =
(const snd_ump_msg_flex_data_t *)ump;
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_TEMPO) {
printf("UMP Set Tempo value %d\n", fh->set_tempo.tempo);
return;
}
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_TIME_SIGNATURE) {
printf("UMP Set Time Signature value %d / %d, num_notes %d\n",
fh->set_time_sig.numerator, fh->set_time_sig.denominator,
fh->set_time_sig.num_notes);
return;
}
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_METRONOME) {
printf("UMP Set Metronome clock %d, bar %d/%d/%d, sub %d/%d\n",
fh->set_metronome.clocks_primary,
fh->set_metronome.bar_accent_1,
fh->set_metronome.bar_accent_2,
fh->set_metronome.bar_accent_3,
fh->set_metronome.subdivision_1,
fh->set_metronome.subdivision_2);
return;
}
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_KEY_SIGNATURE) {
printf("UMP Set Key Signature sharps/flats %d, tonic %d\n",
fh->set_key_sig.sharps_flats,
fh->set_key_sig.tonic_note);
return;
}
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_CHORD_NAME) {
printf("UMP Set Chord Name tonic %d %d %d, alt1 %d/%d, alt2 %d/%d, alt3 %d/%d, alt4 %d/%d, bass %d %d %d, alt1 %d/%d alt2 %d/%d\n",
fh->set_chord_name.tonic_sharp,
fh->set_chord_name.chord_tonic,
fh->set_chord_name.chord_type,
fh->set_chord_name.alter1_type,
fh->set_chord_name.alter1_degree,
fh->set_chord_name.alter2_type,
fh->set_chord_name.alter2_degree,
fh->set_chord_name.alter3_type,
fh->set_chord_name.alter3_degree,
fh->set_chord_name.alter4_type,
fh->set_chord_name.alter4_degree,
fh->set_chord_name.bass_sharp,
fh->set_chord_name.bass_note,
fh->set_chord_name.bass_type,
fh->set_chord_name.bass_alter1_type,
fh->set_chord_name.bass_alter1_type,
fh->set_chord_name.bass_alter2_degree,
fh->set_chord_name.bass_alter2_degree);
return;
}
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_METADATA ||
fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT) {
printf("Meta (%s) ", ump_meta_prefix(fh));
print_ump_string(ump, fh->meta.format, 4, 12);
printf("\n");
return;
}
printf("Flex Data: channel = %d, format = %d, addrs = %d, status_bank = %d, status = %d\n",
fh->meta.channel, fh->meta.format, fh->meta.addrs,
fh->meta.status_bank, fh->meta.status);
}
static void dump_ump_event(const snd_seq_ump_event_t *ev)
{
if (!snd_seq_ev_is_ump(ev)) {
@ -605,12 +1019,30 @@ static void dump_ump_event(const snd_seq_ump_event_t *ev)
printf("%3d:%-3d ", ev->source.client, ev->source.port);
switch (snd_ump_msg_type(ev->ump)) {
case SND_UMP_MSG_TYPE_UTILITY:
dump_ump_utility_event(ev->ump);
break;
case SND_UMP_MSG_TYPE_SYSTEM:
dump_ump_system_event(ev->ump);
break;
case SND_UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE:
dump_ump_midi1_event(ev->ump);
break;
case SND_UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE:
dump_ump_midi2_event(ev->ump);
break;
case SND_UMP_MSG_TYPE_DATA:
dump_ump_sysex_event(ev->ump);
break;
case SND_UMP_MSG_TYPE_EXTENDED_DATA:
dump_ump_extended_data_event(ev->ump);
break;
case SND_UMP_MSG_TYPE_FLEX_DATA:
dump_ump_flex_data_event(ev->ump);
break;
case SND_UMP_MSG_TYPE_STREAM:
dump_ump_stream_event(ev->ump);
break;
default:
printf("UMP event: type = %d, group = %d, status = %d, 0x%08x\n",
snd_ump_msg_type(ev->ump),
@ -620,7 +1052,6 @@ static void dump_ump_event(const snd_seq_ump_event_t *ev)
break;
}
}
#endif /* HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION */
static void list_ports(void)
{
@ -663,10 +1094,8 @@ static void help(const char *argv0)
" -N,--normalized-view show normalized values\n"
" -P,--percent-view show percent values\n"
" -R,--raw-view show raw values (default)\n"
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
" -u,--ump=version set client MIDI version (0=legacy, 1= UMP MIDI 1.0, 2=UMP MIDI2.0)\n"
" -r,--raw do not convert UMP and legacy events\n"
#endif
" -p,--port=client:port,... source port(s)\n",
argv0);
}
@ -683,11 +1112,7 @@ static void sighandler(int sig ATTRIBUTE_UNUSED)
int main(int argc, char *argv[])
{
static const char short_options[] = "hVlp:NPR"
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
"u:r"
#endif
;
static const char short_options[] = "hVlp:NPRu:r";
static const struct option long_options[] = {
{"help", 0, NULL, 'h'},
{"version", 0, NULL, 'V'},
@ -696,10 +1121,8 @@ int main(int argc, char *argv[])
{"normalized-view", 0, NULL, 'N'},
{"percent-view", 0, NULL, 'P'},
{"raw-view", 0, NULL, 'R'},
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
{"ump", 1, NULL, 'u'},
{"raw", 0, NULL, 'r'},
#endif
{0}
};
@ -734,15 +1157,15 @@ int main(int argc, char *argv[])
case 'N':
view_mode = VIEW_NORMALIZED;
break;
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
case 'u':
ump_version = atoi(optarg);
if (ump_version < 0 || ump_version > 2)
fatal("Invalid UMP version %d", ump_version);
snd_seq_set_client_midi_version(seq, ump_version);
break;
case 'r':
snd_seq_set_client_ump_conversion(seq, 0);
break;
#endif
default:
help(argv[0]);
return 1;
@ -784,7 +1207,6 @@ int main(int argc, char *argv[])
break;
for (;;) {
snd_seq_event_t *event;
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
snd_seq_ump_event_t *ump_ev;
if (ump_version > 0) {
err = snd_seq_ump_event_input(seq, &ump_ev);
@ -794,7 +1216,6 @@ int main(int argc, char *argv[])
dump_ump_event(ump_ev);
continue;
}
#endif
err = snd_seq_event_input(seq, &event);
if (err < 0)

5
seq/aseqsend/Makefile.am Normal file
View file

@ -0,0 +1,5 @@
AM_CPPFLAGS = -I$(top_srcdir)/include
EXTRA_DIST = aseqsend.1
bin_PROGRAMS = aseqsend
man_MANS = aseqsend.1

77
seq/aseqsend/aseqsend.1 Normal file
View file

@ -0,0 +1,77 @@
.TH ASEQSEND 1 "11 Mar 2024"
.SH NAME
aseqsend \- send arbitrary messages to selected ALSA MIDI seqencer port
.SH SYNOPSIS
\fBaseqsend\fP \-p client:port -s file-name
.br
\fBaseqsend\fP \-p client:port "hex encoded byte-string"
.SH DESCRIPTION
\fBaseqsend\fP is a command-line utility which allows one to send
SysEx (system exclusive) data to ALSA MIDI sequencer port.
It can also send any other MIDI commands.
Messages to be sent can be given in the last argument as hex encoded
byte string or can be read from raw binary file.
When sending several SysEx messages at once there is a delay of 1ms
after each message as default and can be set to different value with
option \-i.
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.
\fBaseqsend\fP can send UMP packets as MIDI 2.0 device by specifying
via \-u option as well, while the default operation is the legacy MIDI
1.0 byte stream.
.SH OPTIONS
.TP
\fI\-h, \-\-help\fP
Prints a list of options.
.TP
\fI\-V, \-\-version\fP
Prints the current version.
.TP
\fI\-l, \-\-list\FP
Prints a list of possible output ports.
.TP
\fI\-v, \-\-verbose\fP
Prints number of bytes actually sent
.TP
\fI\-p, -\-port=client:port\fP
Target port by number or name
.TP
\fI\-s, \-\-file=filename\fP
Send raw binary data from given file name
.TP
\fI\-i, \-\-interval=msec\fP
Interval between SysEx messages in milliseconds
.TP
\fI\-u, \-\-ump=version\fP
Specify the MIDI version. 0 for the legacy MIDI 1.0 (default),
1 for UMP MIDI 1.0 protocol and 2 for UMP MIDI 2.0 protocol.
When UMP MIDI 1.0 or MIDI 2.0 protocol is specified, \fBaseqsend\fP
reads the input as raw UMP packets, 4 each byte in big endian.
.SH EXAMPLES
\fBaseqsend -p 128:0 "F0 41 10 00 00 64 12 18 00 21 06 59 41 59 4E F7"\fP
\fBaseqsend -p 128:0 -s I7BulkDump.syx\fP
.SH SEE ALSO
\fBaseqdump(1)\fP
.SH AUTHOR
Miroslav Kovac <mixxoo@gmail.com>

458
seq/aseqsend/aseqsend.c Normal file
View file

@ -0,0 +1,458 @@
/*
* aseqsend.c - send arbitrary MIDI messages to selected ALSA MIDI seqencer port
*
* Copyright (c) 2005 Clemens Ladisch <clemens@ladisch.de>
* Copyright (c) 2024 Miroslav Kovac <mixxoo@gmail.com>
*
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define _GNU_SOURCE
#include "aconfig.h"
#include "version.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <alsa/asoundlib.h>
#include <alsa/ump_msg.h>
typedef unsigned char mbyte_t;
static snd_seq_t *seq;
static char *port_name = NULL;
static char *send_file_name = NULL;
static char *send_hex;
static mbyte_t *send_data;
static snd_seq_addr_t addr;
static int send_data_length;
static int sent_data_c;
static int ump_version;
static int sysex_interval = 1000; //us
static snd_midi_event_t *edev;
static void error(const char *format, ...)
{
va_list ap;
va_start(ap, format);
vfprintf(stderr, format, ap);
va_end(ap);
putc('\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);
}
static void usage(void)
{
printf(
"\nUsage: aseqsend -p target-port -s file-name|\"hex encoded bytes\"\n\n"
" -h,--help this help\n"
" -V,--version print current version\n"
" -v,--verbose verbose mode\n"
" -l,--list list all sequencer ports\n"
" -p,--port=c:p target port by number or name\n"
" -s,--file=name send binary data from given file name\n"
" -i,--interval=v interval between SysEx messages in miliseconds\n"
" -u,--ump=version MIDI version: 0=legacy (default), 1=MIDI1, 2=MIDI2\n\n");
}
static void version(void)
{
puts("aseqsend version " SND_UTIL_VERSION_STR);
}
static void *my_malloc(size_t size)
{
void *p = malloc(size);
if (!p) {
fatal("out of memory");
exit(EXIT_FAILURE);
}
return p;
}
static int hex_value(char c)
{
if ('0' <= c && c <= '9')
return c - '0';
if ('A' <= c && c <= 'F')
return c - 'A' + 10;
if ('a' <= c && c <= 'f')
return c - 'a' + 10;
error("invalid character %c", c);
return -1;
}
static void parse_data(void)
{
const char *p;
int i, value;
send_data = my_malloc(strlen(send_hex));
i = 0;
value = -1; /* value is >= 0 when the first hex digit of a byte has been read */
for (p = send_hex; *p; ++p) {
int digit;
if (isspace((unsigned char)*p)) {
if (value >= 0) {
send_data[i++] = value;
value = -1;
}
continue;
}
digit = hex_value(*p);
if (digit < 0) {
exit(EXIT_FAILURE);
}
if (value < 0) {
value = digit;
} else {
send_data[i++] = (value << 4) | digit;
value = -1;
}
}
if (value >= 0)
send_data[i++] = value;
send_data_length = i;
}
static void add_send_hex_data(const char *str)
{
int length;
char *s;
length = (send_hex ? strlen(send_hex) + 1 : 0) + strlen(str) + 1;
s = my_malloc(length);
if (send_hex) {
strcpy(s, send_hex);
strcat(s, " ");
} else {
s[0] = '\0';
}
strcat(s, str);
free(send_hex);
send_hex = s;
}
static void load_file(void)
{
int fd;
off_t length;
fd = open(send_file_name, O_RDONLY);
if (fd == -1) {
error("cannot open %s - %s", send_file_name, strerror(errno));
return;
}
length = lseek(fd, 0, SEEK_END);
if (length == (off_t)-1) {
error("cannot determine length of %s: %s", send_file_name, strerror(errno));
goto _error;
}
send_data = my_malloc(length);
lseek(fd, 0, SEEK_SET);
if (read(fd, send_data, length) != length) {
error("cannot read from %s: %s", send_file_name, strerror(errno));
goto _error;
}
if (length >= 4 && !memcmp(send_data, "MThd", 4)) {
error("%s is a Standard MIDI File; use aplaymidi to send it", send_file_name);
goto _error;
}
send_data_length = length;
goto _exit;
_error:
free(send_data);
send_data = NULL;
_exit:
close(fd);
}
/* 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_OUTPUT, 0);
check_snd("open sequencer", err);
/* set our client's name */
err = snd_seq_set_client_name(seq, "aseqsend");
check_snd("set client name", err);
err = snd_seq_set_client_midi_version(seq, ump_version);
check_snd("set client midi version", err);
}
static void create_port(void)
{
int err;
err = snd_seq_create_simple_port(seq, "aseqsend",
SND_SEQ_PORT_CAP_READ,
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
SND_SEQ_PORT_TYPE_APPLICATION);
check_snd("create port", err);
}
static void init_midi_event_encoder(void)
{
int err;
err = snd_midi_event_new(256, &edev);
check_snd("create midi event encoder", err);
}
static void list_ports(void)
{
snd_seq_client_info_t *cinfo;
snd_seq_port_info_t *pinfo;
snd_seq_client_info_alloca(&cinfo);
snd_seq_port_info_alloca(&pinfo);
puts(" Port Client name Port name");
snd_seq_client_info_set_client(cinfo, -1);
while (snd_seq_query_next_client(seq, cinfo) >= 0) {
int client = snd_seq_client_info_get_client(cinfo);
snd_seq_port_info_set_client(pinfo, client);
snd_seq_port_info_set_port(pinfo, -1);
while (snd_seq_query_next_port(seq, pinfo) >= 0) {
if ((snd_seq_port_info_get_capability(pinfo)
& SND_SEQ_PORT_CAP_WRITE)
!= SND_SEQ_PORT_CAP_WRITE)
continue;
printf("%3d:%-3d %-32.32s %s\n",
snd_seq_port_info_get_client(pinfo),
snd_seq_port_info_get_port(pinfo),
snd_seq_client_info_get_name(cinfo),
snd_seq_port_info_get_name(pinfo));
}
}
}
/* compose a UMP event, submit it, return the next data position */
static int send_ump(int pos)
{
int ump_len = 0, offset = 0;
unsigned int ump[4];
snd_seq_ump_event_t ev;
snd_seq_ump_ev_clear(&ev);
snd_seq_ev_set_source(&ev, 0);
snd_seq_ev_set_dest(&ev, addr.client, addr.port);
snd_seq_ev_set_direct(&ev);
do {
const mbyte_t *data = send_data + pos;
if (pos >= send_data_length)
return pos;
ump[offset] = (data[0] << 24) | (data[1] << 16) |
(data[2] << 8) | data[3];
if (!offset)
ump_len = snd_ump_packet_length(snd_ump_msg_type(ump));
offset++;
pos += 4;
} while (offset < ump_len);
snd_seq_ev_set_ump_data(&ev, ump, ump_len * 4);
snd_seq_ump_event_output(seq, &ev);
snd_seq_drain_output(seq);
sent_data_c += ump_len * 4;
return pos;
}
/* compose an event, submit it, return the next data position */
static int send_midi_bytes(int pos)
{
const mbyte_t *data = send_data + pos;
snd_seq_event_t ev;
int is_sysex = 0;
int end;
snd_seq_ev_clear(&ev);
snd_seq_ev_set_source(&ev, 0);
snd_seq_ev_set_dest(&ev, addr.client, addr.port);
snd_seq_ev_set_direct(&ev);
if (send_data[pos] == 0xf0) {
is_sysex = 1;
for (end = pos + 1; end < send_data_length; end++) {
if (send_data[end] == 0xf7)
break;
}
if (end == send_data_length)
fatal("SysEx is missing terminating byte (0xF7)");
end++;
snd_seq_ev_set_sysex(&ev, end - pos, send_data + pos);
} else {
end = pos;
while (!snd_midi_event_encode_byte(edev, *data++, &ev)) {
if (++end >= send_data_length)
return end;
}
end++;
}
snd_seq_event_output(seq, &ev);
snd_seq_drain_output(seq);
if (is_sysex)
usleep(sysex_interval);
sent_data_c += end - pos;
return end;
}
int main(int argc, char *argv[])
{
static const struct option long_options[] = {
{"help", 0, NULL, 'h'},
{"version", 0, NULL, 'V'},
{"verbose", 0, NULL, 'v'},
{"list", 0, NULL, 'l'},
{"port", 1, NULL, 'p'},
{"file", 1, NULL, 's'},
{"interval", 1, NULL, 'i'},
{"ump", 1, NULL, 'u'},
{0}
};
char c = 0;
char do_send_file = 0;
char do_port_list = 0;
char verbose = 0;
int k;
while ((c = getopt_long(argc, argv, "hi:Vvlp:s:u:", long_options, NULL)) != -1) {
switch (c) {
case 'h':
usage();
return 0;
case 'V':
version();
return 0;
case 'v':
verbose = 1;
break;
case 'l':
do_port_list = 1;
break;
case 'p':
port_name = optarg;
break;
case 's':
send_file_name = optarg;
do_send_file = 1;
break;
case 'i':
sysex_interval = atoi(optarg) * 1000; //ms--->us
break;
case 'u':
ump_version = atoi(optarg);
break;
default:
error("Try 'aseqsend -h' for more information.");
exit(EXIT_FAILURE);
}
}
if (argc < 2) {
usage();
exit(EXIT_FAILURE);
}
if (do_port_list){
init_seq();
list_ports();
exit(EXIT_SUCCESS);
}
if (port_name == NULL)
fatal("Output port must be specified!");
if (do_send_file) {
load_file();
} else {
/* no file specified ---> send hex bytes from cmd arguments*/
/* data for send can be specified as multiple arguments */
for (; argv[optind]; ++optind) {
add_send_hex_data(argv[optind]);
}
if (send_hex)
parse_data();
}
if (!send_data)
exit(EXIT_SUCCESS);
if (ump_version && (send_data_length % 4) != 0)
fatal("UMP data must be aligned to 4 bytes");
init_seq();
create_port();
if (!ump_version)
init_midi_event_encoder();
if (snd_seq_parse_address(seq, &addr, port_name) < 0) {
error("Unable to parse port name!");
exit(EXIT_FAILURE);
}
sent_data_c = 0; //counter of actually sent bytes
k = 0;
while (k < send_data_length) {
if (ump_version)
k = send_ump(k);
else
k = send_midi_bytes(k);
}
if (verbose)
printf("Sent : %u bytes\n", sent_data_c);
snd_seq_close(seq);
exit(EXIT_SUCCESS);
}

View file

@ -495,7 +495,7 @@ static int select_mode(struct intel_dmic_params *dmic, struct dmic_calc_configur
g_cic = mcic * mcic * mcic * mcic * mcic;
if (g_cic < 0) {
/* Erroneous decimation factor and CIC gain */
fprintf(stderr, "%s: erroneous decimation factor and CIC gain\n");
fprintf(stderr, "%s: erroneous decimation factor and CIC gain\n", __func__);
return -EINVAL;
}
@ -520,7 +520,7 @@ static int select_mode(struct intel_dmic_params *dmic, struct dmic_calc_configur
cfg->fir_a->length, gain_to_fir);
if (ret < 0) {
/* Invalid coefficient set found, should not happen. */
fprintf(stderr, "%s: invalid coefficient set found\n");
fprintf(stderr, "%s: invalid coefficient set found\n", __func__);
return -EINVAL;
}
} else {

View file

@ -697,13 +697,11 @@ static int pre_process_create_items(struct tplg_pre_processor *tplg_pp,
snd_config_iterator_t i, next;
snd_config_type_t type;
const char *class_id;
char *class_id_local;
int attr_count = 0;
int object_count = *object_count_offset;
int ret;
snd_config_get_id(top, &class_id);
class_id_local = strdup(class_id);
snd_config_for_each(i, next, cfg) {
snd_config_iterator_t i2, next2;
@ -719,7 +717,7 @@ static int pre_process_create_items(struct tplg_pre_processor *tplg_pp,
if (snd_config_get_id(n, &attribute) < 0)
continue;
ret = snd_config_make(&local_top, class_id_local, SND_CONFIG_TYPE_COMPOUND);
ret = snd_config_make(&local_top, class_id, SND_CONFIG_TYPE_COMPOUND);
snd_config_for_each(i2, next2, n) {
snd_config_t *n2, *new, *new_obj;
@ -787,7 +785,7 @@ static int pre_process_create_items(struct tplg_pre_processor *tplg_pp,
ret = pre_process_add_objects(tplg_pp, &object_count, top,
local_top, new);
if (ret < 0) {
SNDERR("failed to add objects of type %s\n", class_id_local);
SNDERR("failed to add objects of type %s\n", class_id);
goto err;
}
@ -921,6 +919,172 @@ static int pre_process_arrays(struct tplg_pre_processor *tplg_pp, snd_config_t *
return 0;
}
static int pre_process_subtree_copy(struct tplg_pre_processor *tplg_pp, snd_config_t *curr,
snd_config_t *top)
{
snd_config_iterator_t i, next;
snd_config_t *subtrees;
int ret;
ret = snd_config_search(curr, "SubTreeCopy", &subtrees);
if (ret < 0)
return 0;
snd_config_for_each(i, next, subtrees) {
snd_config_t *n, *source_cfg, *target_cfg, *type_cfg;
snd_config_t *source_tree, *target_tree, *tmp, *subtree_cfg;
const char *id, *source_id;
const char *type = NULL;
const char *target_id = NULL;
bool override = false;
n = snd_config_iterator_entry(i);
ret = snd_config_get_id(n, &id);
if (ret < 0) {
SNDERR("Failed to get ID for subtree copy\n");
return ret;
}
/* get the type of copy ex: override/extend if set, by default override is false */
ret = snd_config_search(n, "type", &type_cfg);
if (ret >= 0) {
ret = snd_config_get_string(type_cfg, &type);
if (ret >= 0) {
if (strcmp(type, "override") && strcmp(type, "extend")) {
SNDERR("Invalid value for sub tree copy type %s\n", type);
return ret;
}
if (!strcmp(type, "override"))
override = true;
}
}
ret = snd_config_search(n, "source", &source_cfg);
if (ret < 0) {
SNDERR("failed to get source config for subtree %s\n", id);
return ret;
}
/* if the target node is not set, the subtree will be copied to current node */
ret = snd_config_search(n, "target", &target_cfg);
if (ret >= 0) {
snd_config_t *temp_target;
char *s;
ret = snd_config_get_string(target_cfg, &target_id);
if (ret < 0) {
SNDERR("Invalid target id for subtree %s\n", id);
return ret;
}
/*
* create a temporary node with target_id and merge with top to avoid
* failure in case the target node ID doesn't exist already
*/
s = tplg_snprintf("%s {}", target_id);
if (!s)
return -ENOMEM;
ret = snd_config_load_string(&temp_target, s, 0);
free(s);
if (ret < 0) {
SNDERR("Cannot create temp node with target id %s\n", target_id);
return ret;
}
ret = snd_config_merge(top, temp_target, false);
if (ret < 0) {
SNDERR("Cannot merge temp node with target id %s\n", target_id);
return ret;
}
ret = snd_config_search(top, target_id, &target_tree);
if (ret < 0) {
SNDERR("failed to get target tree %s\n", target_id);
return ret;
}
} else {
target_tree = curr;
}
/* get the source tree node */
ret = snd_config_get_string(source_cfg, &source_id);
if (ret < 0) {
SNDERR("Invalid source node id for subtree %s\n", id);
return ret;
}
ret = snd_config_search(top, source_id, &source_tree);
if (ret < 0) {
SNDERR("failed to get source tree %s\n", source_id);
return ret;
}
/* get the subtree to be merged */
ret = snd_config_search(n, "tree", &subtree_cfg);
if (ret < 0)
subtree_cfg = NULL;
/* make a temp copy of the source tree */
ret = snd_config_copy(&tmp, source_tree);
if (ret < 0) {
SNDERR("failed to copy source tree for subtreecopy %s\n", id);
return ret;
}
/* merge the current block with the source tree */
ret = snd_config_merge(tmp, subtree_cfg, override);
if (ret < 0) {
snd_config_delete(tmp);
SNDERR("Failed to merge source tree w/ subtree %s\n", id);
return ret;
}
/* merge the merged block to the target tree */
ret = snd_config_merge(target_tree, tmp, override);
if (ret < 0) {
snd_config_delete(tmp);
SNDERR("Failed to merge subtree %s w/ target\n", id);
return ret;
}
}
/* all subtree copies have been processed, remove the node */
snd_config_delete(subtrees);
return 0;
}
static int pre_process_subtree_copies(struct tplg_pre_processor *tplg_pp, snd_config_t *top,
snd_config_t *curr)
{
snd_config_iterator_t i, next;
int ret;
if (snd_config_get_type(curr) != SND_CONFIG_TYPE_COMPOUND)
return 0;
/* process subtreecopies at this node */
ret = pre_process_subtree_copy(tplg_pp, curr, top);
if (ret < 0)
return ret;
/* process subtreecopies at all child nodes */
snd_config_for_each(i, next, curr) {
snd_config_t *n;
n = snd_config_iterator_entry(i);
/* process subtreecopies at this node */
ret = pre_process_subtree_copies(tplg_pp, top, n);
if (ret < 0)
return ret;
}
return 0;
}
#endif /* version < 1.2.6 */
int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_size,
@ -980,6 +1144,12 @@ int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_
fprintf(stderr, "Failed to process object arrays in input config\n");
goto err;
}
err = pre_process_subtree_copies(tplg_pp, tplg_pp->input_cfg, tplg_pp->input_cfg);
if (err < 0) {
SNDERR("Failed to process subtree copies in input config\n");
goto err;
}
#endif
err = pre_process_config(tplg_pp, tplg_pp->input_cfg);