mirror of
https://github.com/openharmony/third_party_alsa-utils.git
synced 2026-07-01 09:15:56 -04:00
!29 merge master into master
【third_party】三方库升级 libdrm 2.4.120 -> 2.4.130;alsa-lib 1.2.11 -> 1.2.15.3;alsa-utils 1.2.11 -> 1.2.15.2; Created-by: ykkwang Commit-by: w30051647 Merged-by: openharmony_ci Description: ### 一、内容说明(相关的Issue) https://gitcode.com/openharmony/third_party_libdrm/issues/63?ref=&did=3841281#tid-3841281 ### 二、建议测试周期和提测地址 建议测试完成时间:xxxx.xx.xx 投产上线时间:xxxx.xx.xx 提测地址:CI环境/压测环境 测试账号: ### 三、变更内容 * 3.1 关联PR列表 【third_party】三方库升级 libdrm 2.4.120 -> 2.4.130; alsa-lib 1.2.11 -> 1.2.15.3; alsa-utils 1.2.11 -> 1.2.15.2; * 3.2 数据库和部署说明 1. 常规更新 2. 重启unicorn 3. 重启sidekiq 4. 迁移任务:是否有迁移任务,没有写 "无" 5. rake脚本:`bundle exec xxx RAILS_ENV = production`;没有写 "无" * 3.4 其他技术优化内容(做了什么,变更了什么) - 重构了 xxxx 代码 - xxxx 算法优化 * 3.5 废弃通知(什么字段、方法弃用?) * 3.6 后向不兼容变更(是否有无法向后兼容的变更?) ### 四、研发自测点(自测哪些?冒烟用例全部自测?) 自测测试结论: ### 五、测试关注点(需要提醒QA重点关注的、可能会忽略的地方) 检查点: | 需求名称 | 是否影响xx公共模块 | 是否需要xx功能 | 需求升级是否依赖其他子产品 | |------|------------|----------|---------------| | xxx | 否 | 需要 | 不需要 | | | | | | 接口测试: 性能测试: 并发测试: 其他: See merge request: openharmony/third_party_alsa-utils!29
This commit is contained in:
@@ -148,6 +148,9 @@ ohos_executable("alsactl") {
|
||||
"alsactl/monitor.c",
|
||||
"alsactl/state.c",
|
||||
"alsactl/utils.c",
|
||||
"alsactl/export.c",
|
||||
"alsactl/wait.c",
|
||||
"alsactl/boot_params.c",
|
||||
]
|
||||
|
||||
include_dirs = [
|
||||
@@ -166,7 +169,7 @@ ohos_executable("alsactl") {
|
||||
install_images = [ "system" ]
|
||||
}
|
||||
|
||||
group("alsa-utils") {
|
||||
group("entry") {
|
||||
deps = [
|
||||
":aconnect",
|
||||
":alsactl",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"Name": "alsa-utils",
|
||||
"License": "GPL V2",
|
||||
"License File": "COPYING",
|
||||
"Version Number": "v1.2.11",
|
||||
"Version Number": "v1.2.15.2",
|
||||
"Owner": "zhangyunhu@huawei.com",
|
||||
"Upstream URL": "https://github.com/alsa-project/alsa-utils",
|
||||
"Description": "This package contains the command line utilities for the ALSA project.",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
EXTRA_DIST = alsa-info.sh alsa-info.sh.1
|
||||
EXTRA_DIST = alsa-info.sh alsa-info.sh.8
|
||||
sbin_SCRIPTS = alsa-info.sh
|
||||
man_MANS = alsa-info.sh.1
|
||||
man_MANS = alsa-info.sh.8
|
||||
|
||||
+21
-1
@@ -244,7 +244,7 @@ withpackages() {
|
||||
RPM="$(command -v rpmquery)"
|
||||
DPKG="$(command -v dpkg)"
|
||||
[ -n "$RPM$DPKG" ] || return
|
||||
local PATTERN='(alsa-(lib|oss|plugins|tools|(topology|ucm)-conf|utils|sof-firmware)|libalsa|tinycompress|sof-firmware)'
|
||||
local PATTERN='(alsa-(lib|oss|plugins|tools|ucm|(topology|ucm)-conf|utils|sof-firmware)|libalsa|tinycompress|sof-firmware)'
|
||||
{
|
||||
echo "!!Packages installed"
|
||||
echo "!!--------------------"
|
||||
@@ -461,6 +461,20 @@ if [ -d /sys/bus/acpi/devices ]; then
|
||||
done
|
||||
fi
|
||||
|
||||
# Check for SoundWire ACPI _adr device status
|
||||
if [ -d /sys/bus/acpi/devices ]; then
|
||||
for f in /sys/bus/acpi/devices/*/adr; do
|
||||
ACPI_ADR=$(cat $f 2>/dev/null);
|
||||
if [[ "$ACPI_ADR" -ne 0 ]]; then
|
||||
case $ACPI_ADR in
|
||||
0x??????025d*) echo "Realtek $ACPI_ADR" >>$TEMPDIR/sdwstatus.tmp;;
|
||||
0x??????01fa*) echo "Cirrus Logic $ACPI_ADR" >>$TEMPDIR/sdwstatus.tmp;;
|
||||
0x??????0102*) echo "TI $ACPI_ADR" >>$TEMPDIR/sdwstatus.tmp;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
awk '{ print $2 " (card " $1 ")" }' < /proc/asound/modules > $TEMPDIR/alsamodules.tmp 2> /dev/null
|
||||
cat /proc/asound/cards > $TEMPDIR/alsacards.tmp
|
||||
if [[ ! -z "$LSPCI" ]]; then
|
||||
@@ -528,6 +542,12 @@ echo "" >> $FILE
|
||||
cat $TEMPDIR/acpidevicestatus.tmp >> $FILE
|
||||
echo "" >> $FILE
|
||||
echo "" >> $FILE
|
||||
echo "!!ACPI SoundWire Device Status Information" >> $FILE
|
||||
echo "!!---------------" >> $FILE
|
||||
echo "" >> $FILE
|
||||
cat $TEMPDIR/sdwstatus.tmp >> $FILE
|
||||
echo "" >> $FILE
|
||||
echo "" >> $FILE
|
||||
echo "!!Kernel Information" >> $FILE
|
||||
echo "!!------------------" >> $FILE
|
||||
echo "" >> $FILE
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
.TH ALSA-INFO.SH 1 "13 January 2016"
|
||||
.SH NAME
|
||||
alsa-info.sh \- command\-line utility to gather information about
|
||||
the ALSA subsystem
|
||||
.SH SYNOPSIS
|
||||
\fBalsa-info.sh\fP [\fIoptions\fP]
|
||||
|
||||
.SH DESCRIPTION
|
||||
\fBalsa-info.sh\fP is a command\-line utility gathering information
|
||||
about the ALSA subsystem. It is used mostly for debugging purposes.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fI\-\-upload\fP
|
||||
Upload contents to the server (www.alsa-project.org or pastebin.ca).
|
||||
.TP
|
||||
\fI\-\-no-upload\fP
|
||||
Do not upload contents to the remote server.
|
||||
.TP
|
||||
\fI\-\-stdout\fP
|
||||
Print information to standard output.
|
||||
.TP
|
||||
\fI\-\-output FILE\fP
|
||||
Specify file for output in no-upload mode.
|
||||
.TP
|
||||
\fI\-\-debug\fP
|
||||
Run utility as normal, but will not delete file (usually
|
||||
/tmp/alsa-info.txt).
|
||||
.TP
|
||||
\fI\-\-with-aplay\fP
|
||||
Includes output from \fIaplay -l\fP.
|
||||
.TP
|
||||
\fI\-\-with-amixer\fP
|
||||
Includes output from \fIamixer\fP.
|
||||
.TP
|
||||
\fI\-\-with-alsactl\fP
|
||||
Includes output from \fIalsactl\fP.
|
||||
.TP
|
||||
\fI\-\-with-configs\fP
|
||||
Includes output from ~/.asoundrc and /etc/asound.conf if they exist.
|
||||
.TP
|
||||
\fI\-\-update\fP
|
||||
Check server for updates.
|
||||
.TP
|
||||
\fI\-\-about\fP
|
||||
Print information about authors.
|
||||
|
||||
.SH EXAMPLES
|
||||
|
||||
.TP
|
||||
\fBalsa-info.sh \-\-no-upload\fR
|
||||
Will gather all information and show the output file.
|
||||
|
||||
.SH SEE ALSO
|
||||
\fB
|
||||
aplay(1)
|
||||
amixer(1)
|
||||
alsactl(1)
|
||||
\fP
|
||||
|
||||
.SH AUTHOR
|
||||
\fBalsa-info.sh\fP was created by the ALSA team, see \fI\-\-about\fP .
|
||||
+5
-3
@@ -1,3 +1,5 @@
|
||||
alsa-store.service
|
||||
alsa-restore.service
|
||||
90-alsa-restore.rules
|
||||
conf/90-alsa-restore.rules
|
||||
conf/alsa-state.service
|
||||
conf/alsa-store.service
|
||||
conf/alsa-restore.service
|
||||
conf/alsa-card-wait@.service
|
||||
|
||||
+26
-14
@@ -11,8 +11,9 @@ AM_CFLAGS = -D_GNU_SOURCE
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/include
|
||||
|
||||
alsactl_SOURCES=alsactl.c state.c lock.c utils.c init_parse.c init_ucm.c \
|
||||
daemon.c monitor.c clean.c info.c
|
||||
alsactl_SOURCES=alsactl.c state.c lock.c utils.c wait.c \
|
||||
init_parse.c init_ucm.c boot_params.c \
|
||||
daemon.c monitor.c clean.c info.c export.c
|
||||
|
||||
alsactl_CFLAGS=$(AM_CFLAGS) -D__USE_GNU \
|
||||
-DSYS_ASOUNDRC=\"$(ASOUND_STATE_DIR)/asound.state\" \
|
||||
@@ -24,13 +25,14 @@ noinst_HEADERS=alsactl.h list.h init_sysdeps.c init_utils_string.c \
|
||||
init_utils_run.c init_sysfs.c
|
||||
|
||||
udevrules_DATA = \
|
||||
90-alsa-restore.rules
|
||||
conf/90-alsa-restore.rules
|
||||
|
||||
if HAVE_SYSTEMD
|
||||
|
||||
systemdsystemunit_DATA = \
|
||||
alsa-state.service \
|
||||
alsa-restore.service
|
||||
conf/alsa-state.service \
|
||||
conf/alsa-restore.service \
|
||||
conf/alsa-card-wait@.service
|
||||
|
||||
install-data-hook:
|
||||
$(MKDIR_P) -m 0755 \
|
||||
@@ -43,30 +45,40 @@ install-data-hook:
|
||||
endif
|
||||
|
||||
edit = \
|
||||
extratest=$$(echo ' $(ALSACTL_UDEV_EXTRATEST)' | sed -e 's/__/ /g' -e 's/^ $$//'); \
|
||||
args=$$(echo ' $(ALSACTL_UDEV_ARGS)' | sed -e 's/__/ /g' -e 's/^ $$//'); \
|
||||
mkdir -p conf; \
|
||||
$(SED) -r -e 's,@sbindir\@,$(sbindir),g' \
|
||||
-e 's,@mydatadir\@,$(mydatadir),g' \
|
||||
-e 's,@daemonswitch\@,$(ALSACTL_DAEMONSWITCH),g' \
|
||||
-e 's,@asoundrcfile\@,$(ASOUND_STATE_DIR)/asound.state,g' \
|
||||
-e "s;@extratest\@;$${extratest};g" \
|
||||
-e "s;@args\@;$${args};g" \
|
||||
< $< > $@ || rm $@
|
||||
|
||||
alsa-state.service: alsa-state.service.in
|
||||
conf/alsa-state.service: conf/alsa-state.service.in
|
||||
$(edit)
|
||||
|
||||
alsa-restore.service: alsa-restore.service.in
|
||||
conf/alsa-restore.service: conf/alsa-restore.service.in
|
||||
$(edit)
|
||||
|
||||
90-alsa-restore.rules: 90-alsa-restore.rules.in
|
||||
conf/alsa-card-wait@.service: conf/alsa-card-wait@.service.in
|
||||
$(edit)
|
||||
|
||||
conf/90-alsa-restore.rules: conf/90-alsa-restore.rules.in
|
||||
$(edit)
|
||||
|
||||
EXTRA_DIST += \
|
||||
alsa-state.service.in \
|
||||
alsa-restore.service.in \
|
||||
90-alsa-restore.rules.in
|
||||
conf/alsa-state.service.in \
|
||||
conf/alsa-restore.service.in \
|
||||
conf/alsa-card-wait@.service.in \
|
||||
conf/90-alsa-restore.rules.in
|
||||
|
||||
CLEANFILES = \
|
||||
alsa-state.service \
|
||||
alsa-restore.service \
|
||||
90-alsa-restore.rules
|
||||
conf/alsa-state.service \
|
||||
conf/alsa-restore.service \
|
||||
conf/alsa-card-wait@.service \
|
||||
conf/90-alsa-restore.rules
|
||||
|
||||
%.7: %.xml
|
||||
xmlto man $?
|
||||
|
||||
+2
-2
@@ -90,7 +90,7 @@ Note that the configuration hooks are evaluated.
|
||||
.SH OPTIONS
|
||||
|
||||
.TP
|
||||
\fI\-h, \-\-help\fP
|
||||
\fI\-h, \-\-help\fP
|
||||
Help: show available flags and commands.
|
||||
|
||||
.TP
|
||||
@@ -223,7 +223,7 @@ aplay(1),
|
||||
alsactl_init(7)
|
||||
\fP
|
||||
|
||||
.SH BUGS
|
||||
.SH BUGS
|
||||
None known.
|
||||
|
||||
.SH AUTHOR
|
||||
|
||||
+28
-1
@@ -44,14 +44,19 @@
|
||||
#ifndef SYS_LOCKPATH
|
||||
#define SYS_LOCKPATH "/var/lock"
|
||||
#endif
|
||||
#ifndef SYS_CARD_GROUP
|
||||
#define SYS_CARD_GROUP SYS_ASOUND_DIR "/card-group.state"
|
||||
#endif
|
||||
|
||||
int debugflag = 0;
|
||||
int force_restore = 1;
|
||||
int ignore_nocards = 0;
|
||||
int do_lock = 0;
|
||||
int use_syslog = 0;
|
||||
int do_export = 0;
|
||||
char *command;
|
||||
char *statefile = NULL;
|
||||
char *groupfile = SYS_CARD_GROUP;
|
||||
char *lockpath = SYS_LOCKPATH;
|
||||
char *lockfile = SYS_LOCKFILE;
|
||||
|
||||
@@ -78,6 +83,7 @@ static struct arg args[] = {
|
||||
{ 'v', "version", "print version of this program" },
|
||||
{ HEADER, NULL, "Available state options:" },
|
||||
{ FILEARG | 'f', "file", "configuration file (default " SYS_ASOUNDRC ")" },
|
||||
{ FILEARG | 'G', "group-file", "card group configuration file (default " SYS_CARD_GROUP ")" },
|
||||
{ FILEARG | 'a', "config-dir", "boot / hotplug configuration directory (default " SYS_ASOUND_DIR ")" },
|
||||
{ 'l', "lock", "use file locking to serialize concurrent access" },
|
||||
{ 'L', "no-lock", "do not use file locking to serialize concurrent access" },
|
||||
@@ -92,6 +98,7 @@ static struct arg args[] = {
|
||||
{ FILEARG | 'r', "runstate", "save restore and init state to this file (only errors)" },
|
||||
{ 0, NULL, " default settings is 'no file set'" },
|
||||
{ 'R', "remove", "remove runstate file at first, otherwise append errors" },
|
||||
{ 'Y', "export", "export card state as key=value pairs (restore command only)" },
|
||||
{ INTARG | 'p', "period", "store period in seconds for the daemon command" },
|
||||
{ FILEARG | 'e', "pid-file", "pathname for the process id (daemon mode)" },
|
||||
{ HEADER, NULL, "Available init options:" },
|
||||
@@ -105,6 +112,7 @@ static struct arg args[] = {
|
||||
#ifdef HAVE_ALSA_USE_CASE_H
|
||||
{ 'D', "ucm-defaults", "execute also the UCM 'defaults' section" },
|
||||
{ 'U', "no-ucm", "don't init with UCM" },
|
||||
{ 'm', "force-ucm-restore", "force UCM restore for boot card groups" },
|
||||
#if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION
|
||||
{ 'X', "ucm-nodev", "show UCM no device errors" },
|
||||
#endif
|
||||
@@ -115,6 +123,7 @@ static struct arg args[] = {
|
||||
{ CARDCMD, "restore", "load current driver setup for one or each soundcards" },
|
||||
{ EMPCMD, NULL, " from configuration file" },
|
||||
{ CARDCMD, "nrestore", "like restore, but notify the daemon to rescan soundcards" },
|
||||
{ CARDCMD, "wrestore", "wait for card ready, then restore" },
|
||||
{ CARDCMD, "init", "initialize driver to a default state" },
|
||||
{ CARDCMD, "daemon", "store state periodically for one or each soundcards" },
|
||||
{ CARDCMD, "rdaemon", "like daemon but do the state restore at first" },
|
||||
@@ -301,6 +310,9 @@ int main(int argc, char *argv[])
|
||||
case 'f':
|
||||
cfgfile = optarg;
|
||||
break;
|
||||
case 'G':
|
||||
groupfile = optarg;
|
||||
break;
|
||||
case 'a':
|
||||
cfgdir = optarg;
|
||||
break;
|
||||
@@ -341,6 +353,9 @@ int main(int argc, char *argv[])
|
||||
case 'U':
|
||||
initflags |= FLAG_UCM_DISABLED;
|
||||
break;
|
||||
case 'm':
|
||||
initflags |= FLAG_UCM_RESTORE;
|
||||
break;
|
||||
case 'X':
|
||||
initflags |= FLAG_UCM_NODEV;
|
||||
break;
|
||||
@@ -350,6 +365,9 @@ int main(int argc, char *argv[])
|
||||
case 'R':
|
||||
removestate = 1;
|
||||
break;
|
||||
case 'Y':
|
||||
do_export = 1;
|
||||
break;
|
||||
case 'P':
|
||||
force_restore = 0;
|
||||
break;
|
||||
@@ -441,7 +459,11 @@ int main(int argc, char *argv[])
|
||||
syslog(LOG_INFO, "alsactl " SND_UTIL_VERSION_STR " daemon started");
|
||||
}
|
||||
|
||||
#if SND_LIB_VER(1, 2, 15) < SND_LIB_VERSION
|
||||
snd_lib_error_set_handler(error_handler);
|
||||
#else
|
||||
snd_lib_log_set_handler(log_handler);
|
||||
#endif
|
||||
|
||||
if (!strcmp(cmd, "init")) {
|
||||
res = init(cfgdir, initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname);
|
||||
@@ -450,10 +472,15 @@ int main(int argc, char *argv[])
|
||||
res = save_state(cfgfile, cardname);
|
||||
} else if (!strcmp(cmd, "restore") ||
|
||||
!strcmp(cmd, "rdaemon") ||
|
||||
!strcmp(cmd, "nrestore")) {
|
||||
!strcmp(cmd, "nrestore") ||
|
||||
!strcmp(cmd, "wrestore")) {
|
||||
if (removestate)
|
||||
remove(statefile);
|
||||
if (!strcmp(cmd, "wrestore"))
|
||||
initflags |= FLAG_UCM_WAIT;
|
||||
res = load_state(cfgdir, cfgfile, initfile, initflags, cardname, init_fallback);
|
||||
if (do_export && res >= 0)
|
||||
res = export_cards(cardname);
|
||||
if (!strcmp(cmd, "rdaemon")) {
|
||||
do_nice(use_nice, sched_idle);
|
||||
res = state_daemon(cfgfile, cardname, period, pidfile);
|
||||
|
||||
+38
-2
@@ -2,14 +2,17 @@
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#define LOCK_TIMEOUT 10
|
||||
#define DEFAULT_SYNC_TIME 20
|
||||
|
||||
extern int debugflag;
|
||||
extern int force_restore;
|
||||
extern int ignore_nocards;
|
||||
extern int do_lock;
|
||||
extern int use_syslog;
|
||||
extern int do_export;
|
||||
extern char *command;
|
||||
extern char *statefile;
|
||||
extern char *groupfile;
|
||||
extern char *lockpath;
|
||||
extern char *lockfile;
|
||||
|
||||
@@ -24,7 +27,8 @@ void info_(const char *fcn, long line, const char *fmt, ...);
|
||||
void error_(const char *fcn, long line, const char *fmt, ...);
|
||||
void cerror_(const char *fcn, long line, int cond, const char *fmt, ...);
|
||||
void dbg_(const char *fcn, long line, const char *fmt, ...);
|
||||
void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...);
|
||||
void error_handler(const char *file, int line, const char *function, int errcode, const char *fmt, ...);
|
||||
void log_handler(int prio, int interface, const char *file, int line, const char *function, int errcode, const char *fmt, va_list arg);
|
||||
|
||||
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
|
||||
#define info(...) do { info_(__func__, __LINE__, __VA_ARGS__); } while (0)
|
||||
@@ -43,6 +47,19 @@ void error_handler(const char *file, int line, const char *function, int err, co
|
||||
#define FLAG_UCM_BOOT (1<<2)
|
||||
#define FLAG_UCM_DEFAULTS (1<<3)
|
||||
#define FLAG_UCM_NODEV (1<<4)
|
||||
#define FLAG_UCM_RESTORE (1<<5)
|
||||
#define FLAG_UCM_WAIT (1<<6)
|
||||
|
||||
enum {
|
||||
CARD_STATE_WAIT = 1, /* skip configuration (wait for sync) */
|
||||
CARD_STATE_SKIP = 2, /* skip card */
|
||||
CARD_STATE_RESTORED = 3, /* card was restored */
|
||||
};
|
||||
|
||||
static inline bool card_state_is_okay(int state)
|
||||
{
|
||||
return state >= CARD_STATE_WAIT && state <= CARD_STATE_RESTORED;
|
||||
}
|
||||
|
||||
void snd_card_iterator_init(struct snd_card_iterator *iter, int cardno);
|
||||
int snd_card_iterator_sinit(struct snd_card_iterator *iter, const char *cardname);
|
||||
@@ -51,15 +68,28 @@ int snd_card_iterator_error(struct snd_card_iterator *iter);
|
||||
|
||||
int load_configuration(const char *file, snd_config_t **top, int *open_failed);
|
||||
int init(const char *cfgdir, const char *file, int flags, const char *cardname);
|
||||
int init_ucm(int flags, int cardno);
|
||||
int init_ucm(const char *cfgdir, int flags, int cardno);
|
||||
bool validate_boot_time(long long boot_time, long long current_time, long long synctime);
|
||||
int read_boot_params(snd_ctl_t *handle, long long *boot_time, long long *sync_time, long long *restore_time, long long *primary_card);
|
||||
int write_boot_params(snd_ctl_t *handle, long long boot_time, long long sync_time, long long restore_time, long long primary_card);
|
||||
int card_group_load(snd_config_t **config);
|
||||
int card_group_save(snd_config_t *config);
|
||||
int card_group_get_int64(snd_config_t *config_group, const char *id, long long *val);
|
||||
int card_group_set_int64(snd_config_t *config_group, const char *id, long long val);
|
||||
int check_boot_params_validity(snd_ctl_t *handle, int cardno, char **boot_card_group, bool *valid, bool *in_sync, bool *restored, int *primary_card, long long *synctime);
|
||||
int update_boot_params(snd_ctl_t *handle, int cardno, const char *boot_card_group, bool valid, bool restored, long long synctime);
|
||||
int boot_params_remove_card(int cardno);
|
||||
int state_lock(const char *file, int timeout);
|
||||
int state_unlock(int lock_fd, const char *file);
|
||||
int card_lock(int card_number, int timeout);
|
||||
int card_unlock(int lock_fd, int card_number);
|
||||
int group_state_lock(const char *file, int timeout);
|
||||
int group_state_unlock(int lock_fd, const char *file);
|
||||
int save_state(const char *file, const char *cardname);
|
||||
int load_state(const char *cfgdir, const char *file,
|
||||
const char *initfile, int initflags,
|
||||
const char *cardname, int do_init);
|
||||
int wait_for_card(long long timeout, int cardno);
|
||||
int power(const char *argv[], int argc);
|
||||
int monitor(const char *name);
|
||||
int general_info(const char *name);
|
||||
@@ -68,6 +98,12 @@ int state_daemon(const char *file, const char *cardname, int period,
|
||||
int state_daemon_kill(const char *pidfile, const char *cmd);
|
||||
int clean(const char *cardname, char *const *extra_args);
|
||||
int snd_card_clean_cfgdir(const char *cfgdir, int cardno);
|
||||
void add_linked_card(int cardno);
|
||||
|
||||
/* export */
|
||||
|
||||
int export_card_state_set(int card, int state);
|
||||
int export_cards(const char *cardname);
|
||||
|
||||
/* utils */
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="add", SUBSYSTEM=="sound", KERNEL=="controlC*", KERNELS!="card*",@extratest@ GOTO="alsa_restore_go"
|
||||
GOTO="alsa_restore_end"
|
||||
|
||||
LABEL="alsa_restore_go"
|
||||
|
||||
ENV{ALSA_CARD_NUMBER}="$attr{device/number}"
|
||||
|
||||
# mark HDA analog card; HDMI/DP card does not have capture devices
|
||||
DRIVERS=="snd_hda_intel", TEST=="device/pcmC$env{ALSA_CARD_NUMBER}D0c", RUN+="/bin/sh -c 'echo ALSA_CARD_HDA_ANALOG=$env{ALSA_CARD_NUMBER} >> /run/udev/alsa-hda-analog-card'"
|
||||
|
||||
# check for ACP hardware
|
||||
TEST=="device/device/acp3x-dmic-capture", GOTO="alsa_hda_analog"
|
||||
TEST=="device/device/acp6x-dmic-capture", GOTO="alsa_hda_analog"
|
||||
TEST=="device/device/acp63-dmic-capture", GOTO="alsa_hda_analog"
|
||||
TEST=="device/device/acp-dmic-codec", GOTO="alsa_hda_analog"
|
||||
GOTO="alsa_restore_std"
|
||||
|
||||
LABEL="alsa_hda_analog"
|
||||
# restore configuration for profile with combined cards (HDA + digital mic)
|
||||
TEST!="/run/udev/alsa-hda-analog-card", GOTO="alsa_restore_std"
|
||||
IMPORT{program}="/usr/bin/cat /run/udev/alsa-hda-analog-card"
|
||||
ENV{ALSA_CARD_HDA_ANALOG}!="", ENV{ALSA_CARD_NUMBER}="$env{ALSA_CARD_HDA_ANALOG}"
|
||||
|
||||
LABEL="alsa_restore_std"
|
||||
TEST!="@daemonswitch@", IMPORT{program}="@sbindir@/alsactl@args@ --export restore $env{ALSA_CARD_NUMBER}"
|
||||
TEST=="@daemonswitch@", IMPORT{program}="@sbindir@/alsactl@args@ --export nrestore $env{ALSA_CARD_NUMBER}"
|
||||
ENV{ALSA_CARD_STATE}=="waiting", ENV{SYSTEMD_WANTS}="alsa-card-wait@$env{ALSA_CARD_NUMBER}.service"
|
||||
|
||||
LABEL="alsa_restore_end"
|
||||
@@ -0,0 +1,12 @@
|
||||
#
|
||||
# ALSA card initialization handler for cards in waiting state
|
||||
# This service is triggered by udev when ALSA_CARD_STATE=waiting
|
||||
#
|
||||
|
||||
[Unit]
|
||||
Description=ALSA Card Initialization for card %I
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=no
|
||||
ExecStart=@sbindir@/alsactl@args@ wrestore %i
|
||||
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# Note that two different ALSA card state management schemes exist and they
|
||||
# can be switched using a file exist check - /etc/alsa/state-daemon.conf .
|
||||
#
|
||||
|
||||
[Unit]
|
||||
Description=Save/Restore Sound Card State
|
||||
ConditionPathExists=!@daemonswitch@
|
||||
ConditionPathExistsGlob=/dev/snd/control*
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=true
|
||||
ExecStart=-@sbindir@/alsactl restore
|
||||
ExecStop=-@sbindir@/alsactl store
|
||||
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Note that two different ALSA card state management schemes exist and they
|
||||
# can be switched using a file exist check - /etc/alsa/state-daemon.conf .
|
||||
#
|
||||
|
||||
[Unit]
|
||||
Description=Manage Sound Card State (restore and store)
|
||||
ConditionPathExists=@daemonswitch@
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=-@sbindir@/alsactl -s -n 19 -c rdaemon
|
||||
ExecStop=-@sbindir@/alsactl -s kill save_and_quit
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Advanced Linux Sound Architecture Control Program - Export
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Export variable syntax:
|
||||
*
|
||||
* For single card:
|
||||
*
|
||||
* ALSA_CARD_NUMBER=<number>
|
||||
* ALSA_CARD_STATE=<state>
|
||||
*
|
||||
* For multiple cards:
|
||||
*
|
||||
* ALSA_CARD#_STATE=<state> # where # is replaced with the card number
|
||||
*
|
||||
* State list:
|
||||
*
|
||||
* active # card was initialized and active
|
||||
* skip # card was skipped (other card in group)
|
||||
* waiting # card is waiting for the initial configuration
|
||||
*/
|
||||
|
||||
#include "aconfig.h"
|
||||
#include "version.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "alsactl.h"
|
||||
|
||||
/* Global array to store card states (0 = active, 1 = waiting) */
|
||||
static int export_card_state[32];
|
||||
|
||||
/**
|
||||
* export_card_state_set - Set card state
|
||||
* @card: Card number
|
||||
* @state: Card state (0 = active, 1 = waiting)
|
||||
*
|
||||
* Returns 0 on success, negative error code on failure
|
||||
*/
|
||||
int export_card_state_set(int card, int state)
|
||||
{
|
||||
/* Check bounds */
|
||||
if (card < 0 || (unsigned long)card >= ARRAY_SIZE(export_card_state))
|
||||
return -EINVAL;
|
||||
|
||||
export_card_state[card] = state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* export_card_state_print - Export and print card state information
|
||||
* @iter: Card iterator containing current card information
|
||||
*
|
||||
* Prints key=value pairs based on iterator's single flag and export_card_state array.
|
||||
* Returns 0 on success, negative error code on failure
|
||||
*/
|
||||
static int export_card_state_print(struct snd_card_iterator *iter)
|
||||
{
|
||||
const char *state;
|
||||
int istate;
|
||||
|
||||
if (!iter)
|
||||
return -EINVAL;
|
||||
|
||||
/* Check bounds */
|
||||
if (iter->card < 0 || (unsigned long)iter->card >= ARRAY_SIZE(export_card_state))
|
||||
return -EINVAL;
|
||||
|
||||
/* Determine state from export_card_state array */
|
||||
istate = export_card_state[iter->card];
|
||||
switch (istate) {
|
||||
case CARD_STATE_WAIT: state = "waiting"; break;
|
||||
case CARD_STATE_SKIP: state = "skip"; break;
|
||||
default: state = "active"; break;
|
||||
}
|
||||
|
||||
if (iter->single) {
|
||||
/* Single card export format */
|
||||
printf("ALSA_CARD_NUMBER=%d\n", iter->card);
|
||||
printf("ALSA_CARD_STATE=%s\n", state);
|
||||
} else {
|
||||
/* Multiple cards export format */
|
||||
printf("ALSA_CARD%d_STATE=%s\n", iter->card, state);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* export_cards - Export state for all cards
|
||||
* @cardname: Card name or NULL for all cards
|
||||
*
|
||||
* Returns 0 on success, negative error code on failure
|
||||
*/
|
||||
int export_cards(const char *cardname)
|
||||
{
|
||||
struct snd_card_iterator iter;
|
||||
const char *name;
|
||||
int ret;
|
||||
|
||||
ret = snd_card_iterator_sinit(&iter, cardname);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
while ((name = snd_card_iterator_next(&iter)) != NULL) {
|
||||
ret = export_card_state_print(&iter);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
ret = snd_card_iterator_error(&iter);
|
||||
|
||||
return ret;
|
||||
}
|
||||
+9
-3
@@ -35,7 +35,9 @@ static int pcm_device_list(snd_ctl_t *ctl, snd_pcm_stream_t stream, bool *first)
|
||||
streamfirst = true;
|
||||
while (1) {
|
||||
if ((err = snd_ctl_pcm_next_device(ctl, &dev)) < 0) {
|
||||
error("snd_ctl_pcm_next_device");
|
||||
if (err == ENOTTY) /* no kernel support */
|
||||
return 0;
|
||||
error("snd_ctl_pcm_next_device: %s", snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
if (dev < 0)
|
||||
@@ -102,7 +104,9 @@ static int rawmidi_device_list(snd_ctl_t *ctl, snd_rawmidi_stream_t stream, bool
|
||||
streamfirst = true;
|
||||
while (1) {
|
||||
if ((err = snd_ctl_rawmidi_next_device(ctl, &dev)) < 0) {
|
||||
error("snd_ctl_rawmidi_next_device");
|
||||
if (err == ENOTTY) /* no kernel support */
|
||||
return 0;
|
||||
error("snd_ctl_rawmidi_next_device: %s", snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
if (dev < 0)
|
||||
@@ -159,7 +163,9 @@ static int hwdep_device_list(snd_ctl_t *ctl)
|
||||
first = true;
|
||||
while (1) {
|
||||
if ((err = snd_ctl_hwdep_next_device(ctl, &dev)) < 0) {
|
||||
error("snd_ctl_pcm_next_device");
|
||||
if (err == ENOTTY) /* no kernel support */
|
||||
return 0;
|
||||
error("snd_ctl_pcm_next_device: %s", snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
if (dev < 0)
|
||||
|
||||
@@ -1761,8 +1761,8 @@ int init(const char *cfgdir, const char *filename, int flags, const char *cardna
|
||||
lasterr = err;
|
||||
continue;
|
||||
}
|
||||
err = init_ucm(flags, iter.card);
|
||||
if (err == 0)
|
||||
err = init_ucm(cfgdir, flags, iter.card);
|
||||
if (err == 0 || card_state_is_okay(err))
|
||||
continue;
|
||||
err = init_space(&space, iter.card);
|
||||
if (err != 0)
|
||||
|
||||
+262
-21
@@ -21,6 +21,8 @@
|
||||
|
||||
#include "aconfig.h"
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include "alsactl.h"
|
||||
|
||||
#ifdef HAVE_ALSA_USE_CASE_H
|
||||
@@ -28,51 +30,290 @@
|
||||
#include <alsa/use-case.h>
|
||||
|
||||
/*
|
||||
* Keep it as simple as possible. Execute commands from the
|
||||
* FixedBootSequence and BootSequence only.
|
||||
* Helper: Check if card should skip initialization based on boot parameters
|
||||
* Returns: 1 if should skip, 2 if should skip other card, 0 if should continue, negative on error
|
||||
* If check_restored is true, also checks if card state is already restored
|
||||
*/
|
||||
int init_ucm(int flags, int cardno)
|
||||
static int should_skip_initialization(snd_ctl_t *ctl, int cardno, int flags,
|
||||
char **boot_card_group, bool *valid,
|
||||
bool *in_sync, bool *restored, int *primary_card,
|
||||
long long *synctime)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = check_boot_params_validity(ctl, cardno, boot_card_group, valid, in_sync, restored, primary_card, synctime);
|
||||
if (err < 0) {
|
||||
dbg("boot parameters validity failed: %s", snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
/* do nothing for other cards in group */
|
||||
if (*valid && *primary_card != cardno) {
|
||||
dbg("Skipping card %d - not primary (primary is %d)", cardno, *primary_card);
|
||||
return CARD_STATE_SKIP;
|
||||
}
|
||||
|
||||
/* for immediate initialization, caller must set UCM force-restore flag */
|
||||
if (*valid && *in_sync && (flags & FLAG_UCM_RESTORE) == 0) {
|
||||
dbg("Skipping card %d - in sync and no force-restore flag", cardno);
|
||||
return CARD_STATE_WAIT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper: Get boot card group configuration from UCM
|
||||
* Returns: 0 on success, negative on error
|
||||
*/
|
||||
static int get_boot_card_group_config(snd_use_case_mgr_t *uc_mgr, char **boot_card_group, long long *synctime)
|
||||
{
|
||||
char *sync_time = NULL;
|
||||
int err;
|
||||
|
||||
err = snd_use_case_get(uc_mgr, "=BootCardGroup", (const char **)boot_card_group);
|
||||
if (err != 0 || *boot_card_group == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
dbg("BootCardGroup found: %s", *boot_card_group);
|
||||
|
||||
/* Get optional sync time */
|
||||
err = snd_use_case_get(uc_mgr, "=BootCardSyncTime", (const char **)&sync_time);
|
||||
if (err == 0 && sync_time != NULL) {
|
||||
char *endptr;
|
||||
errno = 0;
|
||||
*synctime = strtoll(sync_time, &endptr, 10);
|
||||
if (errno != 0 || *endptr != '\0' || endptr == sync_time) {
|
||||
error("Invalid BootCardSyncTime value '%s'", sync_time);
|
||||
*synctime = DEFAULT_SYNC_TIME;
|
||||
}
|
||||
free(sync_time);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper: Open UCM manager with appropriate flags
|
||||
* Returns: 0 on success, negative on error
|
||||
*/
|
||||
static int open_ucm_manager(snd_use_case_mgr_t **uc_mgr, int cardno, int flags,
|
||||
bool valid, bool fixed_boot)
|
||||
{
|
||||
char id[64], *nodev, *in_boot;
|
||||
int err;
|
||||
|
||||
nodev = (flags & FLAG_UCM_NODEV) ? "" : "-";
|
||||
in_boot = (valid || !fixed_boot) ? "" : "<<<InBoot=1>>>";
|
||||
snprintf(id, sizeof(id), "%s%shw:%d", nodev, in_boot, cardno);
|
||||
|
||||
err = snd_use_case_mgr_open(uc_mgr, id);
|
||||
dbg("ucm open '%s': %d", id, err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper: Reopen UCM manager without InBoot flag
|
||||
* Returns: 0 on success, negative on error
|
||||
*/
|
||||
static int reopen_ucm_manager(snd_use_case_mgr_t **uc_mgr, int cardno, int flags)
|
||||
{
|
||||
char id[64], *nodev;
|
||||
int err;
|
||||
|
||||
snd_use_case_mgr_close(*uc_mgr);
|
||||
|
||||
nodev = (flags & FLAG_UCM_NODEV) ? "" : "-";
|
||||
snprintf(id, sizeof(id), "%shw:%d", nodev, cardno);
|
||||
|
||||
err = snd_use_case_mgr_open(uc_mgr, id);
|
||||
dbg("ucm reopen '%s': %d", id, err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper: Execute boot sequences
|
||||
* Returns: 0 on success, negative on error
|
||||
*/
|
||||
static int execute_boot_sequences(const char *cfgdir, snd_use_case_mgr_t *uc_mgr,
|
||||
int cardno, int flags, bool fixed_boot)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
if (fixed_boot) {
|
||||
err = snd_card_clean_cfgdir(cfgdir, cardno);
|
||||
if (err < 0) {
|
||||
dbg("ucm clean cfgdir: %d", err);
|
||||
return err;
|
||||
}
|
||||
err = snd_use_case_set(uc_mgr, "_fboot", NULL);
|
||||
dbg("ucm _fboot: %d", err);
|
||||
if (err == -ENOENT && (flags & FLAG_UCM_BOOT) != 0) {
|
||||
/* _fboot not found but _boot requested - continue */
|
||||
err = 0;
|
||||
} else if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & FLAG_UCM_BOOT) {
|
||||
err = snd_use_case_set(uc_mgr, "_boot", NULL);
|
||||
dbg("ucm _boot: %d", err);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if ((flags & FLAG_UCM_DEFAULTS) != 0)
|
||||
err = snd_use_case_set(uc_mgr, "_defaults", NULL);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute commands from the FixedBootSequence and BootSequence.
|
||||
* Handle also card groups.
|
||||
* Returns: 0 = success, 1 = skip this card (e.g. linked or in-sync), negative on error
|
||||
*/
|
||||
int init_ucm(const char *cfgdir, int flags, int cardno)
|
||||
{
|
||||
snd_use_case_mgr_t *uc_mgr;
|
||||
char id[32], *nodev;
|
||||
int err;
|
||||
char id[64];
|
||||
char *boot_card_group = NULL, *boot_card_group_verify = NULL;
|
||||
bool fixed_boot, valid = false, in_sync = false, restored = false;
|
||||
snd_ctl_t *ctl = NULL;
|
||||
int err, primary_card = -1, lock_fd = -1;
|
||||
long long synctime = -1;
|
||||
|
||||
if (flags & FLAG_UCM_DISABLED) {
|
||||
dbg("ucm disabled");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
nodev = (flags & FLAG_UCM_NODEV) ? "" : "-";
|
||||
snprintf(id, sizeof(id), "%shw:%d", nodev, cardno);
|
||||
err = snd_use_case_mgr_open(&uc_mgr, id);
|
||||
dbg("ucm open '%s': %d", id, err);
|
||||
if (err < 0)
|
||||
fixed_boot = (flags & FLAG_UCM_FBOOT) != 0;
|
||||
|
||||
snprintf(id, sizeof(id), "hw:%d", cardno);
|
||||
err = snd_ctl_open(&ctl, id, 0);
|
||||
if (err < 0) {
|
||||
dbg("UCM: unable to open control device '%s': %s", id, snd_strerror(err));
|
||||
return err;
|
||||
if (flags & FLAG_UCM_FBOOT) {
|
||||
err = snd_use_case_set(uc_mgr, "_fboot", NULL);
|
||||
dbg("ucm _fboot: %d", err);
|
||||
if (err == -ENOENT && (flags & FLAG_UCM_BOOT) != 0) {
|
||||
/* nothing */
|
||||
}
|
||||
|
||||
err = should_skip_initialization(ctl, cardno, flags, &boot_card_group, &valid,
|
||||
&in_sync, &restored, &primary_card, &synctime);
|
||||
if (err != 0)
|
||||
goto _fin;
|
||||
|
||||
if (valid) {
|
||||
if (restored) {
|
||||
err = CARD_STATE_RESTORED;
|
||||
goto _fin;
|
||||
}
|
||||
lock_fd = group_state_lock(groupfile, LOCK_TIMEOUT);
|
||||
if (lock_fd < 0) {
|
||||
err = lock_fd;
|
||||
goto _fin;
|
||||
}
|
||||
}
|
||||
|
||||
err = open_ucm_manager(&uc_mgr, cardno, flags, valid, fixed_boot);
|
||||
if (err < 0)
|
||||
goto _fin;
|
||||
|
||||
if (!fixed_boot)
|
||||
goto _execute_boot;
|
||||
|
||||
if (!valid) {
|
||||
err = get_boot_card_group_config(uc_mgr, &boot_card_group, &synctime);
|
||||
if (err == -ENOENT) {
|
||||
/* No BootCardGroup - remove any existing boot params */
|
||||
err = boot_params_remove_card(cardno);
|
||||
if (err < 0)
|
||||
goto _error;
|
||||
goto _execute_boot;
|
||||
} else if (err < 0) {
|
||||
goto _error;
|
||||
}
|
||||
|
||||
if (lock_fd < 0) {
|
||||
lock_fd = group_state_lock(groupfile, LOCK_TIMEOUT);
|
||||
if (lock_fd < 0) {
|
||||
err = lock_fd;
|
||||
goto _error;
|
||||
}
|
||||
}
|
||||
|
||||
err = should_skip_initialization(ctl, cardno, flags, &boot_card_group_verify,
|
||||
&valid, &in_sync, &restored, &primary_card, &synctime);
|
||||
if (err != 0)
|
||||
goto _error;
|
||||
|
||||
if (valid && (boot_card_group_verify == NULL || strcmp(boot_card_group_verify, boot_card_group) != 0)) {
|
||||
dbg("expected different boot card group (got '%s', expected '%s')", boot_card_group_verify, boot_card_group);
|
||||
err = -EINVAL;
|
||||
goto _error;
|
||||
}
|
||||
|
||||
if ((flags & FLAG_UCM_RESTORE) == 0 && (!valid || restored)) {
|
||||
dbg("Skipping card %d (group '%s') - %s and no force-restore flag", cardno, boot_card_group,
|
||||
!valid ? "validity not passed" : "already restored");
|
||||
if (!valid) {
|
||||
/* create initial 'Boot' element */
|
||||
err = update_boot_params(ctl, cardno, boot_card_group, 0, restored, synctime);
|
||||
if (err < 0)
|
||||
goto _error;
|
||||
}
|
||||
err = restored ? CARD_STATE_RESTORED : CARD_STATE_WAIT;
|
||||
goto _error;
|
||||
}
|
||||
|
||||
err = reopen_ucm_manager(&uc_mgr, cardno, flags);
|
||||
if (err < 0)
|
||||
goto _fin;
|
||||
}
|
||||
if (flags & FLAG_UCM_BOOT) {
|
||||
err = snd_use_case_set(uc_mgr, "_boot", NULL);
|
||||
dbg("ucm _boot: %d", err);
|
||||
|
||||
_execute_boot:
|
||||
if (fixed_boot)
|
||||
restored = true;
|
||||
|
||||
if (boot_card_group) {
|
||||
err = update_boot_params(ctl, cardno, boot_card_group, valid, restored, synctime);
|
||||
if (err < 0)
|
||||
goto _error;
|
||||
if ((flags & FLAG_UCM_DEFAULTS) != 0)
|
||||
err = snd_use_case_set(uc_mgr, "_defaults", NULL);
|
||||
}
|
||||
|
||||
err = execute_boot_sequences(cfgdir, uc_mgr, cardno, flags, fixed_boot);
|
||||
if (err < 0)
|
||||
goto _error;
|
||||
|
||||
err = 0;
|
||||
|
||||
_error:
|
||||
snd_use_case_mgr_close(uc_mgr);
|
||||
_fin:
|
||||
if (fixed_boot && primary_card >= 0 && primary_card != cardno) {
|
||||
/* remove card specific configuration files for other cards in group */
|
||||
int clean_err = snd_card_clean_cfgdir(cfgdir, cardno);
|
||||
if (clean_err < 0) {
|
||||
dbg("ucm clean cfgdir: %d", clean_err);
|
||||
if (err >= 0)
|
||||
err = clean_err;
|
||||
}
|
||||
}
|
||||
if (lock_fd >= 0)
|
||||
group_state_unlock(lock_fd, groupfile);
|
||||
if (ctl)
|
||||
snd_ctl_close(ctl);
|
||||
free(boot_card_group);
|
||||
free(boot_card_group_verify);
|
||||
dbg("ucm init complete %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int init_ucm(int flags, int cardno)
|
||||
int init_ucm(const char *cfgdir, int flags, int cardno)
|
||||
{
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
@@ -181,6 +181,40 @@ int state_unlock(int _fd, const char *file)
|
||||
return err;
|
||||
}
|
||||
|
||||
static void group_state_lock_file(char *buf, size_t buflen)
|
||||
{
|
||||
const char *name = strrchr(groupfile, '/');
|
||||
if (name && name[0])
|
||||
name++;
|
||||
else
|
||||
name = "card-group.state";
|
||||
snprintf(buf, buflen, "%s/%s.lock", lockpath, name);
|
||||
}
|
||||
|
||||
int group_state_lock(const char *file, int timeout)
|
||||
{
|
||||
char fn[PATH_SIZE];
|
||||
int err;
|
||||
|
||||
group_state_lock_file(fn, sizeof(fn));
|
||||
err = state_lock_(fn, 1, timeout, -1);
|
||||
if (err < 0)
|
||||
error("file %s lock error: %s", file, strerror(-err));
|
||||
return err;
|
||||
}
|
||||
|
||||
int group_state_unlock(int _fd, const char *file)
|
||||
{
|
||||
char fn[PATH_SIZE];
|
||||
int err;
|
||||
|
||||
group_state_lock_file(fn, sizeof(fn));
|
||||
err = state_lock_(fn, 0, 10, _fd);
|
||||
if (err < 0)
|
||||
error("file %s unlock error: %s", file, strerror(-err));
|
||||
return err;
|
||||
}
|
||||
|
||||
static void card_lock_file(char *buf, size_t buflen, int card_number)
|
||||
{
|
||||
snprintf(buf, buflen, "%s/card%i.lock", lockpath, card_number);
|
||||
|
||||
+1
-1
@@ -176,7 +176,7 @@ static int check_control_cdev(int infd, bool *retry)
|
||||
ssize_t len = read(infd, buf, sizeof(*ev) + NAME_MAX);
|
||||
if (len < 0) {
|
||||
if (errno != EAGAIN)
|
||||
err = errno;
|
||||
err = -errno;
|
||||
break;
|
||||
} else if (len == 0) {
|
||||
break;
|
||||
|
||||
+218
-128
@@ -29,6 +29,33 @@
|
||||
#include <errno.h>
|
||||
#include "alsactl.h"
|
||||
|
||||
static int linked_cards[16];
|
||||
|
||||
static void init_linked_cards(void)
|
||||
{
|
||||
size_t index;
|
||||
|
||||
for (index = 0; index < ARRAY_SIZE(linked_cards); index++)
|
||||
linked_cards[index] = -1;
|
||||
}
|
||||
|
||||
void add_linked_card(int cardno)
|
||||
{
|
||||
size_t index;
|
||||
|
||||
for (index = 0; index < ARRAY_SIZE(linked_cards); index++) {
|
||||
if (linked_cards[index] == cardno)
|
||||
return;
|
||||
}
|
||||
|
||||
for (index = 0; index < ARRAY_SIZE(linked_cards); index++) {
|
||||
if (linked_cards[index] < 0) {
|
||||
linked_cards[index] = cardno;
|
||||
return;
|
||||
}
|
||||
}
|
||||
error("Too many linked cards!");
|
||||
}
|
||||
|
||||
static char *id_str(snd_ctl_elem_id_t *id)
|
||||
{
|
||||
@@ -1201,32 +1228,114 @@ static int restore_config_value2(snd_ctl_t *handle, snd_ctl_elem_info_t *info,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_control(snd_ctl_t *handle, snd_config_t *control,
|
||||
int *maxnumid, int doit)
|
||||
static int parse_config_id(snd_config_t *control, snd_ctl_elem_id_t *id,
|
||||
snd_config_t **comment, snd_config_t **value, int doit)
|
||||
{
|
||||
snd_config_iterator_t i, next;
|
||||
const char *numid;
|
||||
snd_ctl_elem_iface_t iface = -1;
|
||||
long device = -1;
|
||||
long subdevice = -1;
|
||||
const char *name = NULL;
|
||||
long index = -1;
|
||||
|
||||
snd_ctl_elem_id_clear(id);
|
||||
if (snd_config_get_id(control, &numid) < 0)
|
||||
return -EINVAL;
|
||||
snd_config_for_each(i, next, control) {
|
||||
snd_config_t *n = snd_config_iterator_entry(i);
|
||||
const char *fld;
|
||||
if (snd_config_get_id(n, &fld) < 0)
|
||||
continue;
|
||||
if (strcmp(fld, "comment") == 0) {
|
||||
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
|
||||
cerror(doit, "control.%s.%s is invalid", numid, fld);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (comment)
|
||||
*comment = n;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "iface") == 0) {
|
||||
iface = (snd_ctl_elem_iface_t)config_iface(n);
|
||||
if (iface < 0)
|
||||
return -EINVAL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "device") == 0) {
|
||||
if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
|
||||
cerror(doit, "control.%s.%s is invalid", numid, fld);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (snd_config_get_integer(n, &device) < 0)
|
||||
return -EINVAL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "subdevice") == 0) {
|
||||
if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
|
||||
cerror(doit, "control.%s.%s is invalid", numid, fld);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (snd_config_get_integer(n, &subdevice) < 0)
|
||||
return -EINVAL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "name") == 0) {
|
||||
if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) {
|
||||
cerror(doit, "control.%s.%s is invalid", numid, fld);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (snd_config_get_string(n, &name) < 0)
|
||||
return -EINVAL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "index") == 0) {
|
||||
if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
|
||||
cerror(doit, "control.%s.%s is invalid", numid, fld);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (snd_config_get_integer(n, &index) < 0)
|
||||
return -EINVAL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "value") == 0) {
|
||||
if (value)
|
||||
*value = n;
|
||||
continue;
|
||||
}
|
||||
cerror(doit, "unknown control.%s.%s field", numid, fld);
|
||||
}
|
||||
if (device < 0)
|
||||
device = 0;
|
||||
if (subdevice < 0)
|
||||
subdevice = 0;
|
||||
if (index < 0)
|
||||
index = 0;
|
||||
snd_ctl_elem_id_set_interface(id, iface);
|
||||
snd_ctl_elem_id_set_device(id, device);
|
||||
snd_ctl_elem_id_set_subdevice(id, subdevice);
|
||||
snd_ctl_elem_id_set_name(id, name);
|
||||
snd_ctl_elem_id_set_index(id, index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_control(snd_ctl_t *handle, snd_config_t *control, int doit)
|
||||
{
|
||||
snd_ctl_elem_value_t *ctl;
|
||||
snd_ctl_elem_info_t *info;
|
||||
snd_config_iterator_t i, next;
|
||||
unsigned int numid1;
|
||||
snd_ctl_elem_iface_t iface = -1;
|
||||
int iface1;
|
||||
const char *name1;
|
||||
snd_ctl_elem_id_t *elem_id, *elem_id1;
|
||||
unsigned int numid;
|
||||
snd_ctl_elem_type_t type;
|
||||
unsigned int count;
|
||||
long device = -1;
|
||||
long device1;
|
||||
long subdevice = -1;
|
||||
long subdevice1;
|
||||
const char *name = NULL;
|
||||
long index1;
|
||||
long index = -1;
|
||||
snd_config_t *value = NULL;
|
||||
snd_config_t *comment = NULL;
|
||||
unsigned int idx;
|
||||
int err;
|
||||
char *set;
|
||||
const char *id;
|
||||
snd_ctl_elem_id_alloca(&elem_id);
|
||||
snd_ctl_elem_id_alloca(&elem_id1);
|
||||
snd_ctl_elem_value_alloca(&ctl);
|
||||
snd_ctl_elem_info_alloca(&info);
|
||||
if (snd_config_get_type(control) != SND_CONFIG_TYPE_COMPOUND) {
|
||||
@@ -1239,92 +1348,17 @@ static int set_control(snd_ctl_t *handle, snd_config_t *control,
|
||||
return -EINVAL;
|
||||
}
|
||||
numid = atoi(id);
|
||||
if ((int)numid > *maxnumid)
|
||||
*maxnumid = numid;
|
||||
snd_config_for_each(i, next, control) {
|
||||
snd_config_t *n = snd_config_iterator_entry(i);
|
||||
const char *fld;
|
||||
if (snd_config_get_id(n, &fld) < 0)
|
||||
continue;
|
||||
if (strcmp(fld, "comment") == 0) {
|
||||
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
|
||||
cerror(doit, "control.%d.%s is invalid", numid, fld);
|
||||
return -EINVAL;
|
||||
}
|
||||
comment = n;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "iface") == 0) {
|
||||
iface = (snd_ctl_elem_iface_t)config_iface(n);
|
||||
if (iface < 0)
|
||||
return -EINVAL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "device") == 0) {
|
||||
if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
|
||||
cerror(doit, "control.%d.%s is invalid", numid, fld);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (snd_config_get_integer(n, &device) < 0)
|
||||
return -EINVAL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "subdevice") == 0) {
|
||||
if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
|
||||
cerror(doit, "control.%d.%s is invalid", numid, fld);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (snd_config_get_integer(n, &subdevice) < 0)
|
||||
return -EINVAL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "name") == 0) {
|
||||
if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) {
|
||||
cerror(doit, "control.%d.%s is invalid", numid, fld);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (snd_config_get_string(n, &name) < 0)
|
||||
return -EINVAL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "index") == 0) {
|
||||
if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
|
||||
cerror(doit, "control.%d.%s is invalid", numid, fld);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (snd_config_get_integer(n, &index) < 0)
|
||||
return -EINVAL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(fld, "value") == 0) {
|
||||
value = n;
|
||||
continue;
|
||||
}
|
||||
cerror(doit, "unknown control.%d.%s field", numid, fld);
|
||||
}
|
||||
if (!value) {
|
||||
cerror(doit, "missing control.%d.value", numid);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (device < 0)
|
||||
device = 0;
|
||||
if (subdevice < 0)
|
||||
subdevice = 0;
|
||||
if (index < 0)
|
||||
index = 0;
|
||||
|
||||
err = parse_config_id(control, elem_id, &comment, &value, doit);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = -EINVAL;
|
||||
if (!force_restore) {
|
||||
snd_ctl_elem_info_set_numid(info, numid);
|
||||
err = snd_ctl_elem_info(handle, info);
|
||||
}
|
||||
if (err < 0 && name) {
|
||||
snd_ctl_elem_info_set_numid(info, 0);
|
||||
snd_ctl_elem_info_set_interface(info, iface);
|
||||
snd_ctl_elem_info_set_device(info, device);
|
||||
snd_ctl_elem_info_set_subdevice(info, subdevice);
|
||||
snd_ctl_elem_info_set_name(info, name);
|
||||
snd_ctl_elem_info_set_index(info, index);
|
||||
id = snd_ctl_elem_id_get_name(elem_id);
|
||||
if (err < 0 && id && id[0]) {
|
||||
snd_ctl_elem_info_set_id(info, elem_id);
|
||||
err = snd_ctl_elem_info(handle, info);
|
||||
if (err < 0 && comment && check_comment_access(comment, "user")) {
|
||||
err = add_user_control(handle, info, comment);
|
||||
@@ -1339,28 +1373,39 @@ static int set_control(snd_ctl_t *handle, snd_config_t *control,
|
||||
cerror(doit, "failed to obtain info for control #%d (%s)", numid, snd_strerror(err));
|
||||
return -ENOENT;
|
||||
}
|
||||
numid1 = snd_ctl_elem_info_get_numid(info);
|
||||
iface1 = snd_ctl_elem_info_get_interface(info);
|
||||
device1 = snd_ctl_elem_info_get_device(info);
|
||||
subdevice1 = snd_ctl_elem_info_get_subdevice(info);
|
||||
name1 = snd_ctl_elem_info_get_name(info);
|
||||
index1 = snd_ctl_elem_info_get_index(info);
|
||||
count = snd_ctl_elem_info_get_count(info);
|
||||
snd_ctl_elem_info_get_id(info, elem_id1);
|
||||
type = snd_ctl_elem_info_get_type(info);
|
||||
if (err |= numid != numid1 && !force_restore)
|
||||
cerror(doit, "warning: numid mismatch (%d/%d) for control #%d",
|
||||
numid, numid1, numid);
|
||||
if (err |= (int)iface != iface1)
|
||||
cerror(doit, "warning: iface mismatch (%d/%d) for control #%d", iface, iface1, numid);
|
||||
if (err |= device != device1)
|
||||
cerror(doit, "warning: device mismatch (%ld/%ld) for control #%d", device, device1, numid);
|
||||
if (err |= subdevice != subdevice1)
|
||||
cerror(doit, "warning: subdevice mismatch (%ld/%ld) for control #%d", subdevice, subdevice1, numid);
|
||||
if (err |= strcmp(name, name1))
|
||||
cerror(doit, "warning: name mismatch (%s/%s) for control #%d", name, name1, numid);
|
||||
if (err |= index != index1)
|
||||
cerror(doit, "warning: index mismatch (%ld/%ld) for control #%d", index, index1, numid);
|
||||
if (err < 0) {
|
||||
err = 0;
|
||||
if (err |= !force_restore && numid != snd_ctl_elem_id_get_numid(elem_id1))
|
||||
cerror(doit, "warning: numid mismatch (%u/%u) for control #%d",
|
||||
(unsigned int)numid, snd_ctl_elem_id_get_numid(elem_id1), numid);
|
||||
if (err |= snd_ctl_elem_id_get_interface(elem_id) != snd_ctl_elem_id_get_interface(elem_id1))
|
||||
cerror(doit, "warning: iface mismatch (%d/%d) for control #%d",
|
||||
(int)snd_ctl_elem_id_get_interface(elem_id),
|
||||
(int)snd_ctl_elem_id_get_interface(elem_id1),
|
||||
numid);
|
||||
if (err |= snd_ctl_elem_id_get_device(elem_id) != snd_ctl_elem_id_get_device(elem_id1))
|
||||
cerror(doit, "warning: device mismatch (%u/%u) for control #%d",
|
||||
snd_ctl_elem_id_get_device(elem_id),
|
||||
snd_ctl_elem_id_get_device(elem_id1),
|
||||
numid);
|
||||
if (err |= snd_ctl_elem_id_get_subdevice(elem_id) != snd_ctl_elem_id_get_subdevice(elem_id1))
|
||||
cerror(doit, "warning: subdevice mismatch (%u/%%u) for control #%d",
|
||||
snd_ctl_elem_id_get_subdevice(elem_id),
|
||||
snd_ctl_elem_id_get_subdevice(elem_id1),
|
||||
numid);
|
||||
if (err |= strcmp(snd_ctl_elem_id_get_name(elem_id), snd_ctl_elem_id_get_name(elem_id1)))
|
||||
cerror(doit, "warning: name mismatch (%s/%s) for control #%d",
|
||||
snd_ctl_elem_id_get_name(elem_id),
|
||||
snd_ctl_elem_id_get_name(elem_id1),
|
||||
numid);
|
||||
if (err |= snd_ctl_elem_id_get_index(elem_id) != snd_ctl_elem_id_get_index(elem_id1))
|
||||
cerror(doit, "warning: index mismatch (%u/%u) for control #%d",
|
||||
snd_ctl_elem_id_get_index(elem_id),
|
||||
snd_ctl_elem_id_get_index(elem_id1),
|
||||
numid);
|
||||
if (err) {
|
||||
cerror(doit, "failed to obtain info for control #%d (%s)", numid, snd_strerror(err));
|
||||
return -ENOENT;
|
||||
}
|
||||
@@ -1383,7 +1428,7 @@ static int set_control(snd_ctl_t *handle, snd_config_t *control,
|
||||
if (snd_ctl_elem_info_is_inactive(info) ||
|
||||
!snd_ctl_elem_info_is_writable(info))
|
||||
return 0;
|
||||
snd_ctl_elem_value_set_numid(ctl, numid1);
|
||||
snd_ctl_elem_value_set_numid(ctl, snd_ctl_elem_id_get_numid(elem_id1));
|
||||
|
||||
if (count == 1) {
|
||||
err = restore_config_value(handle, info, type, value, ctl, 0, doit);
|
||||
@@ -1472,8 +1517,14 @@ static int set_control(snd_ctl_t *handle, snd_config_t *control,
|
||||
_ok:
|
||||
err = doit ? snd_ctl_elem_write(handle, ctl) : 0;
|
||||
if (err < 0) {
|
||||
error("Cannot write control '%d:%ld:%ld:%s:%ld' : %s", (int)iface, device, subdevice, name, index, snd_strerror(err));
|
||||
char *s = snd_ctl_ascii_elem_id_get(elem_id1);
|
||||
error("Cannot write control '%s' : %s", s, snd_strerror(err));
|
||||
free(s);
|
||||
return err;
|
||||
} else if (debugflag) {
|
||||
char *s = snd_ctl_ascii_elem_id_get(elem_id1);
|
||||
dbg("control '%s' restored", s);
|
||||
free(s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1487,7 +1538,7 @@ static int set_controls(int card, snd_config_t *top, int doit)
|
||||
snd_ctl_elem_id_t *elem_id;
|
||||
snd_config_t *control;
|
||||
snd_config_iterator_t i, next;
|
||||
int err, maxnumid = -1, maxnumid2 = -1;
|
||||
int err, controls1 = -1, controls2 = -1, ucontrols = -1, diff;
|
||||
unsigned int idx, count = 0;
|
||||
char name[32], tmpid[16];
|
||||
const char *id;
|
||||
@@ -1527,11 +1578,13 @@ static int set_controls(int card, snd_config_t *top, int doit)
|
||||
cerror(doit, "state.%s.control is not a compound\n", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
controls1 = 0;
|
||||
snd_config_for_each(i, next, control) {
|
||||
snd_config_t *n = snd_config_iterator_entry(i);
|
||||
err = set_control(handle, n, &maxnumid, doit);
|
||||
err = set_control(handle, n, doit);
|
||||
if (err < 0 && (!force_restore || !doit))
|
||||
goto _close;
|
||||
controls1++;
|
||||
}
|
||||
|
||||
if (doit)
|
||||
@@ -1545,7 +1598,7 @@ static int set_controls(int card, snd_config_t *top, int doit)
|
||||
count = snd_ctl_elem_list_get_count(list);
|
||||
dbg("list count: %u", count);
|
||||
if (count == 0)
|
||||
goto _check;
|
||||
goto _free;
|
||||
snd_ctl_elem_list_set_offset(list, 0);
|
||||
if (snd_ctl_elem_list_alloc_space(list, count) < 0) {
|
||||
error("No enough memory...");
|
||||
@@ -1555,28 +1608,30 @@ static int set_controls(int card, snd_config_t *top, int doit)
|
||||
error("Cannot determine controls (2): %s", snd_strerror(err));
|
||||
goto _free;
|
||||
}
|
||||
maxnumid2 = 0;
|
||||
/* skip non-readable elements */
|
||||
controls2 = ucontrols = 0;
|
||||
/* skip non-readable and count user elements */
|
||||
for (idx = 0; idx < count; ++idx) {
|
||||
snd_ctl_elem_info_clear(elem_info);
|
||||
snd_ctl_elem_list_get_id(list, idx, elem_id);
|
||||
snd_ctl_elem_info_set_id(elem_info, elem_id);
|
||||
if (snd_ctl_elem_info(handle, elem_info) == 0) {
|
||||
if (snd_ctl_elem_info_is_user(elem_info))
|
||||
ucontrols++;
|
||||
if (!snd_ctl_elem_info_is_readable(elem_info))
|
||||
continue;
|
||||
maxnumid2++;
|
||||
controls2++;
|
||||
}
|
||||
}
|
||||
|
||||
/* check if we have additional controls in driver */
|
||||
/* in this case we should go through init procedure */
|
||||
_check:
|
||||
dbg("maxnumid=%i maxnumid2=%i", maxnumid, maxnumid2);
|
||||
if (maxnumid >= 0 && maxnumid != maxnumid2) {
|
||||
diff = controls2 - controls1;
|
||||
dbg("controls1=%i controls2=%i ucontrols=%i diff=%i", controls1, controls2, ucontrols, diff);
|
||||
if (controls1 >= 0 && (-diff > ucontrols || diff > ucontrols)) {
|
||||
/* not very informative */
|
||||
/* but value is used for check only */
|
||||
err = -EAGAIN;
|
||||
dbg("more controls than maxnumid?");
|
||||
dbg("config controls mismatch with hardware?");
|
||||
}
|
||||
|
||||
_free:
|
||||
@@ -1675,10 +1730,11 @@ int load_state(const char *cfgdir, const char *file,
|
||||
const char *initfile, int initflags,
|
||||
const char *cardname, int do_init)
|
||||
{
|
||||
int err, finalerr = 0, open_failed, lock_fd;
|
||||
int err, finalerr = 0, open_failed, lock_fd, cardno;
|
||||
struct snd_card_iterator iter;
|
||||
snd_config_t *config;
|
||||
const char *cardname1;
|
||||
size_t index;
|
||||
|
||||
config = NULL;
|
||||
err = load_configuration(file, &config, &open_failed);
|
||||
@@ -1695,6 +1751,9 @@ int load_state(const char *cfgdir, const char *file,
|
||||
while ((cardname1 = snd_card_iterator_next(&iter)) != NULL) {
|
||||
if (!do_init)
|
||||
break;
|
||||
init_linked_cards();
|
||||
if (initflags & FLAG_UCM_WAIT)
|
||||
wait_for_card(-1, iter.card);
|
||||
lock_fd = card_lock(iter.card, LOCK_TIMEOUT);
|
||||
if (lock_fd < 0) {
|
||||
finalerr = lock_fd;
|
||||
@@ -1703,9 +1762,12 @@ int load_state(const char *cfgdir, const char *file,
|
||||
}
|
||||
err = init(cfgdir, initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname1);
|
||||
card_unlock(lock_fd, iter.card);
|
||||
if (card_state_is_okay(err))
|
||||
export_card_state_set(iter.card, err);
|
||||
if (err < 0) {
|
||||
finalerr = err;
|
||||
initfailed(iter.card, "init", err);
|
||||
continue;
|
||||
}
|
||||
initfailed(iter.card, "restore", -ENOENT);
|
||||
}
|
||||
@@ -1719,6 +1781,9 @@ int load_state(const char *cfgdir, const char *file,
|
||||
if (err < 0)
|
||||
goto out;
|
||||
while ((cardname1 = snd_card_iterator_next(&iter)) != NULL) {
|
||||
init_linked_cards();
|
||||
if (initflags & FLAG_UCM_WAIT)
|
||||
wait_for_card(-1, iter.card);
|
||||
lock_fd = card_lock(iter.card, LOCK_TIMEOUT);
|
||||
if (lock_fd < 0) {
|
||||
initfailed(iter.card, "lock", lock_fd);
|
||||
@@ -1726,7 +1791,20 @@ int load_state(const char *cfgdir, const char *file,
|
||||
continue;
|
||||
}
|
||||
/* error is ignored */
|
||||
init_ucm(initflags | FLAG_UCM_FBOOT, iter.card);
|
||||
err = init_ucm(cfgdir, initflags | FLAG_UCM_FBOOT, iter.card);
|
||||
/* return code 1 and 2 -> postpone initialization */
|
||||
if (card_state_is_okay(err)) {
|
||||
export_card_state_set(iter.card, err);
|
||||
goto unlock_card;
|
||||
} else if (err < 0) {
|
||||
/* no UCM - remove card specific configuration */
|
||||
err = snd_card_clean_cfgdir(cfgdir, iter.card);
|
||||
if (err < 0) {
|
||||
initfailed(iter.card, "cfgdir", err);
|
||||
finalerr = err;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* do a check if controls matches state file */
|
||||
if (do_init && set_controls(iter.card, config, 0)) {
|
||||
err = init(cfgdir, initfile, initflags | FLAG_UCM_BOOT, cardname1);
|
||||
@@ -1740,6 +1818,18 @@ int load_state(const char *cfgdir, const char *file,
|
||||
finalerr = err;
|
||||
initfailed(iter.card, "restore", err);
|
||||
}
|
||||
/* for linked cards, restore controls, too */
|
||||
for (index = 0; index < ARRAY_SIZE(linked_cards); index++) {
|
||||
if ((cardno = linked_cards[index]) < 0)
|
||||
break;
|
||||
dbg("Restore for linked card %d", cardno);
|
||||
if ((err = set_controls(cardno, config, 1))) {
|
||||
if (!force_restore)
|
||||
finalerr = err;
|
||||
initfailed(cardno, "restore", err);
|
||||
}
|
||||
}
|
||||
unlock_card:
|
||||
card_unlock(lock_fd, iter.card);
|
||||
}
|
||||
err = finalerr ? finalerr : snd_card_iterator_error(&iter);
|
||||
|
||||
+28
-3
@@ -177,7 +177,7 @@ void dbg_(const char *fcn, long line, const char *fmt, ...)
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
|
||||
void error_handler(const char *file, int line, const char *function, int errcode, const char *fmt, ...)
|
||||
{
|
||||
char buf[2048];
|
||||
va_list arg;
|
||||
@@ -187,12 +187,36 @@ void error_handler(const char *file, int line, const char *function, int err, co
|
||||
va_end(arg);
|
||||
if (use_syslog)
|
||||
syslog(LOG_ERR, "alsa-lib %s:%i:(%s) %s%s%s\n", file, line, function,
|
||||
buf, err ? ": " : "", err ? snd_strerror(err) : "");
|
||||
buf, errcode ? ": " : "", errcode ? snd_strerror(errcode) : "");
|
||||
else
|
||||
fprintf(stderr, "alsa-lib %s:%i:(%s) %s%s%s\n", file, line, function,
|
||||
buf, err ? ": " : "", err ? snd_strerror(err) : "");
|
||||
buf, errcode ? ": " : "", errcode ? snd_strerror(errcode) : "");
|
||||
}
|
||||
|
||||
#if SND_LIB_VER(1, 2, 15) >= SND_LIB_VERSION
|
||||
void log_handler(int prio, int interface, const char *file, int line, const char *function, int errcode, const char *fmt, va_list arg)
|
||||
{
|
||||
char buf[2048], level[50] = "";
|
||||
const char *text1, *text2;
|
||||
|
||||
if (!snd_lib_log_filter(prio, interface, NULL))
|
||||
return;
|
||||
|
||||
text1 = snd_lib_log_priority(prio);
|
||||
text2 = snd_lib_log_interface(interface);
|
||||
if (text1 || text2)
|
||||
snprintf(level, sizeof(level), "[%s.%s] ", text1 ? text1 : "", text2 ? text2 : "");
|
||||
|
||||
vsnprintf(buf, sizeof(buf), fmt, arg);
|
||||
if (use_syslog)
|
||||
syslog(LOG_ERR, "alsa-lib %s:%i:(%s) %s%s%s%s\n", file, line, function, level,
|
||||
buf, errcode ? ": " : "", errcode ? snd_strerror(errcode) : "");
|
||||
else
|
||||
fprintf(stderr, "alsa-lib %s:%i:(%s) %s%s%s%s\n", file, line, function, level,
|
||||
buf, errcode ? ": " : "", errcode ? snd_strerror(errcode) : "");
|
||||
}
|
||||
#endif
|
||||
|
||||
int load_configuration(const char *file, snd_config_t **top, int *open_failed)
|
||||
{
|
||||
snd_config_t *config;
|
||||
@@ -329,6 +353,7 @@ int snd_card_clean_cfgdir(const char *cfgdir, int cardno)
|
||||
lasterr = -errno;
|
||||
}
|
||||
}
|
||||
free(list);
|
||||
|
||||
return lasterr;
|
||||
}
|
||||
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Advanced Linux Sound Architecture Control Program - Wait for Boot
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include "aconfig.h"
|
||||
#include "version.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "alsactl.h"
|
||||
|
||||
/**
|
||||
* \brief Wait for card boot synchronization using Boot control element
|
||||
* \param timeout Maximum wait time in seconds
|
||||
* \param cardno Card number
|
||||
* \return 0 on success, negative error code on failure
|
||||
*
|
||||
* This function waits until the card releases the 'waiting' state (UCM).
|
||||
* It monitors the '.Boot' control element and uses snd_ctl_wait() and
|
||||
* snd_ctl_read() for event-based waiting, similar to boot_wait() in
|
||||
* ../alsa-lib/alsa-lib/src/ucm/main.c
|
||||
*/
|
||||
int wait_for_card(long long timeout, int cardno)
|
||||
{
|
||||
snd_ctl_t *handle;
|
||||
snd_ctl_event_t *event;
|
||||
snd_ctl_elem_id_t *id;
|
||||
snd_ctl_elem_info_t *info;
|
||||
snd_ctl_elem_value_t *value;
|
||||
long long boot_time_val, restore_time_val;
|
||||
long long synctime = -1;
|
||||
struct timespec start_time, now;
|
||||
char name[32];
|
||||
int err;
|
||||
|
||||
sprintf(name, "hw:%d", cardno);
|
||||
err = snd_ctl_open(&handle, name, SND_CTL_READONLY);
|
||||
if (err < 0) {
|
||||
error("snd_ctl_open error for %s: %s", name, snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Try to get synctime from boot_synctime in group configuration */
|
||||
if (timeout <= 0) {
|
||||
bool valid = false, restored = false;
|
||||
err = check_boot_params_validity(handle, cardno, NULL, &valid, NULL, &restored, NULL, &synctime);
|
||||
if (err == 0 && synctime > 0) {
|
||||
timeout = synctime;
|
||||
dbg("Using boot_synctime value: %lld seconds", timeout);
|
||||
} else {
|
||||
timeout = DEFAULT_SYNC_TIME;
|
||||
}
|
||||
/* Break early if boot params are invalid or already restored */
|
||||
if (!valid || restored) {
|
||||
dbg("Boot params check: valid=%d, restored=%d - skipping wait", valid, restored);
|
||||
snd_ctl_close(handle);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
snd_ctl_event_alloca(&event);
|
||||
snd_ctl_elem_id_alloca(&id);
|
||||
snd_ctl_elem_info_alloca(&info);
|
||||
snd_ctl_elem_value_alloca(&value);
|
||||
|
||||
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
|
||||
snd_ctl_elem_id_set_name(id, ".Boot");
|
||||
|
||||
snd_ctl_elem_info_set_id(info, id);
|
||||
err = snd_ctl_elem_info(handle, info);
|
||||
if (err < 0) {
|
||||
dbg("Boot control element not present on card %d, skipping wait", cardno);
|
||||
snd_ctl_close(handle);
|
||||
return 0; /* No Boot element, no wait needed */
|
||||
}
|
||||
|
||||
if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_INTEGER64) {
|
||||
error("Boot control element is not INTEGER64 type on card %d", cardno);
|
||||
snd_ctl_close(handle);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (snd_ctl_elem_info_get_count(info) < 3) {
|
||||
error("Boot control element does not have count >= 3 on card %d", cardno);
|
||||
snd_ctl_close(handle);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = snd_ctl_subscribe_events(handle, 1);
|
||||
if (err < 0) {
|
||||
error("Cannot subscribe to control events: %s", snd_strerror(err));
|
||||
snd_ctl_close(handle);
|
||||
return err;
|
||||
}
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &start_time);
|
||||
|
||||
dbg("Waiting for card %d to become ready (timeout=%lld seconds)", cardno, timeout);
|
||||
|
||||
while (1) {
|
||||
long long diff, remaining = 0;
|
||||
long long sync_time_val = -1;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
|
||||
|
||||
/* Read current Boot control values */
|
||||
err = read_boot_params(handle, &boot_time_val, &sync_time_val, &restore_time_val, NULL);
|
||||
if (err < 0) {
|
||||
error("Failed to read Boot control element: %s", snd_strerror(err));
|
||||
goto _fin;
|
||||
}
|
||||
|
||||
dbg("Boot info: boot_time=%lld, sync_time=%lld, restore_time=%lld", boot_time_val, sync_time_val, restore_time_val);
|
||||
|
||||
if (restore_time_val > 0) {
|
||||
diff = now.tv_sec - restore_time_val;
|
||||
dbg("Controls already restored (diff=%lld seconds), card is ready", diff);
|
||||
err = 0;
|
||||
goto _fin;
|
||||
}
|
||||
|
||||
/* note that realtime may differ from monotonic time, add one second for safety */
|
||||
diff = now.tv_sec - start_time.tv_sec;
|
||||
if (diff > timeout + 1) {
|
||||
dbg("Maximum wait time exceeded (%lld >= %lld seconds), proceeding", diff, timeout);
|
||||
break;
|
||||
}
|
||||
|
||||
remaining = timeout - diff;
|
||||
|
||||
/* Use synctime from element to limit timeout if available */
|
||||
if (sync_time_val > 0 && sync_time_val < timeout) {
|
||||
dbg("Limiting timeout from %lld to sync_time %lld seconds", timeout, sync_time_val);
|
||||
timeout = sync_time_val;
|
||||
}
|
||||
|
||||
if (!validate_boot_time(boot_time_val, now.tv_sec, timeout)) {
|
||||
if (boot_time_val > 0) {
|
||||
diff = now.tv_sec - boot_time_val;
|
||||
dbg("Boot timeout reached (%lld >= %lld seconds), proceeding", diff, timeout);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
diff = now.tv_sec - boot_time_val;
|
||||
if (timeout - diff < remaining)
|
||||
remaining = timeout - diff;
|
||||
if (remaining <= 0)
|
||||
remaining = 1;
|
||||
|
||||
dbg("Waiting %lld seconds", remaining);
|
||||
err = snd_ctl_wait(handle, remaining * 1000);
|
||||
if (err < 0) {
|
||||
error("snd_ctl_wait failed: %s", snd_strerror(err));
|
||||
goto _fin;
|
||||
}
|
||||
|
||||
if (err == 0)
|
||||
continue; /* Timeout, no events */
|
||||
|
||||
/* Read and check events */
|
||||
while (snd_ctl_read(handle, event) > 0) {
|
||||
if (!(snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE))
|
||||
continue; /* Not a value change event */
|
||||
|
||||
if (snd_ctl_event_elem_get_interface(event) != SND_CTL_ELEM_IFACE_CARD ||
|
||||
snd_ctl_event_elem_get_index(event) != 0 ||
|
||||
strcmp(snd_ctl_event_elem_get_name(event), ".Boot") != 0)
|
||||
continue;
|
||||
|
||||
dbg("Boot control element value changed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
err = 0;
|
||||
_fin:
|
||||
snd_ctl_subscribe_events(handle, 0);
|
||||
snd_ctl_close(handle);
|
||||
return err;
|
||||
}
|
||||
+4
-2
@@ -647,13 +647,15 @@ static int xrun(struct loopback_handle *lhandle)
|
||||
int err;
|
||||
|
||||
if (lhandle == lhandle->loopback->play) {
|
||||
logit(LOG_DEBUG, "underrun for %s\n", lhandle->id);
|
||||
if (verbose)
|
||||
logit(LOG_DEBUG, "underrun for %s\n", lhandle->id);
|
||||
xrun_stats(lhandle->loopback);
|
||||
if ((err = snd_pcm_prepare(lhandle->handle)) < 0)
|
||||
return err;
|
||||
lhandle->xrun_pending = 1;
|
||||
} else {
|
||||
logit(LOG_DEBUG, "overrun for %s\n", lhandle->id);
|
||||
if (verbose)
|
||||
logit(LOG_DEBUG, "overrun for %s\n", lhandle->id);
|
||||
xrun_stats(lhandle->loopback);
|
||||
if ((err = snd_pcm_prepare(lhandle->handle)) < 0)
|
||||
return err;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
static WINDOW *curses_initialized;
|
||||
|
||||
#if SND_LIB_VER(1, 2, 15) < SND_LIB_VERSION
|
||||
static void black_hole_error_handler(const char *file ATTRIBUTE_UNUSED,
|
||||
int line ATTRIBUTE_UNUSED,
|
||||
const char *function ATTRIBUTE_UNUSED,
|
||||
@@ -41,6 +42,18 @@ static void black_hole_error_handler(const char *file ATTRIBUTE_UNUSED,
|
||||
const char *fmt ATTRIBUTE_UNUSED, ...)
|
||||
{
|
||||
}
|
||||
#else
|
||||
static void black_hole_log_handler(int prio ATTRIBUTE_UNUSED,
|
||||
int interface ATTRIBUTE_UNUSED,
|
||||
const char *file ATTRIBUTE_UNUSED,
|
||||
int line ATTRIBUTE_UNUSED,
|
||||
const char *function ATTRIBUTE_UNUSED,
|
||||
int errcode ATTRIBUTE_UNUSED,
|
||||
const char *fmt ATTRIBUTE_UNUSED,
|
||||
va_list arg ATTRIBUTE_UNUSED)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
void initialize_curses(bool use_color, bool use_mouse)
|
||||
{
|
||||
@@ -55,7 +68,11 @@ void initialize_curses(bool use_color, bool use_mouse)
|
||||
if (use_mouse)
|
||||
mousemask(ALL_MOUSE_EVENTS, NULL);
|
||||
|
||||
#if SND_LIB_VER(1, 2, 15) < SND_LIB_VERSION
|
||||
snd_lib_error_set_handler(black_hole_error_handler);
|
||||
#else
|
||||
snd_lib_log_set_handler(black_hole_log_handler);
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_shutdown(void)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,11 @@ Prints the current version.
|
||||
.I \-l, \-\-list\-devices
|
||||
Prints a list of all hardware MIDI ports.
|
||||
|
||||
.TP
|
||||
.I \-x, \-\-list\-inactive
|
||||
Use together with \fI\-l\fP option.
|
||||
Print all MIDI ports including inactive ports.
|
||||
|
||||
.TP
|
||||
.I \-L, \-\-list\-rawmidis
|
||||
Prints all RawMIDI definitions.
|
||||
|
||||
+10
-1
@@ -57,6 +57,7 @@ static int stop;
|
||||
static int sysex_interval;
|
||||
static snd_rawmidi_t *input, **inputp;
|
||||
static snd_rawmidi_t *output, **outputp;
|
||||
static int list_all;
|
||||
|
||||
static void error(const char *format, ...)
|
||||
{
|
||||
@@ -76,6 +77,7 @@ static void usage(void)
|
||||
"-h, --help this help\n"
|
||||
"-V, --version print current version\n"
|
||||
"-l, --list-devices list all hardware ports\n"
|
||||
"-x, --list-inactive list inactive ports, too\n"
|
||||
"-L, --list-rawmidis list all RawMIDI definitions\n"
|
||||
"-p, --port=name select port by name\n"
|
||||
"-s, --send=file send the contents of a (.syx) file\n"
|
||||
@@ -151,6 +153,9 @@ static void list_device(snd_ctl_t *ctl, int card, int device)
|
||||
card, device, sub, snd_strerror(err));
|
||||
return;
|
||||
}
|
||||
if (!list_all &&
|
||||
(snd_rawmidi_info_get_flags(info) & SNDRV_RAWMIDI_INFO_STREAM_INACTIVE))
|
||||
continue;
|
||||
name = snd_rawmidi_info_get_name(info);
|
||||
sub_name = snd_rawmidi_info_get_subdevice_name(info);
|
||||
if (sub == 0 && sub_name[0] == '\0') {
|
||||
@@ -471,11 +476,12 @@ static void add_send_hex_data(const char *str)
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
static const char short_options[] = "hVlLp:s:r:S::dt:aci:T:";
|
||||
static const char short_options[] = "hVlxLp:s:r:S::dt:aci:T:";
|
||||
static const struct option long_options[] = {
|
||||
{"help", 0, NULL, 'h'},
|
||||
{"version", 0, NULL, 'V'},
|
||||
{"list-devices", 0, NULL, 'l'},
|
||||
{"list-inactive", 0, NULL, 'x'},
|
||||
{"list-rawmidis", 0, NULL, 'L'},
|
||||
{"port", 1, NULL, 'p'},
|
||||
{"send", 1, NULL, 's'},
|
||||
@@ -508,6 +514,9 @@ int main(int argc, char *argv[])
|
||||
case 'l':
|
||||
do_device_list = 1;
|
||||
break;
|
||||
case 'x':
|
||||
list_all = 1;
|
||||
break;
|
||||
case 'L':
|
||||
do_rawmidi_list = 1;
|
||||
break;
|
||||
|
||||
+8
-1
@@ -587,10 +587,17 @@ static void decode_tlv(unsigned int spaces, unsigned int *tlv, unsigned int tlv_
|
||||
#endif
|
||||
default:
|
||||
printf("unk-%u-", type);
|
||||
while (size > 0) {
|
||||
while (size > sizeof(unsigned int)) {
|
||||
printf("0x%08x,", tlv[idx++]);
|
||||
size -= sizeof(unsigned int);
|
||||
}
|
||||
if (size > 0) {
|
||||
unsigned char *b = (void *)&tlv[idx];
|
||||
while (size > 0) {
|
||||
printf("E-0x%02x,", *b++);
|
||||
size--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (lf)
|
||||
|
||||
+16
-16
@@ -1,6 +1,6 @@
|
||||
.TH APLAY 1 "1 January 2010"
|
||||
.SH NAME
|
||||
arecord, aplay \- command\-line sound recorder and player for ALSA
|
||||
arecord, aplay \- command\-line sound recorder and player for ALSA
|
||||
soundcard driver
|
||||
.SH SYNOPSIS
|
||||
\fBarecord\fP [\fIflags\fP] [filename]
|
||||
@@ -84,41 +84,41 @@ A value of zero means infinity.
|
||||
The default is zero, so if this options is omitted then the record/playback process will run until it is killed.
|
||||
Either '-d' or '-s' option is available exclusively.
|
||||
.TP
|
||||
\fI\-M, \-\-mmap\fP
|
||||
\fI\-M, \-\-mmap\fP
|
||||
Use memory\-mapped (mmap) I/O mode for the audio stream.
|
||||
If this option is not set, the read/write I/O mode will be used.
|
||||
.TP
|
||||
\fI\-N, \-\-nonblock\fP
|
||||
\fI\-N, \-\-nonblock\fP
|
||||
Open the audio device in non\-blocking mode. If the device is busy the program will exit immediately.
|
||||
If this option is not set the program will block until the audio device is available again.
|
||||
.TP
|
||||
\fI\-F, \-\-period\-time=#\fP
|
||||
\fI\-F, \-\-period\-time=#\fP
|
||||
Distance between interrupts is # microseconds.
|
||||
If no period time and no period size is given then a quarter of the buffer time is set.
|
||||
.TP
|
||||
\fI\-B, \-\-buffer\-time=#\fP
|
||||
\fI\-B, \-\-buffer\-time=#\fP
|
||||
Buffer duration is # microseconds
|
||||
If no buffer time and no buffer size is given then the maximal allowed buffer time but not more than 500ms is set.
|
||||
.TP
|
||||
\fI\-\-period\-size=#\fP
|
||||
\fI\-\-period\-size=#\fP
|
||||
Distance between interrupts is # frames
|
||||
If no period size and no period time is given then a quarter of the buffer size is set.
|
||||
.TP
|
||||
\fI\-\-buffer\-size=#\fP
|
||||
\fI\-\-buffer\-size=#\fP
|
||||
Buffer duration is # frames
|
||||
If no buffer time and no buffer size is given then the maximal allowed buffer time but not more than 500ms is set.
|
||||
.TP
|
||||
\fI\-A, \-\-avail\-min=#\fP
|
||||
\fI\-A, \-\-avail\-min=#\fP
|
||||
Min available space for wakeup is # microseconds
|
||||
.TP
|
||||
\fI\-R, \-\-start\-delay=#\fP
|
||||
Delay for automatic PCM start is # microseconds
|
||||
\fI\-R, \-\-start\-delay=#\fP
|
||||
Delay for automatic PCM start is # microseconds
|
||||
(relative to buffer size if <= 0)
|
||||
.TP
|
||||
\fI\-T, \-\-stop\-delay=#\fP
|
||||
\fI\-T, \-\-stop\-delay=#\fP
|
||||
Delay for automatic PCM stop is # microseconds from xrun
|
||||
.TP
|
||||
\fI\-v, \-\-verbose\fP
|
||||
\fI\-v, \-\-verbose\fP
|
||||
Show PCM structure and setup.
|
||||
This option is accumulative. The VU meter is displayed when this
|
||||
is given twice or three times.
|
||||
@@ -128,7 +128,7 @@ Specifies the VU\-meter type, either \fIstereo\fP or \fImono\fP.
|
||||
The stereo VU\-meter is available only for 2\-channel stereo samples
|
||||
with interleaved format.
|
||||
.TP
|
||||
\fI\-I, \-\-separate\-channels\fP
|
||||
\fI\-I, \-\-separate\-channels\fP
|
||||
One file for each channel. This option disables max\-file\-time
|
||||
and use\-strftime, and ignores SIGUSR1. The stereo VU meter is
|
||||
not available with separate channels.
|
||||
@@ -212,7 +212,7 @@ Disables recovery attempts when errors (e.g. xrun) are encountered; the
|
||||
aplay process instead aborts immediately.
|
||||
|
||||
.SH SIGNALS
|
||||
When recording, SIGINT, SIGTERM and SIGABRT will close the output
|
||||
When recording, SIGINT, SIGTERM and SIGABRT will close the output
|
||||
file and exit. SIGUSR1 will close the output file, open a new one,
|
||||
and continue recording. However, SIGUSR1 does not work with
|
||||
\-\-separate\-channels.
|
||||
@@ -222,7 +222,7 @@ and continue recording. However, SIGUSR1 does not work with
|
||||
.TP
|
||||
\fBaplay \-c 1 \-t raw \-r 22050 \-f mu_law foobar\fR
|
||||
will play the raw file "foobar" as a
|
||||
22050\-Hz, mono, 8\-bit, Mu\-Law .au file.
|
||||
22050\-Hz, mono, 8\-bit, Mu\-Law .au file.
|
||||
|
||||
.TP
|
||||
\fBarecord \-d 10 \-f cd \-t wav \-D copy foobar.wav\fP
|
||||
@@ -257,7 +257,7 @@ alsamixer(1),
|
||||
amixer(1)
|
||||
\fP
|
||||
|
||||
.SH BUGS
|
||||
.SH BUGS
|
||||
Note that .aiff files are not currently supported.
|
||||
|
||||
.SH AUTHOR
|
||||
|
||||
+30
-21
@@ -1245,6 +1245,12 @@ static int test_au(int fd, void *buffer)
|
||||
fprintf(stderr, _("Warning: format is changed to S16_BE\n"));
|
||||
hwparams.format = SND_PCM_FORMAT_S16_BE;
|
||||
break;
|
||||
case AU_FMT_ALAW:
|
||||
if (hwparams.format != DEFAULT_FORMAT &&
|
||||
hwparams.format != SND_PCM_FORMAT_A_LAW)
|
||||
fprintf(stderr, _("Warning: format is changed to A_LAW\n"));
|
||||
hwparams.format = SND_PCM_FORMAT_A_LAW;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
@@ -1464,6 +1470,13 @@ static void set_params(void)
|
||||
chunk_size, buffer_size);
|
||||
prg_exit(EXIT_FAILURE);
|
||||
}
|
||||
if (dump_hw_params) {
|
||||
fprintf(stderr, _("HW Params of device \"%s\":\n"),
|
||||
snd_pcm_name(handle));
|
||||
fprintf(stderr, "--------------------\n");
|
||||
snd_pcm_hw_params_dump(params, log);
|
||||
fprintf(stderr, "--------------------\n");
|
||||
}
|
||||
err = snd_pcm_sw_params_current(handle, swparams);
|
||||
if (err < 0) {
|
||||
error(_("Unable to get current sw params."));
|
||||
@@ -1618,6 +1631,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 +1657,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);
|
||||
@@ -2703,7 +2716,7 @@ static void begin_wave(int fd, size_t cnt)
|
||||
WaveHeader h;
|
||||
WaveFmtBody f;
|
||||
WaveChunkHeader cf, cd;
|
||||
int bits;
|
||||
int width, physical_width;
|
||||
uint32_t tmp;
|
||||
uint16_t tmp2;
|
||||
|
||||
@@ -2711,23 +2724,22 @@ static void begin_wave(int fd, size_t cnt)
|
||||
if (cnt == (size_t)-2)
|
||||
cnt = 0x7fffff00;
|
||||
|
||||
bits = 8;
|
||||
width = snd_pcm_format_physical_width(hwparams.format);
|
||||
physical_width = snd_pcm_format_width(hwparams.format);
|
||||
|
||||
if (width < 0 || physical_width < 0)
|
||||
goto _format;
|
||||
|
||||
switch ((unsigned long) hwparams.format) {
|
||||
case SND_PCM_FORMAT_U8:
|
||||
bits = 8;
|
||||
break;
|
||||
case SND_PCM_FORMAT_S16_LE:
|
||||
bits = 16;
|
||||
break;
|
||||
case SND_PCM_FORMAT_S24_LE: /* S24_LE is 24 bits stored in 32 bit width with 8 bit padding */
|
||||
case SND_PCM_FORMAT_S32_LE:
|
||||
case SND_PCM_FORMAT_FLOAT_LE:
|
||||
bits = 32;
|
||||
break;
|
||||
case SND_PCM_FORMAT_S24_LE:
|
||||
case SND_PCM_FORMAT_FLOAT_LE:
|
||||
case SND_PCM_FORMAT_S24_3LE:
|
||||
bits = 24;
|
||||
break;
|
||||
default:
|
||||
_format:
|
||||
error(_("Wave doesn't support %s format..."), snd_pcm_format_name(hwparams.format));
|
||||
prg_exit(EXIT_FAILURE);
|
||||
}
|
||||
@@ -2745,17 +2757,11 @@ static void begin_wave(int fd, size_t cnt)
|
||||
f.format = LE_SHORT(WAV_FMT_PCM);
|
||||
f.channels = LE_SHORT(hwparams.channels);
|
||||
f.sample_fq = LE_INT(hwparams.rate);
|
||||
#if 0
|
||||
tmp2 = (samplesize == 8) ? 1 : 2;
|
||||
f.byte_p_spl = LE_SHORT(tmp2);
|
||||
tmp = dsp_speed * hwparams.channels * (uint32_t) tmp2;
|
||||
#else
|
||||
tmp2 = hwparams.channels * snd_pcm_format_physical_width(hwparams.format) / 8;
|
||||
tmp2 = hwparams.channels * physical_width / 8;
|
||||
f.byte_p_spl = LE_SHORT(tmp2);
|
||||
tmp = (uint32_t) tmp2 * hwparams.rate;
|
||||
#endif
|
||||
f.byte_p_sec = LE_INT(tmp);
|
||||
f.bit_p_spl = LE_SHORT(bits);
|
||||
f.bit_p_spl = LE_SHORT(width);
|
||||
|
||||
cd.type = WAV_DATA;
|
||||
cd.length = LE_INT(cnt);
|
||||
@@ -2787,6 +2793,9 @@ static void begin_au(int fd, size_t cnt)
|
||||
case SND_PCM_FORMAT_S16_BE:
|
||||
ah.encoding = BE_INT(AU_FMT_LIN16);
|
||||
break;
|
||||
case SND_PCM_FORMAT_A_LAW:
|
||||
ah.encoding = BE_INT(AU_FMT_ALAW);
|
||||
break;
|
||||
default:
|
||||
error(_("Sparc Audio doesn't support %s format..."), snd_pcm_format_name(hwparams.format));
|
||||
prg_exit(EXIT_FAILURE);
|
||||
|
||||
@@ -120,6 +120,7 @@ typedef struct {
|
||||
#define AU_FMT_ULAW 1
|
||||
#define AU_FMT_LIN8 2
|
||||
#define AU_FMT_LIN16 3
|
||||
#define AU_FMT_ALAW 27
|
||||
|
||||
typedef struct au_header {
|
||||
uint32_t magic; /* '.snd' */
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
#ifndef __ALSA_UTILS_AXFER_WAITER__H_
|
||||
#define __ALSA_UTILS_AXFER_WAITER__H_
|
||||
|
||||
#include <alsa/global.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <poll.h>
|
||||
|
||||
enum waiter_type {
|
||||
|
||||
+11
-11
@@ -221,7 +221,17 @@ static int set_snd_pcm_params(struct bat *bat, struct pcm_container *sndpcm)
|
||||
|
||||
period_time = buffer_time / DIV_BUFFERTIME;
|
||||
|
||||
/* Set buffer time and period time */
|
||||
/* Set period time and buffer time */
|
||||
err = snd_pcm_hw_params_set_period_time_near(sndpcm->handle,
|
||||
params, &period_time, 0);
|
||||
if (err < 0) {
|
||||
fprintf(bat->err, _("Set parameter to device error: "));
|
||||
fprintf(bat->err, _("period time: %d %s: %s(%d)\n"),
|
||||
period_time,
|
||||
device_name, snd_strerror(err), err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_buffer_time_near(sndpcm->handle,
|
||||
params, &buffer_time, 0);
|
||||
if (err < 0) {
|
||||
@@ -231,16 +241,6 @@ static int set_snd_pcm_params(struct bat *bat, struct pcm_container *sndpcm)
|
||||
device_name, snd_strerror(err), err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_period_time_near(sndpcm->handle,
|
||||
params, &period_time, 0);
|
||||
if (err < 0) {
|
||||
fprintf(bat->err, _("Set parameter to device error: "));
|
||||
fprintf(bat->err, _("period time: %d %s: %s(%d)\n"),
|
||||
period_time,
|
||||
device_name, snd_strerror(err), err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the parameters to the driver */
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
"third_party": [ "alsa-lib" ]
|
||||
},
|
||||
"build": {
|
||||
"sub_component": [ "//third_party/alsa-utils:alsa-utils" ],
|
||||
"sub_component": [ "//third_party/alsa-utils:entry" ],
|
||||
"inner_kits": [],
|
||||
"test": []
|
||||
}
|
||||
|
||||
+22
-18
@@ -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.15.2)
|
||||
AC_CONFIG_SRCDIR([aplay/aplay.c])
|
||||
AC_PREFIX_DEFAULT(/usr)
|
||||
AM_INIT_AUTOMAKE([subdir-objects])
|
||||
@@ -8,7 +8,7 @@ AM_INIT_AUTOMAKE([subdir-objects])
|
||||
AM_MAINTAINER_MODE([enable])
|
||||
|
||||
AM_GNU_GETTEXT([external])
|
||||
AM_GNU_GETTEXT_VERSION([0.19.8])
|
||||
AM_GNU_GETTEXT_VERSION([0.22.5])
|
||||
|
||||
dnl Checks for programs.
|
||||
|
||||
@@ -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"])
|
||||
|
||||
#
|
||||
@@ -430,8 +419,8 @@ AC_ARG_WITH([systemdsystemunitdir],
|
||||
if test "x$with_systemdsystemunitdir" != xno; then
|
||||
AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])
|
||||
fi
|
||||
AM_CONDITIONAL(HAVE_SYSTEMD, [test "$have_min_systemd" = "yes" \
|
||||
-a -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ])
|
||||
AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" \
|
||||
-a "x$with_systemdsystemunitdir" != xno ])
|
||||
|
||||
AC_ARG_WITH([asound-state-dir],
|
||||
AS_HELP_STRING([--with-asound-state-dir=DIR], [Directory to place asound.state file in]),
|
||||
@@ -457,6 +446,21 @@ AC_ARG_WITH([alsactl-daemonswitch],
|
||||
[ALSACTL_DAEMONSWITCH="/etc/alsa/state-daemon.conf"])
|
||||
AC_SUBST(ALSACTL_DAEMONSWITCH)
|
||||
|
||||
AC_ARG_WITH([alsactl-udev-extra-test],
|
||||
AS_HELP_STRING([--with-alsactl-udev-extra-test=TEST], [Extra udev tests]),
|
||||
[ALSACTL_UDEV_EXTRATEST="$withval"],
|
||||
[ALSACTL_UDEV_EXTRATEST="default"])
|
||||
if test "$ALSACTL_UDEV_EXTRATEST" = "default"; then
|
||||
ALSACTL_UDEV_EXTRATEST="TEST==\"${sbindir}\", TEST==\"${mydatadir}\","
|
||||
fi
|
||||
AC_SUBST(ALSACTL_UDEV_EXTRATEST)
|
||||
|
||||
AC_ARG_WITH([alsactl-udev-args],
|
||||
AS_HELP_STRING([--with-alsactl-udev-args=ARGS], [Extra alsactl arguments (udev rules)]),
|
||||
[ALSACTL_UDEV_ARGS="$withval"],
|
||||
[ALSACTL_UDEV_ARGS=""])
|
||||
AC_SUBST(ALSACTL_UDEV_ARGS)
|
||||
|
||||
dnl pre-process plugin directory
|
||||
AC_ARG_WITH(plugindir,
|
||||
AS_HELP_STRING([--with-plugindir=dir],
|
||||
@@ -486,8 +490,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)
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+20
-28
@@ -2,10 +2,10 @@
|
||||
/* include/aconfig.h.in. Generated from configure.ac by autoheader. */
|
||||
|
||||
/* directory containing ALSA topology pre-process plugins */
|
||||
#define ALSA_TOPOLOGY_PLUGIN_DIR "/home/xuxuehai/code/alsa-utils-1.2.11/build/lib/alsa-topology"
|
||||
#define ALSA_TOPOLOGY_PLUGIN_DIR "/usr/lib/alsa-topology"
|
||||
|
||||
/* directory containing alsa configuration */
|
||||
#define DATADIR "/home/xuxuehai/code/alsa-utils-1.2.11/build/share/alsa"
|
||||
#define DATADIR "/usr/share/alsa"
|
||||
|
||||
/* Define to 1 if translation of program messages to the user's native
|
||||
language is requested. */
|
||||
@@ -32,9 +32,9 @@
|
||||
/* Define to 1 if you have the <alsa/use-case.h> header file. */
|
||||
#define HAVE_ALSA_USE_CASE_H 1
|
||||
|
||||
/* Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in the
|
||||
CoreFoundation framework. */
|
||||
/* #undef HAVE_CFLOCALECOPYCURRENT */
|
||||
/* Define to 1 if you have the Mac OS X function
|
||||
CFLocaleCopyPreferredLanguages in the CoreFoundation framework. */
|
||||
/* #undef HAVE_CFLOCALECOPYPREFERREDLANGUAGES */
|
||||
|
||||
/* Define to 1 if you have the Mac OS X function CFPreferencesCopyAppValue in
|
||||
the CoreFoundation framework. */
|
||||
@@ -92,9 +92,6 @@
|
||||
/* Define if Linux kernel supports memfd_create system call */
|
||||
#define HAVE_MEMFD_CREATE 1
|
||||
|
||||
/* Define to 1 if you have the <memory.h> header file. */
|
||||
#define HAVE_MEMORY_H 1
|
||||
|
||||
/* Define to 1 if you have the <menu.h> header file. */
|
||||
#define HAVE_MENU_H 1
|
||||
|
||||
@@ -104,18 +101,12 @@
|
||||
/* Define to 1 if you have the <samplerate.h> header file. */
|
||||
/* #undef HAVE_SAMPLERATE_H */
|
||||
|
||||
/* alsa-lib supports snd_seq_client_info_get_card */
|
||||
#define HAVE_SEQ_CLIENT_INFO_GET_CARD 1
|
||||
|
||||
/* alsa-lib supports snd_seq_client_info_get_midi_version */
|
||||
/* #undef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION */
|
||||
|
||||
/* alsa-lib supports snd_seq_client_info_get_pid */
|
||||
#define HAVE_SEQ_CLIENT_INFO_GET_PID 1
|
||||
|
||||
/* Define to 1 if you have the <stdint.h> header file. */
|
||||
#define HAVE_STDINT_H 1
|
||||
|
||||
/* Define to 1 if you have the <stdio.h> header file. */
|
||||
#define HAVE_STDIO_H 1
|
||||
|
||||
/* Define to 1 if you have the <stdlib.h> header file. */
|
||||
#define HAVE_STDLIB_H 1
|
||||
|
||||
@@ -128,6 +119,9 @@
|
||||
/* Define to 1 if you have the <sys/stat.h> header file. */
|
||||
#define HAVE_SYS_STAT_H 1
|
||||
|
||||
/* Define to 1 if you have the <sys/time.h> header file. */
|
||||
#define HAVE_SYS_TIME_H 1
|
||||
|
||||
/* Define to 1 if you have the <sys/types.h> header file. */
|
||||
#define HAVE_SYS_TYPES_H 1
|
||||
|
||||
@@ -147,7 +141,7 @@
|
||||
#define PACKAGE_NAME "alsa-utils"
|
||||
|
||||
/* Define to the full name and version of this package. */
|
||||
#define PACKAGE_STRING "alsa-utils 1.2.11"
|
||||
#define PACKAGE_STRING "alsa-utils 1.2.15.2"
|
||||
|
||||
/* Define to the one symbol short name of this package. */
|
||||
#define PACKAGE_TARNAME "alsa-utils"
|
||||
@@ -156,28 +150,26 @@
|
||||
#define PACKAGE_URL ""
|
||||
|
||||
/* Define to the version of this package. */
|
||||
#define PACKAGE_VERSION "1.2.11"
|
||||
#define PACKAGE_VERSION "1.2.15.2"
|
||||
|
||||
/* directory containing sample data */
|
||||
#define SOUNDSDIR "/home/xuxuehai/code/alsa-utils-1.2.11/build/share/sounds/alsa"
|
||||
#define SOUNDSDIR "/usr/share/sounds/alsa"
|
||||
|
||||
/* Define to 1 if you have the ANSI C header files. */
|
||||
/* Define to 1 if all of the C90 standard headers exist (not just the ones
|
||||
required in a freestanding environment). This macro is provided for
|
||||
backward compatibility; new code need not use it. */
|
||||
#define STDC_HEADERS 1
|
||||
|
||||
/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
|
||||
/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. This
|
||||
macro is obsolete. */
|
||||
#define TIME_WITH_SYS_TIME 1
|
||||
|
||||
/* ALSA util version */
|
||||
#define VERSION "1.2.11"
|
||||
#define VERSION "1.2.15.2"
|
||||
|
||||
/* Define if FFADO library is available */
|
||||
/* #undef WITH_FFADO */
|
||||
|
||||
/* Enable large inode numbers on Mac OS X 10.5. */
|
||||
#ifndef _DARWIN_USE_64_BIT_INODE
|
||||
# define _DARWIN_USE_64_BIT_INODE 1
|
||||
#endif
|
||||
|
||||
/* Number of bits in a file offset, on hosts where this is settable. */
|
||||
/* #undef _FILE_OFFSET_BITS */
|
||||
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@
|
||||
|
||||
#define SND_UTIL_MAJOR 1
|
||||
#define SND_UTIL_MINOR 2
|
||||
#define SND_UTIL_SUBMINOR 11
|
||||
#define SND_UTIL_SUBMINOR 15
|
||||
#define SND_UTIL_VERSION ((SND_UTIL_MAJOR<<16)|\
|
||||
(SND_UTIL_MINOR<<8)|\
|
||||
SND_UTIL_SUBMINOR)
|
||||
#define SND_UTIL_VERSION_STR "1.2.11"
|
||||
#define SND_UTIL_VERSION_STR "1.2.15.2"
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
SUBDIRS=aconnect aplaymidi aseqdump aseqnet
|
||||
SUBDIRS=aconnect aplaymidi aplaymidi2 aseqdump aseqnet aseqsend
|
||||
|
||||
+22
-36
@@ -29,18 +29,15 @@
|
||||
#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, ...)
|
||||
|
||||
#if SND_LIB_VER(1, 2, 15) < SND_LIB_VERSION
|
||||
static void error_handler(const char *file, int line, const char *function, int errcode, const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
|
||||
if (err == ENOENT) /* Ignore those misleading "warnings" */
|
||||
if (errcode == ENOENT) /* Ignore those misleading "warnings" */
|
||||
return;
|
||||
va_start(arg, fmt);
|
||||
fprintf(stderr, "ALSA lib %s:%i:(%s) ", file, line, function);
|
||||
@@ -50,6 +47,16 @@ static void error_handler(const char *file, int line, const char *function, int
|
||||
putc('\n', stderr);
|
||||
va_end(arg);
|
||||
}
|
||||
#else
|
||||
static snd_lib_log_handler_t original_log_handler;
|
||||
static void log_handler(int prio, int interface, const char *file, int line, const char *function, int errcode, const char *fmt, va_list arg)
|
||||
{
|
||||
if (prio == SND_LOG_ERROR && errcode == ENOENT) /* Ignore those misleading "warnings" */
|
||||
return;
|
||||
if (original_log_handler)
|
||||
original_log_handler(prio, interface, file, line, function, errcode, fmt, arg);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
@@ -67,9 +74,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 +89,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 +175,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 +202,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 +210,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 +288,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 +299,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 +353,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);
|
||||
@@ -386,7 +368,11 @@ int main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if SND_LIB_VER(1, 2, 15) < SND_LIB_VERSION
|
||||
snd_lib_error_set_handler(error_handler);
|
||||
#else
|
||||
original_log_handler = snd_lib_log_set_handler(log_handler);
|
||||
#endif
|
||||
|
||||
switch (command) {
|
||||
case LIST:
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -66,7 +66,7 @@ will generate a "format 0" file.
|
||||
.I \-m,\-\-metronome=client:port
|
||||
Plays a metronome signal on the specified sequencer port.
|
||||
|
||||
Metronome sounds are played on channel 10, MIDI notes 33 & 34 (GM2/GS/XG
|
||||
Metronome sounds are played on channel 10, MIDI notes 33 & 34 (GM2/GS/XG
|
||||
metronome standard notes), with velocity 100 and duration 1.
|
||||
|
||||
.TP
|
||||
@@ -74,7 +74,7 @@ metronome standard notes), with velocity 100 and duration 1.
|
||||
Sets the time signature for the MIDI file and metronome.
|
||||
|
||||
The time signature is specified as usual with two numbers, representing
|
||||
the numerator and denominator of the time signature as it would be
|
||||
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.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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(*buf);
|
||||
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;
|
||||
}
|
||||
+449
-28
@@ -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)
|
||||
@@ -400,8 +393,8 @@ static void dump_ump_midi1_event(const unsigned int *ump)
|
||||
break;
|
||||
case SND_UMP_MSG_NOTE_ON:
|
||||
printf("Note on %2d, note %d, velocity %s",
|
||||
channel, m->note_off.note,
|
||||
midi1_data(m->note_off.velocity));
|
||||
channel, m->note_on.note,
|
||||
midi1_data(m->note_on.velocity));
|
||||
break;
|
||||
case SND_UMP_MSG_POLY_PRESSURE:
|
||||
printf("Poly pressure %2d, note %d, value %s",
|
||||
@@ -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) {
|
||||
@@ -552,9 +545,9 @@ static void dump_ump_midi2_event(const unsigned int *ump)
|
||||
break;
|
||||
case SND_UMP_MSG_NOTE_ON:
|
||||
printf("Note on %2d, note %d, velocity %s, attr type = %d, data = 0x%x",
|
||||
channel, m->note_off.note,
|
||||
midi2_velocity(m->note_off.velocity),
|
||||
m->note_off.attr_type, m->note_off.attr_data);
|
||||
channel, m->note_on.note,
|
||||
midi2_velocity(m->note_on.velocity),
|
||||
m->note_on.attr_type, m->note_on.attr_data);
|
||||
break;
|
||||
case SND_UMP_MSG_POLY_PRESSURE:
|
||||
printf("Poly pressure %2d, note %d, value %s",
|
||||
@@ -579,7 +572,7 @@ static void dump_ump_midi2_event(const unsigned int *ump)
|
||||
midi2_data(m->channel_pressure.data));
|
||||
break;
|
||||
case SND_UMP_MSG_PITCHBEND:
|
||||
printf("Channel pressure %2d, value %s",
|
||||
printf("Pitchbend %2d, value %s",
|
||||
channel,
|
||||
midi2_pitchbend(m->channel_pressure.data));
|
||||
break;
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/include
|
||||
EXTRA_DIST = aseqsend.1
|
||||
|
||||
bin_PROGRAMS = aseqsend
|
||||
man_MANS = aseqsend.1
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
};
|
||||
int 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);
|
||||
}
|
||||
@@ -10,11 +10,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <alsa/global.h>
|
||||
#include <alsa/input.h>
|
||||
#include <alsa/output.h>
|
||||
#include <alsa/conf.h>
|
||||
#include <alsa/error.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "dmic-nhlt.h"
|
||||
#include "dmic/dmic-process.h"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "aconfig.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <alsa/global.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "dmic-debug.h"
|
||||
|
||||
#ifdef NHLT_DEBUG
|
||||
|
||||
@@ -11,10 +11,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <alsa/input.h>
|
||||
#include <alsa/output.h>
|
||||
#include <alsa/conf.h>
|
||||
#include <alsa/error.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "../intel-nhlt.h"
|
||||
#include "dmic-process.h"
|
||||
#include "dmic-internal.h"
|
||||
@@ -159,28 +156,33 @@ static void find_modes(struct intel_dmic_params *dmic, struct dmic_calc_decim_mo
|
||||
/* Check for sane pdm clock, min 100 kHz, max ioclk/2 */
|
||||
if (dmic->dmic_prm[di].pdmclk_max < DMIC_HW_PDM_CLK_MIN ||
|
||||
dmic->dmic_prm[di].pdmclk_max > dmic->dmic_prm[di].io_clk / 2) {
|
||||
fprintf(stderr, "find_modes(): pdm clock max not in range\n");
|
||||
fprintf(stderr, "%s: pdm clock max %d not in range\n", __func__,
|
||||
dmic->dmic_prm[di].pdmclk_max);
|
||||
return;
|
||||
}
|
||||
if (dmic->dmic_prm[di].pdmclk_min < DMIC_HW_PDM_CLK_MIN ||
|
||||
dmic->dmic_prm[di].pdmclk_min > dmic->dmic_prm[di].pdmclk_max) {
|
||||
fprintf(stderr, "find_modes(): pdm clock min not in range\n");
|
||||
fprintf(stderr, "%s: pdm clock min %d not in range\n", __func__,
|
||||
dmic->dmic_prm[di].pdmclk_min);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check for sane duty cycle */
|
||||
if (dmic->dmic_prm[di].duty_min > dmic->dmic_prm[di].duty_max) {
|
||||
fprintf(stderr, "find_modes(): duty cycle min > max\n");
|
||||
fprintf(stderr, "%s: duty cycle min > max: %d > %d\n", __func__,
|
||||
dmic->dmic_prm[di].duty_min, dmic->dmic_prm[di].duty_max);
|
||||
return;
|
||||
}
|
||||
if (dmic->dmic_prm[di].duty_min < DMIC_HW_DUTY_MIN ||
|
||||
dmic->dmic_prm[di].duty_min > DMIC_HW_DUTY_MAX) {
|
||||
fprintf(stderr, "find_modes(): pdm clock min not in range\n");
|
||||
fprintf(stderr, "%s: pdm clock min %d not in range\n", __func__,
|
||||
dmic->dmic_prm[di].duty_min);
|
||||
return;
|
||||
}
|
||||
if (dmic->dmic_prm[di].duty_max < DMIC_HW_DUTY_MIN ||
|
||||
dmic->dmic_prm[di].duty_max > DMIC_HW_DUTY_MAX) {
|
||||
fprintf(stderr, "find_modes(): pdm clock max not in range\n");
|
||||
fprintf(stderr, "%s: pdm clock max %d not in range\n", __func__,
|
||||
dmic->dmic_prm[di].duty_max);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -428,7 +430,7 @@ static int select_mode(struct intel_dmic_params *dmic, struct dmic_calc_configur
|
||||
* candidates should be sufficient.
|
||||
*/
|
||||
if (modes->num_of_modes == 0) {
|
||||
fprintf(stderr, "select_mode(): no modes available\n");
|
||||
fprintf(stderr, "%s: no modes available\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -451,7 +453,7 @@ static int select_mode(struct intel_dmic_params *dmic, struct dmic_calc_configur
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
fprintf(stderr, "select_mode(): No filter for decimation found\n");
|
||||
fprintf(stderr, "%s: No filter for decimation found\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
n = idx[found - 1]; /* Option with highest clock divisor and lowest mic clock rate */
|
||||
@@ -468,8 +470,8 @@ static int select_mode(struct intel_dmic_params *dmic, struct dmic_calc_configur
|
||||
if (cfg->mfir_a > 0) {
|
||||
cfg->fir_a = get_fir(dmic, cfg, cfg->mfir_a);
|
||||
if (!cfg->fir_a) {
|
||||
fprintf(stderr, "select_mode(): can't find FIR coefficients, mfir_a = %d\n",
|
||||
cfg->mfir_a);
|
||||
fprintf(stderr, "%s: can't find FIR coefficients, mfir_a = %d\n",
|
||||
__func__, cfg->mfir_a);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
@@ -477,8 +479,8 @@ static int select_mode(struct intel_dmic_params *dmic, struct dmic_calc_configur
|
||||
if (cfg->mfir_b > 0) {
|
||||
cfg->fir_b = get_fir(dmic, cfg, cfg->mfir_b);
|
||||
if (!cfg->fir_b) {
|
||||
fprintf(stderr, "select_mode(): can't find FIR coefficients, mfir_b = %d\n",
|
||||
cfg->mfir_b);
|
||||
fprintf(stderr, "%s: can't find FIR coefficients, mfir_b = %d\n",
|
||||
__func__, cfg->mfir_b);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
@@ -490,7 +492,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, "select_mode(): erroneous decimation factor and CIC gain\n");
|
||||
fprintf(stderr, "%s: erroneous decimation factor and CIC gain\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -515,7 +517,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, "select_mode(): invalid coefficient set found\n");
|
||||
fprintf(stderr, "%s: invalid coefficient set found\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
@@ -531,7 +533,7 @@ static int select_mode(struct intel_dmic_params *dmic, struct dmic_calc_configur
|
||||
cfg->fir_b->length, gain_to_fir);
|
||||
if (ret < 0) {
|
||||
/* Invalid coefficient set found, should not happen. */
|
||||
fprintf(stderr, "select_mode(): invalid coefficient set found\n");
|
||||
fprintf(stderr, "%s: invalid coefficient set found\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
@@ -608,13 +610,24 @@ static void ipm_helper2(struct intel_dmic_params *dmic, int source[], int *ipm)
|
||||
* operate as stereo or mono left (A) or mono right (B) mode. Mono right mode is setup as channel
|
||||
* swapped mono left.
|
||||
*/
|
||||
static int stereo_helper(struct intel_dmic_params *dmic, int stereo[], int swap[])
|
||||
static int stereo_helper(struct intel_dmic_params *dmic, int stereo[], int swap[], int ipmsm[])
|
||||
{
|
||||
int cnt;
|
||||
int i;
|
||||
int i, j;
|
||||
int swap_check;
|
||||
int ret = 0;
|
||||
|
||||
/* Find FIFOs those need stereo mode, ipmsm 0 is mono, 1 is stereo */
|
||||
for (i = 0; i < DMIC_HW_FIFOS; i++) {
|
||||
ipmsm[i] = 0;
|
||||
for (j = 0; j < DMIC_HW_CONTROLLERS; j++) {
|
||||
if (dmic->dmic_prm[i].pdm[j].enable_mic_a && dmic->dmic_prm[i].pdm[j].enable_mic_b) {
|
||||
ipmsm[i] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < DMIC_HW_CONTROLLERS; i++) {
|
||||
cnt = 0;
|
||||
if (dmic->dmic_prm[0].pdm[i].enable_mic_a ||
|
||||
@@ -647,6 +660,7 @@ static int stereo_helper(struct intel_dmic_params *dmic, int stereo[], int swap[
|
||||
|
||||
static int configure_registers(struct intel_dmic_params *dmic, struct dmic_calc_configuration *cfg)
|
||||
{
|
||||
int ipmsm[DMIC_HW_FIFOS];
|
||||
int stereo[DMIC_HW_CONTROLLERS];
|
||||
int swap[DMIC_HW_CONTROLLERS];
|
||||
uint32_t val = 0;
|
||||
@@ -757,6 +771,12 @@ static int configure_registers(struct intel_dmic_params *dmic, struct dmic_calc_
|
||||
of0 = (dmic->dmic_prm[0].fifo_bits == 32) ? 2 : 0;
|
||||
of1 = (dmic->dmic_prm[1].fifo_bits == 32) ? 2 : 0;
|
||||
|
||||
ret = stereo_helper(dmic, stereo, swap, ipmsm);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "%s: Microphones enable conflict for DMIC0 and DMIC1 FIFOs\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (dmic->dmic_prm[di].driver_version == 1) {
|
||||
if (di == 0) {
|
||||
ipm_helper1(dmic, &ipm);
|
||||
@@ -781,7 +801,10 @@ static int configure_registers(struct intel_dmic_params *dmic, struct dmic_calc_
|
||||
}
|
||||
}
|
||||
|
||||
if (dmic->dmic_prm[di].driver_version == 2 || dmic->dmic_prm[di].driver_version == 3) {
|
||||
if (dmic->dmic_prm[di].driver_version >= 2) {
|
||||
if (dmic->dmic_prm[di].driver_version >= 4)
|
||||
bfth = 0;
|
||||
|
||||
if (di == 0) {
|
||||
ipm_helper2(dmic, source, &ipm);
|
||||
val = OUTCONTROL0_TIE(0) |
|
||||
@@ -795,7 +818,7 @@ static int configure_registers(struct intel_dmic_params *dmic, struct dmic_calc_
|
||||
OUTCONTROL0_IPM_SOURCE_2(source[1]) |
|
||||
OUTCONTROL0_IPM_SOURCE_3(source[2]) |
|
||||
OUTCONTROL0_IPM_SOURCE_4(source[3]) |
|
||||
OUTCONTROL0_IPM_SOURCE_MODE(1) |
|
||||
OUTCONTROL0_IPM_SOURCE_MODE(ipmsm[0]) |
|
||||
OUTCONTROL0_TH(th);
|
||||
} else {
|
||||
ipm_helper2(dmic, source, &ipm);
|
||||
@@ -810,19 +833,13 @@ static int configure_registers(struct intel_dmic_params *dmic, struct dmic_calc_
|
||||
OUTCONTROL1_IPM_SOURCE_2(source[1]) |
|
||||
OUTCONTROL1_IPM_SOURCE_3(source[2]) |
|
||||
OUTCONTROL1_IPM_SOURCE_4(source[3]) |
|
||||
OUTCONTROL1_IPM_SOURCE_MODE(1) |
|
||||
OUTCONTROL1_IPM_SOURCE_MODE(ipmsm[1]) |
|
||||
OUTCONTROL1_TH(th);
|
||||
}
|
||||
}
|
||||
|
||||
dmic->dmic_blob.chan_ctrl_cfg[di] = val;
|
||||
|
||||
ret = stereo_helper(dmic, stereo, swap);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "configure_registers(): enable conflict\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < DMIC_HW_CONTROLLERS; i++) {
|
||||
/* CIC */
|
||||
val = CIC_CONTROL_SOFT_RESET(soft_reset) |
|
||||
@@ -975,13 +992,14 @@ int dmic_calculate(struct intel_nhlt_params *nhlt)
|
||||
di = dmic->dmic_dai_index;
|
||||
|
||||
if (di >= DMIC_HW_FIFOS) {
|
||||
fprintf(stderr, "dmic_set_config(): dai->index exceeds number of FIFOs\n");
|
||||
fprintf(stderr, "%s: dai->index %d exceeds number of FIFOs\n", __func__, di);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dmic->dmic_prm[di].num_pdm_active > DMIC_HW_CONTROLLERS) {
|
||||
fprintf(stderr, "dmic_set_config():controller count exceeds platform capability\n");
|
||||
fprintf(stderr, "%s: controller count %d exceeds platform capability\n",
|
||||
__func__, dmic->dmic_prm[di].num_pdm_active);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
@@ -993,7 +1011,8 @@ int dmic_calculate(struct intel_nhlt_params *nhlt)
|
||||
case 32:
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "dmic_set_config(): fifo_bits EINVAL\n");
|
||||
fprintf(stderr, "%s: Bad fifo_bits %d\n", __func__,
|
||||
dmic->dmic_prm[di].fifo_bits);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
@@ -1005,14 +1024,14 @@ int dmic_calculate(struct intel_nhlt_params *nhlt)
|
||||
*/
|
||||
find_modes(dmic, &modes_a, dmic->dmic_prm[0].fifo_fs);
|
||||
if (modes_a.num_of_modes == 0 && dmic->dmic_prm[0].fifo_fs > 0) {
|
||||
fprintf(stderr, "dmic_set_config(): No modes found for FIFO A\n");
|
||||
fprintf(stderr, "%s: No modes found for FIFO A\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
find_modes(dmic, &modes_b, dmic->dmic_prm[1].fifo_fs);
|
||||
if (modes_b.num_of_modes == 0 && dmic->dmic_prm[1].fifo_fs > 0) {
|
||||
fprintf(stderr, "dmic_set_config(): No modes found for FIFO B\n");
|
||||
fprintf(stderr, "%s: No modes found for FIFO B\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
@@ -1020,7 +1039,7 @@ int dmic_calculate(struct intel_nhlt_params *nhlt)
|
||||
match_modes(&modes_ab, &modes_a, &modes_b);
|
||||
ret = select_mode(dmic, &cfg, &modes_ab);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "dmic_set_config(): select_mode() failed\n");
|
||||
fprintf(stderr, "%s: select_mode() failed %d\n", __func__, ret);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
@@ -1030,7 +1049,7 @@ int dmic_calculate(struct intel_nhlt_params *nhlt)
|
||||
*/
|
||||
ret = configure_registers(dmic, &cfg);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "dmic_set_config(): cannot configure registers\n");
|
||||
fprintf(stderr, "%s: cannot configure registers %d\n", __func__, ret);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
@@ -1232,7 +1251,12 @@ int dmic_set_params(struct intel_nhlt_params *nhlt, int dai_index, int driver_ve
|
||||
return -EINVAL;
|
||||
|
||||
if (dai_index >= DMIC_HW_FIFOS) {
|
||||
fprintf(stderr, "set_dmic_data illegal dai index\n");
|
||||
fprintf(stderr, "%s: illegal dai index %d \n", __func__, dai_index);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (driver_version < 1 || driver_version > 5) {
|
||||
fprintf(stderr, "%s: illegal driver version %d\n", __func__, driver_version);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -1261,7 +1285,7 @@ int dmic_set_pdm_params(struct intel_nhlt_params *nhlt, int pdm_index, int enabl
|
||||
return -EINVAL;
|
||||
|
||||
if (pdm_index >= DMIC_HW_CONTROLLERS) {
|
||||
fprintf(stderr, "set_pdm_data illegal pdm_index\n");
|
||||
fprintf(stderr, "%s: illegal pdm_index %d\n", __func__, pdm_index);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <alsa/input.h>
|
||||
#include <alsa/output.h>
|
||||
#include <alsa/conf.h>
|
||||
#include <alsa/error.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#define MIN(a, b) ({ \
|
||||
typeof(a) __a = (a); \
|
||||
|
||||
@@ -10,10 +10,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <alsa/input.h>
|
||||
#include <alsa/output.h>
|
||||
#include <alsa/conf.h>
|
||||
#include <alsa/error.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "intel-nhlt.h"
|
||||
#include "ssp-nhlt.h"
|
||||
#include "ssp/ssp-process.h"
|
||||
|
||||
@@ -7,20 +7,110 @@
|
||||
#include "aconfig.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <alsa/global.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "ssp-debug.h"
|
||||
#include "../intel-nhlt.h"
|
||||
|
||||
#ifdef NHLT_DEBUG
|
||||
|
||||
void ssp_print_calculated(struct intel_ssp_params *ssp)
|
||||
static void ssp_print_blob10(struct intel_ssp_params *ssp, int i)
|
||||
{
|
||||
struct ssp_intel_config_data *blob;
|
||||
int ssp_index = ssp->ssp_count;
|
||||
int j;
|
||||
|
||||
blob = &ssp->ssp_blob[ssp_index][i];
|
||||
fprintf(stdout, "gateway_attributes %u\n", blob->gateway_attributes);
|
||||
for (j = 0; j < SSP_TS_GROUP_SIZE; j++)
|
||||
fprintf(stdout, "ts_group[%d] 0x%08x\n", j, blob->ts_group[j]);
|
||||
|
||||
fprintf(stdout, "ssc0 0x%08x\n", blob->ssc0);
|
||||
fprintf(stdout, "ssc1 0x%08x\n", blob->ssc1);
|
||||
fprintf(stdout, "sscto 0x%08x\n", blob->sscto);
|
||||
fprintf(stdout, "sspsp 0x%08x\n", blob->sspsp);
|
||||
fprintf(stdout, "sstsa 0x%08x\n", blob->sstsa);
|
||||
fprintf(stdout, "ssrsa 0x%08x\n", blob->ssrsa);
|
||||
fprintf(stdout, "ssc2 0x%08x\n", blob->ssc2);
|
||||
fprintf(stdout, "sspsp2 0x%08x\n", blob->sspsp2);
|
||||
fprintf(stdout, "ssc3 0x%08x\n", blob->ssc3);
|
||||
fprintf(stdout, "ssioc 0x%08x\n", blob->ssioc);
|
||||
fprintf(stdout, "mdivc 0x%08x\n", blob->mdivc);
|
||||
fprintf(stdout, "mdivr 0x%08x\n", blob->mdivr);
|
||||
}
|
||||
|
||||
static void ssp_print_blob15(struct intel_ssp_params *ssp, int i)
|
||||
{
|
||||
struct ssp_intel_config_data_1_5 *blob15;
|
||||
int ssp_index = ssp->ssp_count;
|
||||
uint32_t j;
|
||||
|
||||
blob15 = &ssp->ssp_blob_1_5[ssp_index][i];
|
||||
fprintf(stdout, "gateway_attributes %u\n", blob15->gateway_attributes);
|
||||
fprintf(stdout, "version %u\n", blob15->version);
|
||||
fprintf(stdout, "size %u\n", blob15->size);
|
||||
for (j = 0; j < SSP_TS_GROUP_SIZE; j++)
|
||||
fprintf(stdout, "ts_group[%d] 0x%08x\n", j, blob15->ts_group[j]);
|
||||
|
||||
fprintf(stdout, "ssc0 0x%08x\n", blob15->ssc0);
|
||||
fprintf(stdout, "ssc1 0x%08x\n", blob15->ssc1);
|
||||
fprintf(stdout, "sscto 0x%08x\n", blob15->sscto);
|
||||
fprintf(stdout, "sspsp 0x%08x\n", blob15->sspsp);
|
||||
fprintf(stdout, "sstsa 0x%08x\n", blob15->sstsa);
|
||||
fprintf(stdout, "ssrsa 0x%08x\n", blob15->ssrsa);
|
||||
fprintf(stdout, "ssc2 0x%08x\n", blob15->ssc2);
|
||||
fprintf(stdout, "sspsp2 0x%08x\n", blob15->sspsp2);
|
||||
fprintf(stdout, "ssc3 0x%08x\n", blob15->ssc3);
|
||||
fprintf(stdout, "ssioc 0x%08x\n", blob15->ssioc);
|
||||
fprintf(stdout, "mdivc 0x%08x\n", blob15->mdivctlr);
|
||||
fprintf(stdout, "mdivr count 0x%08x\n", blob15->mdivrcnt);
|
||||
for (j = 0; j < blob15->mdivrcnt; j++)
|
||||
fprintf(stdout, "mdivr 0x%08x\n",
|
||||
ssp->ssp_prm[ssp_index].mdivr[i].mdivrs[j]);
|
||||
}
|
||||
|
||||
static void ssp_print_blob30(struct intel_ssp_params *ssp, int i)
|
||||
{
|
||||
struct ssp_intel_config_data_3_0 *blob30;
|
||||
int ssp_index = ssp->ssp_count;
|
||||
uint32_t j;
|
||||
|
||||
blob30 = &ssp->ssp_blob_3_0[ssp_index][i];
|
||||
fprintf(stdout, "gateway_attributes %u\n", blob30->gateway_attributes);
|
||||
fprintf(stdout, "version %u\n", blob30->version);
|
||||
fprintf(stdout, "size %u\n", blob30->size);
|
||||
for (j = 0; j < SSP_TS_GROUP_SIZE; j++)
|
||||
fprintf(stdout, "ts_group[%d] 0x%08x\n", j, blob30->ts_group[j]);
|
||||
|
||||
fprintf(stdout, "ssc0 0x%08x\n", blob30->ssc0);
|
||||
fprintf(stdout, "ssc1 0x%08x\n", blob30->ssc1);
|
||||
fprintf(stdout, "sscto 0x%08x\n", blob30->sscto);
|
||||
fprintf(stdout, "sspsp 0x%08x\n", blob30->sspsp);
|
||||
fprintf(stdout, "ssc2 0x%08x\n", blob30->ssc2);
|
||||
fprintf(stdout, "sspsp2 0x%08x\n", blob30->sspsp2);
|
||||
fprintf(stdout, "rsvd2 0x%08x\n", blob30->rsvd2);
|
||||
fprintf(stdout, "ssioc 0x%08x\n", blob30->ssioc);
|
||||
for (j = 0; j < I2SIPCMC; j++) {
|
||||
fprintf(stdout, "rx_dir[%d] 0x%08x\n", j * 2, (uint32_t)(blob30->rx_dir[j].ssmidytsa & 0xffffffff));
|
||||
fprintf(stdout, "rx_dir[%d] 0x%08x\n", j * 2 + 1, (uint32_t)(blob30->rx_dir[j].ssmidytsa >> 32));
|
||||
}
|
||||
for (j = 0; j < I2SOPCMC; j++) {
|
||||
fprintf(stdout, "tx_dir[%d] 0x%08x\n", j * 2, (uint32_t)(blob30->tx_dir[j].ssmodytsa & 0xffffffff));
|
||||
fprintf(stdout, "tx_dir[%d] 0x%08x\n", j * 2 + 1, (uint32_t)(blob30->tx_dir[j].ssmodytsa >> 32));
|
||||
}
|
||||
|
||||
fprintf(stdout, "mdivc 0x%08x\n", blob30->mdivctlr);
|
||||
fprintf(stdout, "mdivr count 0x%08x\n", blob30->mdivrcnt);
|
||||
for (j = 0; j < blob30->mdivrcnt; j++)
|
||||
fprintf(stdout, "mdivr 0x%08x\n",
|
||||
ssp->ssp_prm[ssp_index].mdivr[i].mdivrs[j]);
|
||||
}
|
||||
|
||||
void ssp_print_calculated(struct intel_ssp_params *ssp)
|
||||
{
|
||||
struct ssp_aux_blob *blob_aux;
|
||||
int ssp_index = ssp->ssp_count;
|
||||
uint32_t *ptr;
|
||||
int i, j;
|
||||
uint32_t i, j;
|
||||
|
||||
fprintf(stdout, "printing ssp nhlt calculated data:\n");
|
||||
|
||||
@@ -38,58 +128,17 @@ void ssp_print_calculated(struct intel_ssp_params *ssp)
|
||||
|
||||
for (i = 0; i < ssp->ssp_hw_config_count[ssp_index]; i++) {
|
||||
fprintf(stdout, "ssp blob %d hw_config %d\n", ssp->ssp_count, i);
|
||||
if (ssp->ssp_prm[ssp_index].version == SSP_BLOB_VER_1_5) {
|
||||
blob15 = &ssp->ssp_blob_1_5[ssp_index][i];
|
||||
fprintf(stdout, "gateway_attributes %u\n", blob15->gateway_attributes);
|
||||
fprintf(stdout, "version %u\n", blob15->version);
|
||||
fprintf(stdout, "size %u\n", blob15->size);
|
||||
fprintf(stdout, "ts_group[0] 0x%08x\n", blob15->ts_group[0]);
|
||||
fprintf(stdout, "ts_group[1] 0x%08x\n", blob15->ts_group[1]);
|
||||
fprintf(stdout, "ts_group[2] 0x%08x\n", blob15->ts_group[2]);
|
||||
fprintf(stdout, "ts_group[3] 0x%08x\n", blob15->ts_group[3]);
|
||||
fprintf(stdout, "ts_group[4] 0x%08x\n", blob15->ts_group[4]);
|
||||
fprintf(stdout, "ts_group[5] 0x%08x\n", blob15->ts_group[5]);
|
||||
fprintf(stdout, "ts_group[6] 0x%08x\n", blob15->ts_group[6]);
|
||||
fprintf(stdout, "ts_group[7] 0x%08x\n", blob15->ts_group[7]);
|
||||
fprintf(stdout, "ssc0 0x%08x\n", blob15->ssc0);
|
||||
fprintf(stdout, "ssc1 0x%08x\n", blob15->ssc1);
|
||||
fprintf(stdout, "sscto 0x%08x\n", blob15->sscto);
|
||||
fprintf(stdout, "sspsp 0x%08x\n", blob15->sspsp);
|
||||
fprintf(stdout, "sstsa 0x%08x\n", blob15->sstsa);
|
||||
fprintf(stdout, "ssrsa 0x%08x\n", blob15->ssrsa);
|
||||
fprintf(stdout, "ssc2 0x%08x\n", blob15->ssc2);
|
||||
fprintf(stdout, "sspsp2 0x%08x\n", blob15->sspsp2);
|
||||
fprintf(stdout, "ssc3 0x%08x\n", blob15->ssc3);
|
||||
fprintf(stdout, "ssioc 0x%08x\n", blob15->ssioc);
|
||||
fprintf(stdout, "mdivc 0x%08x\n", blob15->mdivctlr);
|
||||
fprintf(stdout, "mdivr count 0x%08x\n", blob15->mdivrcnt);
|
||||
for (j = 0; j < blob15->mdivrcnt; j++)
|
||||
fprintf(stdout, "mdivr 0x%08x\n",
|
||||
ssp->ssp_prm[ssp_index].mdivr[i].mdivrs[j]);
|
||||
} else {
|
||||
blob = &ssp->ssp_blob[ssp_index][i];
|
||||
fprintf(stdout, "gateway_attributes %u\n", blob->gateway_attributes);
|
||||
fprintf(stdout, "ts_group[0] 0x%08x\n", blob->ts_group[0]);
|
||||
fprintf(stdout, "ts_group[1] 0x%08x\n", blob->ts_group[1]);
|
||||
fprintf(stdout, "ts_group[2] 0x%08x\n", blob->ts_group[2]);
|
||||
fprintf(stdout, "ts_group[3] 0x%08x\n", blob->ts_group[3]);
|
||||
fprintf(stdout, "ts_group[4] 0x%08x\n", blob->ts_group[4]);
|
||||
fprintf(stdout, "ts_group[5] 0x%08x\n", blob->ts_group[5]);
|
||||
fprintf(stdout, "ts_group[6] 0x%08x\n", blob->ts_group[6]);
|
||||
fprintf(stdout, "ts_group[7] 0x%08x\n", blob->ts_group[7]);
|
||||
fprintf(stdout, "ssc0 0x%08x\n", blob->ssc0);
|
||||
fprintf(stdout, "ssc1 0x%08x\n", blob->ssc1);
|
||||
fprintf(stdout, "sscto 0x%08x\n", blob->sscto);
|
||||
fprintf(stdout, "sspsp 0x%08x\n", blob->sspsp);
|
||||
fprintf(stdout, "sstsa 0x%08x\n", blob->sstsa);
|
||||
fprintf(stdout, "ssrsa 0x%08x\n", blob->ssrsa);
|
||||
fprintf(stdout, "ssc2 0x%08x\n", blob->ssc2);
|
||||
fprintf(stdout, "sspsp2 0x%08x\n", blob->sspsp2);
|
||||
fprintf(stdout, "ssc3 0x%08x\n", blob->ssc3);
|
||||
fprintf(stdout, "ssioc 0x%08x\n", blob->ssioc);
|
||||
fprintf(stdout, "mdivc 0x%08x\n", blob->mdivc);
|
||||
fprintf(stdout, "mdivr 0x%08x\n", blob->mdivr);
|
||||
switch (ssp->ssp_prm[ssp_index].version) {
|
||||
case SSP_BLOB_VER_1_5:
|
||||
ssp_print_blob15(ssp, i);
|
||||
break;
|
||||
case SSP_BLOB_VER_3_0:
|
||||
ssp_print_blob30(ssp, i);
|
||||
break;
|
||||
default:
|
||||
ssp_print_blob10(ssp, i);
|
||||
}
|
||||
|
||||
blob_aux = (struct ssp_aux_blob *)&(ssp->ssp_blob_ext[ssp_index][i]);
|
||||
fprintf(stdout, "aux_blob size %u\n", blob_aux->size);
|
||||
for (j = 0; j < blob_aux->size; j += 4) {
|
||||
@@ -115,7 +164,7 @@ void ssp_print_internal(struct intel_ssp_params *ssp)
|
||||
struct ssp_config_dai *dai;
|
||||
struct ssp_config_hw *hw_conf;
|
||||
uint32_t enabled;
|
||||
int i, j;
|
||||
uint32_t i, j;
|
||||
|
||||
dai = &ssp->ssp_prm[ssp->ssp_count];
|
||||
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SSP_TS_GROUP_SIZE 8
|
||||
|
||||
/* struct for intel ssp nhlt vendor specific blob generation */
|
||||
struct ssp_intel_config_data {
|
||||
uint32_t gateway_attributes;
|
||||
uint32_t ts_group[8];
|
||||
uint32_t ts_group[SSP_TS_GROUP_SIZE];
|
||||
uint32_t ssc0;
|
||||
uint32_t ssc1;
|
||||
uint32_t sscto;
|
||||
@@ -31,12 +33,13 @@ struct ssp_intel_config_data {
|
||||
} __attribute__((packed));
|
||||
|
||||
#define SSP_BLOB_VER_1_5 0xEE000105
|
||||
#define SSP_BLOB_VER_3_0 0xEE000300
|
||||
|
||||
struct ssp_intel_config_data_1_5 {
|
||||
uint32_t gateway_attributes;
|
||||
uint32_t version;
|
||||
uint32_t size;
|
||||
uint32_t ts_group[8];
|
||||
uint32_t ts_group[SSP_TS_GROUP_SIZE];
|
||||
uint32_t ssc0;
|
||||
uint32_t ssc1;
|
||||
uint32_t sscto;
|
||||
@@ -52,6 +55,37 @@ struct ssp_intel_config_data_1_5 {
|
||||
uint32_t mdivr[];
|
||||
} __attribute__((packed));
|
||||
|
||||
#define I2SIPCMC 8
|
||||
#define I2SOPCMC 8
|
||||
|
||||
struct ssp_rx_dir {
|
||||
uint64_t ssmidytsa;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ssp_tx_dir {
|
||||
uint64_t ssmodytsa;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ssp_intel_config_data_3_0 {
|
||||
uint32_t gateway_attributes;
|
||||
uint32_t version;
|
||||
uint32_t size;
|
||||
uint32_t ts_group[SSP_TS_GROUP_SIZE];
|
||||
uint32_t ssc0;
|
||||
uint32_t ssc1;
|
||||
uint32_t sscto;
|
||||
uint32_t sspsp;
|
||||
uint32_t ssc2;
|
||||
uint32_t sspsp2;
|
||||
uint32_t rsvd2;
|
||||
uint32_t ssioc;
|
||||
struct ssp_rx_dir rx_dir[I2SIPCMC];
|
||||
struct ssp_tx_dir tx_dir[I2SOPCMC];
|
||||
uint32_t mdivctlr;
|
||||
uint32_t mdivrcnt;
|
||||
uint32_t mdivr[];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ssp_intel_aux_tlv {
|
||||
uint32_t type;
|
||||
uint32_t size;
|
||||
|
||||
@@ -147,6 +147,7 @@ struct intel_ssp_params {
|
||||
/* ssp vendor blob structs */
|
||||
struct ssp_intel_config_data ssp_blob[SSP_MAX_DAIS][SSP_MAX_HW_CONFIG];
|
||||
struct ssp_intel_config_data_1_5 ssp_blob_1_5[SSP_MAX_DAIS][SSP_MAX_HW_CONFIG];
|
||||
struct ssp_intel_config_data_3_0 ssp_blob_3_0[SSP_MAX_DAIS][SSP_MAX_HW_CONFIG];
|
||||
struct ssp_aux_blob ssp_blob_ext[SSP_MAX_DAIS][SSP_MAX_HW_CONFIG];
|
||||
};
|
||||
|
||||
|
||||
@@ -14,11 +14,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <alsa/global.h>
|
||||
#include <alsa/input.h>
|
||||
#include <alsa/output.h>
|
||||
#include <alsa/conf.h>
|
||||
#include <alsa/error.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "../intel-nhlt.h"
|
||||
#include "../../nhlt.h"
|
||||
#include "ssp-process.h"
|
||||
@@ -38,10 +34,51 @@ static int popcount(uint32_t value)
|
||||
return bits_set;
|
||||
}
|
||||
|
||||
static void ssp_calculate_intern_v30(struct intel_nhlt_params *nhlt, int hwi)
|
||||
{
|
||||
struct intel_ssp_params *ssp = (struct intel_ssp_params *)nhlt->ssp_params;
|
||||
int di = ssp->ssp_count;
|
||||
struct ssp_intel_config_data_3_0 *blob30 = &ssp->ssp_blob_3_0[di][hwi];
|
||||
struct ssp_intel_config_data *blob = &ssp->ssp_blob[di][hwi];
|
||||
int i;
|
||||
|
||||
blob30->gateway_attributes = blob->gateway_attributes;
|
||||
blob30->version = SSP_BLOB_VER_3_0;
|
||||
for (i = 0; i < SSP_TS_GROUP_SIZE; i++)
|
||||
blob30->ts_group[i] = blob->ts_group[i];
|
||||
|
||||
blob30->ssc0 = blob->ssc0;
|
||||
blob30->ssc1 = blob->ssc1;
|
||||
blob30->sscto = blob->sscto;
|
||||
blob30->sspsp = blob->sspsp;
|
||||
blob30->ssc2 = blob->ssc2;
|
||||
blob30->sspsp2 = blob->sspsp2;
|
||||
blob30->rsvd2 = 0;
|
||||
blob30->ssioc = blob->ssioc;
|
||||
|
||||
blob30->rx_dir[0].ssmidytsa = ssp->ssp_prm[di].hw_cfg[hwi].rx_slots;
|
||||
for (i = 1; i < I2SIPCMC; i++)
|
||||
blob30->rx_dir[i].ssmidytsa = 0;
|
||||
|
||||
blob30->tx_dir[0].ssmodytsa = ssp->ssp_prm[di].hw_cfg[hwi].tx_slots;
|
||||
for (i = 1; i < I2SOPCMC; i++)
|
||||
blob30->tx_dir[i].ssmodytsa = 0;
|
||||
|
||||
/* mdivr count is always 1 */
|
||||
blob30->mdivctlr = blob->mdivc;
|
||||
ssp->ssp_prm[di].mdivr[hwi].count = 1;
|
||||
blob30->mdivrcnt = ssp->ssp_prm[di].mdivr[hwi].count;
|
||||
ssp->ssp_prm[di].mdivr[hwi].mdivrs[0] = blob->mdivr;
|
||||
|
||||
blob30->size = sizeof(struct ssp_intel_config_data_3_0) +
|
||||
blob30->mdivrcnt * sizeof(uint32_t) +
|
||||
ssp->ssp_blob_ext[di][hwi].size;
|
||||
}
|
||||
|
||||
static void ssp_calculate_intern_v15(struct intel_nhlt_params *nhlt, int hwi)
|
||||
{
|
||||
struct intel_ssp_params *ssp = (struct intel_ssp_params *)nhlt->ssp_params;
|
||||
int di = ssp->ssp_count;;
|
||||
int di = ssp->ssp_count;
|
||||
struct ssp_intel_config_data_1_5 *blob15 = &ssp->ssp_blob_1_5[di][hwi];
|
||||
struct ssp_intel_config_data *blob = &ssp->ssp_blob[di][hwi];
|
||||
int i;
|
||||
@@ -49,7 +86,7 @@ static void ssp_calculate_intern_v15(struct intel_nhlt_params *nhlt, int hwi)
|
||||
blob15->gateway_attributes = ssp->ssp_blob[di][hwi].gateway_attributes;
|
||||
blob15->version = SSP_BLOB_VER_1_5;
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
for (i = 0; i < SSP_TS_GROUP_SIZE; i++)
|
||||
blob15->ts_group[i] = blob->ts_group[i];
|
||||
|
||||
blob15->ssc0 = blob->ssc0;
|
||||
@@ -115,11 +152,17 @@ static int ssp_calculate_intern(struct intel_nhlt_params *nhlt, int hwi)
|
||||
|
||||
/* reset SSP settings */
|
||||
/* sscr0 dynamic settings are DSS, EDSS, SCR, FRDC, ECS */
|
||||
ssp->ssp_blob[di][hwi].ssc0 = SSCR0_PSP | SSCR0_RIM | SSCR0_TIM;
|
||||
ssp->ssp_blob[di][hwi].ssc0 = SSCR0_MOD | SSCR0_PSP | SSCR0_RIM | SSCR0_TIM;
|
||||
|
||||
/* sscr1 dynamic settings are SFRMDIR, SCLKDIR, SCFR */
|
||||
ssp->ssp_blob[di][hwi].ssc1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_TRAIL | SSCR1_RSRE |
|
||||
SSCR1_TSRE;
|
||||
if (ssp->ssp_prm[di].version == SSP_BLOB_VER_3_0)
|
||||
/* bits 21 and 20, TSRE and RSRE do not exist in ACE3.x
|
||||
* Note: Assuming SSP_BLOB_VER_3_0 is ACE3.x
|
||||
*/
|
||||
ssp->ssp_blob[di][hwi].ssc1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_TRAIL;
|
||||
else
|
||||
ssp->ssp_blob[di][hwi].ssc1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_TRAIL |
|
||||
SSCR1_RSRE | SSCR1_TSRE;
|
||||
|
||||
/* sscr2 dynamic setting is LJDFD */
|
||||
ssp->ssp_blob[di][hwi].ssc2 = SSCR2_SDFD | SSCR2_TURM1;
|
||||
@@ -197,7 +240,13 @@ static int ssp_calculate_intern(struct intel_nhlt_params *nhlt, int hwi)
|
||||
ssp->ssp_blob[di][hwi].sspsp |= SSPSP_SCMODE(inverted_bclk);
|
||||
}
|
||||
|
||||
ssp->ssp_blob[di][hwi].ssc0 |= SSCR0_MOD | SSCR0_ACS;
|
||||
/* Note: ACS as SSCR0(30) does not exist in any ACE version, or cAVS2.x. This disables
|
||||
* it for ACE3.x. It might be good to fix later for other platforms. In cAVS this is 30:29
|
||||
* reserved, in ACE1.x this is DLE as 30:29, in ACE2.x this is RSVD30 as 30:29, in ACE3.x this
|
||||
* is DLE as 30:29.
|
||||
*/
|
||||
if (ssp->ssp_prm[di].version != SSP_BLOB_VER_3_0)
|
||||
ssp->ssp_blob[di][hwi].ssc0 |= SSCR0_ACS;
|
||||
|
||||
/* Additional hardware settings */
|
||||
|
||||
@@ -538,13 +587,23 @@ static int ssp_calculate_intern(struct intel_nhlt_params *nhlt, int hwi)
|
||||
|
||||
ssp->ssp_blob[di][hwi].mdivr = clk_div;
|
||||
/* clock will always go through the divider */
|
||||
|
||||
/* Note: There is no SSC0(6) ECS in ACE3.x but RSVD6 in same bit position
|
||||
* that must be set to one.
|
||||
*/
|
||||
ssp->ssp_blob[di][hwi].ssc0 |= SSCR0_ECS;
|
||||
|
||||
/* enable divider for this clock id */
|
||||
ssp->ssp_blob[di][hwi].mdivc |= BIT(ssp->ssp_prm[di].mclk_id);
|
||||
/* set mclk source always for audio cardinal clock */
|
||||
ssp->ssp_blob[di][hwi].mdivc |= MCDSS(SSP_CLOCK_AUDIO_CARDINAL);
|
||||
/* set bclk source for audio cardinal clock */
|
||||
ssp->ssp_blob[di][hwi].mdivc |= MNDSS(SSP_CLOCK_AUDIO_CARDINAL);
|
||||
/* set bclk source for audio cardinal clock
|
||||
* Note: There is no MDIVXCTRL(21:20) MNDSS in any ACE version 1.x - 3.x. MNDSS
|
||||
* exists in cAVS2.x. This removes the set for ACE3.x. May need to address other
|
||||
* ACE platforms later.
|
||||
*/
|
||||
if (ssp->ssp_prm[di].version != SSP_BLOB_VER_3_0)
|
||||
ssp->ssp_blob[di][hwi].mdivc |= MNDSS(SSP_CLOCK_AUDIO_CARDINAL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -748,6 +807,9 @@ int ssp_calculate(struct intel_nhlt_params *nhlt)
|
||||
return -EINVAL;
|
||||
/* v15 blob is made from legacy blob, so it can't fail */
|
||||
ssp_calculate_intern_v15(nhlt, i);
|
||||
|
||||
/* v30 blob is made from legacy blob, so it can't fail */
|
||||
ssp_calculate_intern_v30(nhlt, i);
|
||||
}
|
||||
|
||||
ssp_print_internal(ssp);
|
||||
@@ -826,6 +888,8 @@ int ssp_get_vendor_blob_size(struct intel_nhlt_params *nhlt, int dai_index,
|
||||
/* set size for the blob */
|
||||
if (ssp->ssp_prm[dai_index].version == SSP_BLOB_VER_1_5)
|
||||
*size = ssp->ssp_blob_1_5[dai_index][hw_config_index].size;
|
||||
else if (ssp->ssp_prm[dai_index].version == SSP_BLOB_VER_3_0)
|
||||
*size = ssp->ssp_blob_3_0[dai_index][hw_config_index].size;
|
||||
else
|
||||
/* legacy */
|
||||
*size = sizeof(struct ssp_intel_config_data) +
|
||||
@@ -867,8 +931,19 @@ int ssp_get_vendor_blob(struct intel_nhlt_params *nhlt, uint8_t *vendor_blob,
|
||||
memcpy(vendor_blob + basic_len + clock_len,
|
||||
ssp->ssp_blob_ext[dai_index][hw_config_index].aux_blob,
|
||||
ssp->ssp_blob_ext[dai_index][hw_config_index].size);
|
||||
}
|
||||
else {
|
||||
} else if (ssp->ssp_prm[dai_index].version == SSP_BLOB_VER_3_0) {
|
||||
basic_len = sizeof(struct ssp_intel_config_data_3_0);
|
||||
clock_len = sizeof(uint32_t) * ssp->ssp_prm[dai_index].mdivr[hw_config_index].count;
|
||||
/* basic data */
|
||||
memcpy(vendor_blob, &ssp->ssp_blob_3_0[dai_index][hw_config_index], basic_len);
|
||||
/* clock data */
|
||||
memcpy(vendor_blob + basic_len,
|
||||
&ssp->ssp_prm[dai_index].mdivr[hw_config_index].mdivrs[0], clock_len);
|
||||
/* ext data */
|
||||
memcpy(vendor_blob + basic_len + clock_len,
|
||||
ssp->ssp_blob_ext[dai_index][hw_config_index].aux_blob,
|
||||
ssp->ssp_blob_ext[dai_index][hw_config_index].size);
|
||||
} else {
|
||||
basic_len = sizeof(struct ssp_intel_config_data);
|
||||
/*basic data */
|
||||
memcpy(vendor_blob, &ssp->ssp_blob[dai_index][hw_config_index], basic_len);
|
||||
@@ -914,6 +989,9 @@ int ssp_set_params(struct intel_nhlt_params *nhlt, const char *dir, int dai_inde
|
||||
/* let's compare the lower 16 bits as we don't send the signature from topology */
|
||||
if (version == (SSP_BLOB_VER_1_5 & ((1 << 16) - 1)))
|
||||
ssp->ssp_prm[ssp->ssp_count].version = SSP_BLOB_VER_1_5;
|
||||
else if (version == (SSP_BLOB_VER_3_0 & ((1 << 16) - 1)))
|
||||
ssp->ssp_prm[ssp->ssp_count].version = SSP_BLOB_VER_3_0;
|
||||
|
||||
if (tdm_padding_per_slot && !strcmp(tdm_padding_per_slot, "true"))
|
||||
ssp->ssp_prm[ssp->ssp_count].tdm_per_slot_padding_flag = 1;
|
||||
else
|
||||
|
||||
@@ -11,11 +11,7 @@
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
#include <alsa/global.h>
|
||||
#include <alsa/input.h>
|
||||
#include <alsa/output.h>
|
||||
#include <alsa/conf.h>
|
||||
#include <alsa/error.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "pre-process-external.h"
|
||||
#include "nhlt.h"
|
||||
#include "intel/intel-nhlt.h"
|
||||
|
||||
+174
-4
@@ -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);
|
||||
|
||||
+5
-2
@@ -398,9 +398,12 @@ static int add_define(char **defs, char *d)
|
||||
size_t len = (*defs ? strlen(*defs) : 0) + strlen(d) + 2;
|
||||
char *m = realloc(*defs, len);
|
||||
if (m) {
|
||||
if (*defs)
|
||||
if (*defs) {
|
||||
strcat(m, ",");
|
||||
strcat(m, d);
|
||||
strcat(m, d);
|
||||
} else {
|
||||
strcpy(m, d);
|
||||
}
|
||||
*defs = m;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user