diff options
-rw-r--r-- | lib/libsndio/mio_thru.c | 22 | ||||
-rw-r--r-- | usr.bin/aucat/aucat.1 | 349 | ||||
-rw-r--r-- | usr.bin/aucat/aucat.c | 334 | ||||
-rw-r--r-- | usr.bin/aucat/dev.c | 17 | ||||
-rw-r--r-- | usr.bin/aucat/dev.h | 5 | ||||
-rw-r--r-- | usr.bin/aucat/midi.c | 8 | ||||
-rw-r--r-- | usr.bin/aucat/midicat.1 | 162 | ||||
-rw-r--r-- | usr.bin/aucat/opt.c | 5 |
8 files changed, 513 insertions, 389 deletions
diff --git a/lib/libsndio/mio_thru.c b/lib/libsndio/mio_thru.c index 67763ad9000..7ce4853dd7b 100644 --- a/lib/libsndio/mio_thru.c +++ b/lib/libsndio/mio_thru.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mio_thru.c,v 1.8 2010/04/24 06:15:54 ratchov Exp $ */ +/* $OpenBSD: mio_thru.c,v 1.9 2010/07/06 01:12:45 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org> * @@ -55,6 +55,7 @@ struct mio_hdl * thru_open(const char *str, char *sock, unsigned mode, int nbio) { extern char *__progname; + char unit[4], *sep, *opt; struct amsg msg; int s, n, todo; unsigned char *data; @@ -63,11 +64,24 @@ thru_open(const char *str, char *sock, unsigned mode, int nbio) socklen_t len = sizeof(struct sockaddr_un); uid_t uid; + sep = strchr(str, '.'); + if (sep == NULL) { + opt = "default"; + strlcpy(unit, str, sizeof(unit)); + } else { + opt = sep + 1; + if (sep - str >= sizeof(unit)) { + DPRINTF("thru_open: %s: too long\n", str); + return NULL; + } + strlcpy(unit, str, opt - str); + } + DPRINTF("thru_open: trying %s -> %s.%s\n", str, unit, opt); uid = geteuid(); if (strchr(str, '/') != NULL) return NULL; snprintf(ca.sun_path, sizeof(ca.sun_path), - "/tmp/aucat-%u/%s%s", uid, sock, str); + "/tmp/aucat-%u/%s%s", uid, sock, unit); ca.sun_family = AF_UNIX; hdl = malloc(sizeof(struct thru_hdl)); @@ -84,7 +98,7 @@ thru_open(const char *str, char *sock, unsigned mode, int nbio) DPERROR("thru_open: connect"); /* try shared server */ snprintf(ca.sun_path, sizeof(ca.sun_path), - "/tmp/aucat/%s%s", sock, str); + "/tmp/aucat/%s%s", sock, unit); while (connect(s, (struct sockaddr *)&ca, len) < 0) { if (errno == EINTR) continue; @@ -110,7 +124,7 @@ thru_open(const char *str, char *sock, unsigned mode, int nbio) msg.u.hello.proto |= AMSG_MIDIIN; if (mode & MIO_OUT) msg.u.hello.proto |= AMSG_MIDIOUT; - strlcpy(msg.u.hello.opt, "default", sizeof(msg.u.hello.opt)); + strlcpy(msg.u.hello.opt, opt, sizeof(msg.u.hello.opt)); strlcpy(msg.u.hello.who, __progname, sizeof(msg.u.hello.who)); n = write(s, &msg, sizeof(struct amsg)); if (n < 0) { diff --git a/usr.bin/aucat/aucat.1 b/usr.bin/aucat/aucat.1 index a94cb907027..2b60a6750ca 100644 --- a/usr.bin/aucat/aucat.1 +++ b/usr.bin/aucat/aucat.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: aucat.1,v 1.70 2010/06/04 06:15:28 ratchov Exp $ +.\" $OpenBSD: aucat.1,v 1.71 2010/07/06 01:12:45 ratchov Exp $ .\" .\" Copyright (c) 2006 Alexandre Ratchov <alex@caoua.org> .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: June 4 2010 $ +.Dd $Mdocdate: July 6 2010 $ .Dt AUCAT 1 .Os .Sh NAME @@ -45,24 +45,34 @@ .Op Fl z Ar nframes .Ek .Sh DESCRIPTION -The .Nm -utility can record one input stream -and store it on multiple destination files, -doing the necessary conversions on the fly. -It can play, convert, and mix multiple input files simultaneously, -and can also act as an audio server. +is an audio utility which can simultaneously play and record +any number of audio +.Em streams +on any number of audio devices, +possibly controlled through MIDI. +It can also act as an audio server, in which case streams +correspond to client connections rather than plain files. +.Pp +Audio devices are independent. +A list of streams is attached to each audio device, +as well as an optional list of MIDI ports to control the device. +A typical invocation of .Nm -also has a legacy mode that works like previous versions of -.Nm , -which does not convert on the fly and supports playback of .au files. +consists in providing streams to play and record, +and possibly the audio device to use, if the default is not desired. +.Pp +This also applies to server mode, except that streams are created +dynamically when clients connect to the server. +Thus, instead of actual streams (paths to plain files), +templates for client streams (sub-device names) must be provided. .Pp The options are as follows: .Bl -tag -width Ds .It Fl a Ar flag Control whether .Nm -opens the device only when needed or keeps it open all the time. +opens the audio device only when needed or keeps it open all the time. If the flag is .Va on then the device is kept open all the time, ensuring no other program can @@ -82,31 +92,38 @@ and thus controls the playback latency. .Fl C Ar min : Ns Ar max , .Fl c Ar min : Ns Ar max .Xc -The range of channel numbers on the record or playback stream, respectively. +The range of stream channel numbers for recording and playback directions, respectively. The default is 0:1, i.e. stereo. .It Fl d -Do not daemonize. -If this option is specified, +Increase log verbosity. .Nm -will run in the foreground and log to -.Em stderr . +logs on stderr until it daemonizes. .It Fl e Ar enc Encoding of the playback or recording stream (see below). The default is signed, 16-bit, native byte order. .It Fl f Ar device -The +Add this .Xr sndio 7 -audio device to use for playing and/or recording. +audio device to devices used for playing and/or recording. +Preceding streams +.Pq Fl ios , +control MIDI ports +.Pq Fl q , +and per-device options +.Pq Fl abz +apply to this device. +Device mode and parameters are determined from streams +attached to it. .It Fl h Ar fmt File format of the playback or record stream (see below). The default is auto. .It Fl i Ar file -Add this file to the list of files to play. +Add this file to the list of streams to play. If the option argument is .Sq - then standard input will be used. .It Fl j Ar flag -Control whether channels are joined or expanded if +Control whether stream channels are joined or expanded if the stream number of channels is not equal to the device number of channels. If the flag is .Va off @@ -121,21 +138,7 @@ be sent on multiple outputs or to record a stereo input into a mono stream. The default is .Ar on . .It Fl l -Listen for incoming connections on a -.Ux Ns -domain -socket. -This allows clients to use -.Nm -instead of the physical audio device for audio input and output -in order to share the physical device with other clients. -The default -.Xr sndio 7 -device exposed is -.Pa aucat:0 -.Pq "also known as" Pa aucat:0.default -but other names can be used with the -.Fl s -option. +Detach and become a daemon. .It Fl m Ar mode Set the stream mode. Valid modes are @@ -153,27 +156,36 @@ The default is (i.e. full-duplex). .It Fl n Loopback mode. -Instead of using an audio device, send input streams +Instead of using audio devices, send input streams to the output, processing them on the fly. This mode is useful to mix, demultiplex, resample or reencode audio files offline. .It Fl o Ar file -Add this file to the list of files in which to store recorded samples. +Add this file to the list of recording streams. If the option argument is .Sq - then standard output will be used. .It Fl q Ar device -The +Expose the audio device clock on this .Xr sndio 7 -MIDI device to use for controlling stream volumes or -to start multiple streams synchronously. +MIDI port and allow audio device properties to be controlled +through MIDI. +This includes per-stream volumes and the ability to +synchronously start, stop and relocate streams created in +MIDI Machine +Control (MMC) slave mode +.Pq Fl t . .It Fl r Ar rate -Sample rate in Hertz of the playback or record stream. +Sample rate in Hertz of the stream. The default is 44100Hz. .It Fl s Ar name Add .Ar name to the list of sub-devices to expose in server mode. +This allows clients to use +.Nm +instead of the physical audio device for audio input and output +in order to share the physical device with other clients. Defining multiple sub-devices allows splitting a physical audio device into logical devices having different properties (e.g. channel ranges). The given @@ -184,7 +196,7 @@ part of the .Xr sndio 7 device name string. .It Fl t Ar mode -Select the way sub-devices are controlled by MIDI Machine Control (MMC) +Select the way streams are controlled by MIDI Machine Control (MMC) messages. If the mode is .Va off @@ -242,7 +254,7 @@ If the policy is .Dq error then the stream is closed permanently. .Pp -If a sub-device is created with the +If a stream is created with the .Fl t option, the @@ -250,10 +262,10 @@ the action is disabled for any stream connected to it to ensure proper synchronization. .It Fl z Ar nframes -The audio block size in frames. +The audio device block size in frames. This is the number of frames between audio clock ticks, i.e. the clock resolution. -If a sub-device is created with the +If a stream is created with the .Fl t option, and MTC is used for synchronization, the clock @@ -263,6 +275,43 @@ For instance, 120 ticks per second at 48000Hz corresponds to a 400 frame block size. .El .Pp +On the command line, +per-device parameters +.Pq Fl abz +must precede the device definition +.Pq Fl f , +and per-stream parameters +.Pq Fl Ccehjmrtvx +must precede the stream definition +.Pq Fl ios . +MIDI ports +.Pq Fl q +and streams definitions +.Pq Fl ios +must precede the definition of the device +.Pq Fl f +to which they are attached. +Global parameters +.Pq Fl dlnu +are position-independent. +.Pp +If no audio devices +.Pq Fl f +are specified, +settings are applied as if +the default device is specified as the last argument. +If no streams +.Pq Fl ios +are specified for a device, a default server sub-device is +created attached to it, meaning that +.Nm +behaves as an audio server. +The default +.Xr sndio 7 +device is +.Pa aucat:0 +.Pq also known as Pa aucat:0.default +.Pp If .Nm is sent @@ -272,50 +321,6 @@ or .Dv SIGTERM , it terminates recording to files. .Pp -Settings for input files -.Pq Fl i , -output files -.Pq Fl o , -and sub-devices -.Pq Fl s -can be changed using the -.Fl Ccehrvx -options. -The last -.Fl Ccehrvx -options specified before an -.Fl i , -.Fl o , -or -.Fl s -are applied to the corresponding file. -.Pp -Settings for the audio device -can be changed using the -.Fl Ccer -options. -They apply to the audio device only if the -.Fl u -option is given as well. -The last -.Fl Ccer -option specified before an -.Fl f -is applied to -.Ar device . -.Pp -If no audio device -.Pq Fl f -is specified, -settings are applied as if -the default device is specified as the last argument. -If no sub-devices -.Pq Fl s -are specified -settings are applied as if -.Ar default -is specified as the last argument. -.Pp File formats are specified using the .Fl h option. @@ -396,104 +401,78 @@ signed 18-bit, packed in 3 bytes, little endian unsigned 18-bit, packed in 3 bytes, big endian .El .Sh SERVER MODE +If at least one sub-device +.Pq Fl s +is exposed by +.Nm , +including the case when no stream options are given, +then .Nm -can be used in server mode -.Pq Fl l +can be used as a server to overcome hardware limitations and allow applications to run on fixed sample rate devices or on devices supporting only unusual encodings. .Pp -The -.Nm -audio server may be started by the super-user, -in which case any user will be able to connect to it. -For privacy reasons, only one user may have connections to it -at a given time. -.Pp -Alternatively, each user may run his instance -of the server. -It is generally not desirable to have multiple -instances of -.Nm -running in server mode, -so it is good practice to start it thus: -.Bd -literal -offset indent -$ pgrep -x aucat || aucat -l -.Ed -.Pp -This also ensures privacy by preventing -other users from accessing the audio system. -On multi-user machines -.Nm -should be killed when no longer in use to make audio resources -available again to others: -.Bd -literal -offset indent -$ pkill -x aucat -.Ed -.Pp Certain applications, such as synthesis software, require a low latency audio setup. -To reduce the probability of buffer underruns or overruns, -the -.Xr renice 8 -command can be used to give a higher priority to the -.Nm -process. -Superuser privileges are required. -For example: -.Bd -literal -offset indent -$ aucat -b 3500 -l -$ sudo renice -n -20 -p `pgrep -x aucat` -.Ed +To reduce the probability of buffer underruns or overruns, especially +on busy machines, the server can be started by the super-user, in which +case it will run with higher priority. +Any user will still be able to +connect to it, but for privacy reasons, only one user may have +connections to it at a given time. .Sh MIDI CONTROL -While running in server mode -.Pq Fl l .Nm -exposes a MIDI device with the same name as the default audio -device. -It allows MIDI hardware or software to control programs -using +can expose the audio device clock on registered +MIDI ports +.Pq Fl q +and allows audio device properties to be controlled +through MIDI. +If running in server mode .Nm -or to synchronize to them. +creates a MIDI port with the same name as the default audio +device to which MIDI programs can connect. .Pp A MIDI channel is assigned to each stream, and the volume is changed using the standard volume controller (number 7). -Similarly, when the audio application changes its volume, +Similarly, when the audio client changes its volume, the same MIDI controller message is sent out; it can be used for instance for monitoring or as feedback for motorized faders. .Pp -Clients connected to sub-devices created with the +Streams created with the .Fl t option are controlled by the following MMC messages: .Bl -tag -width relocateXXX -offset indent .It relocate -Gives -.Nm -the time, relative to the beginning of the stream, at which playback +Streams are relocated to the requested time postion +relative to the beginning of the stream, at which playback and recording must start. -It is not interpreted by -.Nm -itself. -The given time position is sent to MIDI clients as an MTC +If the requested position is beyond the end of file, +the stream is temporarly disabled until a valid postion is requested. +This message is ignored by client streams (server mode). +The given time position is sent to MIDI ports as an MTC .Dq "full frame" message forcing all MTC-slaves to relocate to the given position (see below). .It start -Put the sub-device in starting mode. -In this mode, the sub-device waits for all streams to become ready +Put all streams in starting mode. +In this mode, +.Nm +waits for all streams to become ready to start, and then starts them synchronously. -Once started, new streams can be created, but they will be blocked +Once started, new streams can be created (server mode), but they will be blocked until the next stop-to-start transition. .It stop -Put the sub-device in stopped mode (the default). +Put all streams in stopped mode (the default). In this mode, any stream attempting to start playback or recording is paused. -Streams that are already started are not affected until they stop -and try to start again. +Files are stopped and rewound back to the starting position, +while client streams (server mode) that are already +started are not affected until they stop and try to start again. .El .Pp -Sub-devices created with the +Streams created with the .Fl t option export the server clock using MTC, allowing non-audio software or hardware to be synchronized to the audio stream. @@ -531,36 +510,6 @@ wait for the MMC start signal and start synchronously. Regardless of which device a stream is connected to, its playback volume knob is exposed. .Pp -If -.Nm -is used to play and record audio files, it offers -similar MIDI control. -.Nm -can open a -.Xr sndio 7 -MIDI device allowing MIDI hardware or software -to control playback and recording in real time. -.Pp -A MIDI channel is assigned to each stream, and the volume -is changed using the standard volume controller (number 7). -Streams created with the -.Fl t -option are controlled by the following MMC messages: -.Bl -tag -width relocateXXX -offset indent -.It relocate -Streams are relocated to the requested time postion -relative to the beginning of the stream, at which playback -and recording must start. -If the requested position is beyond the end of file, -the stream is temporarly disabled until a valid postion is requested. -.It start -Start all streams synchronously. -By default, streams are created in a stopped state. -.It stop -Playback or recording is stopped, and -the stream is rewound back to the starting position. -.El -.Pp For instance, the following command will play a file on the .Va aucat:0.mmc audio device, and give full control to MIDI software or hardware @@ -656,6 +605,17 @@ devices: .Bd -literal -offset indent $ aucat -l -v 65 -s default -v 127 -s max .Ed +.Pp +The following will start +.Nm +in server mode configuring the audio device to use +48kHz sample frequency, 240-frame block size, +and a 2-block buffers. +The corresponding latency is 10ms, which is +the time it takes the sound to propagate 3.5 meters. +.Bd -literal -offset indent +$ aucat -l -r 48000 -b 480 -z 240 +.Ed .Sh SEE ALSO .Xr audioctl 1 , .Xr cdio 1 , @@ -668,6 +628,11 @@ The utility assumes non-blocking I/O for input and output streams. It will not work reliably on files that may block (ordinary files block, pipes don't). +To avoid audio underruns/overruns or MIDI jitter caused by file I/O, +it's recommended to use two +.Nm +processes: a server handling audio and MIDI I/O and a client handling +disk I/O. .Pp Resampling is low quality; down-sampling especially should be avoided when recording. @@ -675,3 +640,17 @@ when recording. Processing is done using 16-bit arithmetic, thus samples with more than 16 bits are rounded. 16 bits (i.e. 97dB dynamic) are largely enough for most applications though. +.Pp +If +.Fl a Ar off +option is used in server mode, +.Nm +creates sub-devices to expose first and then opens the audio hardware on demand. +Technically, this allows +.Nm +to attempt to use one of the sub-devices it exposes as audio device, +creating a deadlock. +To avoid this, +.Fl a Ar off +is disabled for the default audio device, but nothing prevents the user +from shooting himself in the foot by creating a similar deadlock. diff --git a/usr.bin/aucat/aucat.c b/usr.bin/aucat/aucat.c index 2770ca13fa3..cd88c2f3942 100644 --- a/usr.bin/aucat/aucat.c +++ b/usr.bin/aucat/aucat.c @@ -1,4 +1,4 @@ -/* $OpenBSD: aucat.c,v 1.97 2010/06/29 06:48:39 jakemsr Exp $ */ +/* $OpenBSD: aucat.c,v 1.98 2010/07/06 01:12:45 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org> * @@ -434,6 +434,7 @@ aucat_main(int argc, char **argv) int autostart, legacy; struct dev *d, *dnext; unsigned active; + unsigned nsock, nfile; /* * global options defaults @@ -446,6 +447,7 @@ aucat_main(int argc, char **argv) legacy = 1; legacy_path = NULL; SLIST_INIT(&cfdevs); + nfile = nsock = 0; /* * default stream params @@ -561,14 +563,17 @@ aucat_main(int argc, char **argv) case 'i': legacy = 0; cfstr_add(&cd->ins, cs, optarg); + nfile++; break; case 'o': legacy = 0; cfstr_add(&cd->outs, cs, optarg); + nfile++; break; case 's': legacy = 0; cfstr_add(&cd->opts, cs, optarg); + nsock++; break; case 'a': legacy = 0; @@ -593,6 +598,12 @@ aucat_main(int argc, char **argv) case 'f': legacy = 0; legacy_path = optarg; + if (SLIST_EMPTY(&cd->opts) && + SLIST_EMPTY(&cd->ins) && + SLIST_EMPTY(&cd->outs)) { + cfstr_add(&cd->opts, cs, DEFAULT_OPT); + nsock++; + } cfdev_add(&cfdevs, cd, optarg); break; case 'l': @@ -624,36 +635,37 @@ aucat_main(int argc, char **argv) exit(0); } - if (!l_flag && unit >= 0) - errx(1, "can't use -U without -l"); + /* + * Check constraints specific to -n option + */ if (n_flag) { - if (!SLIST_EMPTY(&cfdevs) || l_flag || - !SLIST_EMPTY(&cd->opts) || !SLIST_EMPTY(&cd->mids)) - errx(1, "can't use -n with -l, -f, -q or -s"); + if (!SLIST_EMPTY(&cfdevs) || + !SLIST_EMPTY(&cd->mids) || + !SLIST_EMPTY(&cd->opts)) + errx(1, "-f, -s, and -q not allowed in loopback mode"); if (SLIST_EMPTY(&cd->ins) || SLIST_EMPTY(&cd->outs)) - errx(1, "both -i and -o are required with -n"); + errx(1, "-i and -o are required in loopback mode"); } /* - * if no device is given, add the default one + * If there's no device specified, do as if the default + * device is specified as last argument. */ if (SLIST_EMPTY(&cfdevs)) { - if (!cd->hold) - errx(1, "-a not compatible with default device"); - cfdev_add(&cfdevs, cd, "default"); - } - - /* - * If there are no sockets paths provided use the default. - */ - if (l_flag) { - SLIST_FOREACH(cd, &cfdevs, entry) { - if (!SLIST_EMPTY(&cd->opts)) - continue; + if (SLIST_EMPTY(&cd->opts) && + SLIST_EMPTY(&cd->ins) && + SLIST_EMPTY(&cd->outs)) { cfstr_add(&cd->opts, cs, DEFAULT_OPT); - break; + nsock++; } + if (!cd->hold) + errx(1, "-a off not compatible with default device"); + cfdev_add(&cfdevs, cd, "default"); } + if ((cs = SLIST_FIRST(&cd->opts)) || + (cs = SLIST_FIRST(&cd->ins)) || + (cs = SLIST_FIRST(&cd->outs))) + errx(1, "%s: no device to attach the stream to", cs->path); /* * Check modes and calculate "best" device parameters. Iterate over all @@ -687,14 +699,10 @@ aucat_main(int argc, char **argv) aparams_grow(&cd->ipar, &cs->opar); } } - if (l_flag && SLIST_EMPTY(&cd->opts)) - errx(1, "%s: no subdevs for this device", cd->path); - if (!l_flag && SLIST_EMPTY(&cd->ins) && SLIST_EMPTY(&cd->outs)) - errx(1, "%s: no files for this device", cd->path); - if (n_flag && (mode & MODE_MON)) - errx(1, "monitoring not allowed in loopback mode"); if ((mode & MODE_MON) && !(mode & MODE_PLAY)) errx(1, "no playback stream to monitor"); + if (n_flag && (mode & MODE_MON)) + errx(1, "-m mon not allowed in loopback mode"); rate = (mode & MODE_REC) ? cd->ipar.rate : cd->opar.rate; if (!cd->round) cd->round = rate / 15; @@ -702,8 +710,7 @@ aucat_main(int argc, char **argv) cd->bufsz = rate / 15 * 4; cd->mode = mode; } - - if (l_flag) { + if (nsock > 0) { getbasepath(base, sizeof(base)); if (unit < 0) unit = 0; @@ -781,15 +788,19 @@ aucat_main(int argc, char **argv) ctl_start(d->midi); } } - if (l_flag) { + if (nsock > 0) { snprintf(path, sizeof(path), "%s/%s%u", base, DEFAULT_SOFTAUDIO, unit); listen = listen_new(&listen_ops, path); - if (listen == 0) + if (listen == NULL) exit(1); - if (geteuid() == 0) - privdrop(); - if (!d_flag && daemon(0, 0) < 0) + } + if (geteuid() == 0) + privdrop(); + if (l_flag) { + debug_level = 0; + dbg_flush(); + if (daemon(0, 0) < 0) err(1, "daemon"); } @@ -809,13 +820,13 @@ aucat_main(int argc, char **argv) } if (dev_list == NULL) break; - if (!l_flag && !active) + if (nsock == 0 && !active) break; if (!file_poll()) break; } fatal: - if (l_flag) + if (nsock > 0) file_close(&listen->file); /* * give a chance to drain @@ -828,7 +839,7 @@ aucat_main(int argc, char **argv) while (dev_list) dev_del(dev_list); filelist_done(); - if (l_flag) { + if (nsock > 0) { if (rmdir(base) < 0 && errno != ENOTEMPTY && errno != EPERM) warn("rmdir(\"%s\")", base); } @@ -847,8 +858,10 @@ midicat_usage(void) int midicat_main(int argc, char **argv) { - struct cfmidlist mids, ins, outs; + struct cfdevlist cfdevs; struct cfmid *cm; + struct cfstr *cs; + struct cfdev *cd; struct listen *listen = NULL; int c, d_flag, l_flag, unit, fd; char base[PATH_MAX], path[PATH_MAX]; @@ -856,7 +869,8 @@ midicat_main(int argc, char **argv) struct aproc *p; struct abuf *buf; const char *str; - struct dev *d; + struct dev *d, *dnext; + unsigned nsock; /* * global options defaults @@ -864,11 +878,35 @@ midicat_main(int argc, char **argv) unit = -1; d_flag = 0; l_flag = 0; - SLIST_INIT(&mids); - SLIST_INIT(&ins); - SLIST_INIT(&outs); + SLIST_INIT(&cfdevs); + nsock = 0; + + /* + * default stream params + */ + cs = malloc(sizeof(struct cfstr)); + if (cs == NULL) { + perror("malloc"); + exit(1); + } + cs->hdr = HDR_RAW; - while ((c = getopt(argc, argv, "di:o:lf:q:U:")) != -1) { + /* + * default device + */ + cd = malloc(sizeof(struct cfdev)); + if (cd == NULL) { + perror("malloc"); + exit(1); + } + SLIST_INIT(&cd->ins); + SLIST_INIT(&cd->outs); + SLIST_INIT(&cd->opts); + SLIST_INIT(&cd->mids); + cd->path = NULL; + + + while ((c = getopt(argc, argv, "di:o:ls:q:U:")) != -1) { switch (c) { case 'd': #ifdef DEBUG @@ -878,15 +916,18 @@ midicat_main(int argc, char **argv) d_flag = 1; break; case 'i': - cfmid_add(&ins, optarg); + cfstr_add(&cd->ins, cs, optarg); break; case 'o': - cfmid_add(&outs, optarg); + cfstr_add(&cd->outs, cs, optarg); break; - /* XXX: backward compat, remove this */ - case 'f': case 'q': - cfmid_add(&mids, optarg); + cfmid_add(&cd->mids, optarg); + break; + case 's': + cfstr_add(&cd->opts, cs, optarg); + cfdev_add(&cfdevs, cd, optarg); + nsock++; break; case 'l': l_flag = 1; @@ -904,15 +945,37 @@ midicat_main(int argc, char **argv) argc -= optind; argv += optind; - if (argc > 0 || (SLIST_EMPTY(&ins) && SLIST_EMPTY(&outs) && !l_flag)) { +#ifdef DEBUG + if (debug_level == 0) + debug_level = 1; +#endif + if (argc > 0) { midicat_usage(); exit(1); } - if (!l_flag && unit >= 0) - errx(1, "can't use -U without -l"); - if (l_flag) { - if (!SLIST_EMPTY(&ins) || !SLIST_EMPTY(&outs)) - errx(1, "can't use -i or -o with -l"); + + /* + * If there's no device specified (-s), then create one with + * reasonable defaults: + * + * - if there are no streams (-ioq) defined, assume server mode + * and expose the "defaut" option + * + * - if there are files (-io) but no ports (-q) to send/receive + * from, add the default sndio(7) MIDI port + */ + if (SLIST_EMPTY(&cfdevs)) { + if (SLIST_EMPTY(&cd->mids)) { + if (SLIST_EMPTY(&cd->ins) && SLIST_EMPTY(&cd->outs)) + cfmid_add(&cd->mids, "default"); + else { + cfstr_add(&cd->opts, cs, DEFAULT_OPT); + nsock++; + } + } + cfdev_add(&cfdevs, cd, "default"); + } + if (nsock > 0) { getbasepath(base, sizeof(path)); if (unit < 0) unit = 0; @@ -920,95 +983,124 @@ midicat_main(int argc, char **argv) setsig(); filelist_init(); - d = dev_new_thru(); - if (!dev_ref(d)) - errx(1, "couldn't open midi thru box"); - if (!l_flag && APROC_OK(d->midi)) - d->midi->flags |= APROC_QUIT; - if ((!SLIST_EMPTY(&ins) || !SLIST_EMPTY(&outs)) && SLIST_EMPTY(&mids)) { - cfmid_add(&mids, "default"); - } - while (!SLIST_EMPTY(&mids)) { - cm = SLIST_FIRST(&mids); - SLIST_REMOVE_HEAD(&mids, entry); - if (!dev_thruadd(d, cm->path, - !SLIST_EMPTY(&outs) || l_flag, - !SLIST_EMPTY(&ins) || l_flag)) { - errx(1, "%s: can't open device", cm->path); + while (!SLIST_EMPTY(&cfdevs)) { + cd = SLIST_FIRST(&cfdevs); + SLIST_REMOVE_HEAD(&cfdevs, entry); + + d = dev_new_thru(); + if (d == NULL) + errx(1, "%s: can't open device", cd->path); + if (!dev_ref(d)) + errx(1, "couldn't open midi thru box"); + if (!SLIST_EMPTY(&cd->opts) && APROC_OK(d->midi)) + d->midi->flags |= APROC_QUIT; + + /* + * register midi ports + */ + while (!SLIST_EMPTY(&cd->mids)) { + cm = SLIST_FIRST(&cd->mids); + SLIST_REMOVE_HEAD(&cd->mids, entry); + if (!dev_thruadd(d, cm->path, 1, 1)) + errx(1, "%s: can't open device", cm->path); + free(cm); } - free(cm); + + /* + * register files + */ + while (!SLIST_EMPTY(&cd->ins)) { + cs = SLIST_FIRST(&cd->ins); + SLIST_REMOVE_HEAD(&cd->ins, entry); + if (strcmp(cs->path, "-") == 0) { + fd = STDIN_FILENO; + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) + warn("stdin"); + } else { + fd = open(cs->path, O_RDONLY | O_NONBLOCK, 0666); + if (fd < 0) + err(1, "%s", cs->path); + } + stdx = (struct file *)pipe_new(&pipe_ops, fd, cs->path); + p = rfile_new(stdx); + buf = abuf_new(MIDI_BUFSZ, &aparams_none); + aproc_setout(p, buf); + dev_midiattach(d, buf, NULL); + free(cs); + } + while (!SLIST_EMPTY(&cd->outs)) { + cs = SLIST_FIRST(&cd->outs); + SLIST_REMOVE_HEAD(&cd->outs, entry); + if (strcmp(cs->path, "-") == 0) { + fd = STDOUT_FILENO; + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) + warn("stdout"); + } else { + fd = open(cs->path, + O_WRONLY | O_TRUNC | O_CREAT | O_NONBLOCK, 0666); + if (fd < 0) + err(1, "%s", cs->path); + } + stdx = (struct file *)pipe_new(&pipe_ops, fd, cs->path); + p = wfile_new(stdx); + buf = abuf_new(MIDI_BUFSZ, &aparams_none); + aproc_setin(p, buf); + dev_midiattach(d, NULL, buf); + free(cs); + } + while (!SLIST_EMPTY(&cd->opts)) { + cs = SLIST_FIRST(&cd->opts); + SLIST_REMOVE_HEAD(&cd->opts, entry); + opt_new(cs->path, d, NULL, NULL, 0, 0, 0, 0); + free(cs); + } + free(cd); } - if (l_flag) { - opt_new(DEFAULT_OPT, d, NULL, NULL, 0, 0, 0, 0); + if (nsock > 0) { snprintf(path, sizeof(path), "%s/%s%u", base, DEFAULT_MIDITHRU, unit); listen = listen_new(&listen_ops, path); - if (geteuid() == 0) - privdrop(); - if (!d_flag && daemon(0, 0) < 0) - err(1, "daemon"); - } - while (!SLIST_EMPTY(&ins)) { - cm = SLIST_FIRST(&ins); - SLIST_REMOVE_HEAD(&ins, entry); - if (strcmp(cm->path, "-") == 0) { - fd = STDIN_FILENO; - if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) - warn("stdin"); - } else { - fd = open(cm->path, O_RDONLY | O_NONBLOCK, 0666); - if (fd < 0) - err(1, "%s", cm->path); - } - stdx = (struct file *)pipe_new(&pipe_ops, fd, cm->path); - p = rfile_new(stdx); - buf = abuf_new(MIDI_BUFSZ, &aparams_none); - aproc_setout(p, buf); - dev_midiattach(d, buf, NULL); - free(cm); + if (listen == NULL) + exit(1); } - while (!SLIST_EMPTY(&outs)) { - cm = SLIST_FIRST(&outs); - SLIST_REMOVE_HEAD(&outs, entry); - if (strcmp(cm->path, "-") == 0) { - fd = STDOUT_FILENO; - if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) - warn("stdout"); - } else { - fd = open(cm->path, - O_WRONLY | O_TRUNC | O_CREAT | O_NONBLOCK, 0666); - if (fd < 0) - err(1, "%s", cm->path); - } - stdx = (struct file *)pipe_new(&pipe_ops, fd, cm->path); - p = wfile_new(stdx); - buf = abuf_new(MIDI_BUFSZ, &aparams_none); - aproc_setin(p, buf); - dev_midiattach(d, NULL, buf); - free(cm); + if (geteuid() == 0) + privdrop(); + if (l_flag) { + debug_level = 0; + dbg_flush(); + if (daemon(0, 0) < 0) + err(1, "daemon"); } + /* * loop, start processing */ for (;;) { if (quit_flag) break; - if (!dev_run(d)) - break; + for (d = dev_list; d != NULL; d = dnext) { + dnext = d->next; + if (!dev_run(d)) + goto fatal; + } if (!file_poll()) break; } - if (l_flag) + fatal: + if (nsock > 0) file_close(&listen->file); - dev_unref(d); - dev_del(d); /* - * drain + * give a chance to drain */ + for (d = dev_list; d != NULL; d = d->next) + dev_drain(d); while (file_poll()) ; /* nothing */ + + while (dev_list) + dev_del(dev_list); filelist_done(); - if (l_flag) { + if (nsock > 0) { if (rmdir(base) < 0 && errno != ENOTEMPTY && errno != EPERM) warn("rmdir(\"%s\")", base); } diff --git a/usr.bin/aucat/dev.c b/usr.bin/aucat/dev.c index c32dca7cf6a..4ef56595c76 100644 --- a/usr.bin/aucat/dev.c +++ b/usr.bin/aucat/dev.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dev.c,v 1.61 2010/06/29 06:57:00 jakemsr Exp $ */ +/* $OpenBSD: dev.c,v 1.62 2010/07/06 01:12:45 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org> * @@ -169,6 +169,7 @@ dev_new_loop(struct aparams *dipar, struct aparams *dopar, unsigned bufsz) d->reqmode = MODE_PLAY | MODE_REC | MODE_LOOP; d->pstate = DEV_CLOSED; d->hold = 0; + d->path = "loop"; d->next = dev_list; dev_list = d; return d; @@ -190,6 +191,7 @@ dev_new_thru(void) d->reqmode = 0; d->pstate = DEV_CLOSED; d->hold = 0; + d->path = "midithru"; d->next = dev_list; dev_list = d; return d; @@ -237,7 +239,7 @@ dev_open(struct dev *d) if (f == NULL) { #ifdef DEBUG if (debug_level >= 1) { - dbg_puts(d->path ? d->path : "default"); + dbg_puts(d->path); dbg_puts(": failed to open audio device\n"); } #endif @@ -250,7 +252,7 @@ dev_open(struct dev *d) if ((d->mode & (MODE_PLAY | MODE_REC)) == 0) { #ifdef DEBUG if (debug_level >= 1) { - dbg_puts(d->path ? d->path : "default"); + dbg_puts(d->path); dbg_puts(": mode not supported by device\n"); } #endif @@ -260,12 +262,14 @@ dev_open(struct dev *d) #ifdef DEBUG if (debug_level >= 2) { if (d->mode & MODE_REC) { - dbg_puts("hw recording "); + dbg_puts(d->path); + dbg_puts(": recording "); aparams_dbg(&d->ipar); dbg_puts("\n"); } if (d->mode & MODE_PLAY) { - dbg_puts("hw playing "); + dbg_puts(d->path); + dbg_puts(": playing "); aparams_dbg(&d->opar); dbg_puts("\n"); } @@ -381,7 +385,8 @@ dev_open(struct dev *d) #ifdef DEBUG if (debug_level >= 2) { if (d->mode & (MODE_PLAY | MODE_RECMASK)) { - dbg_puts("device block size is "); + dbg_puts(d->path); + dbg_puts(": block size is "); dbg_putu(d->round); dbg_puts(" frames, using "); dbg_putu(d->bufsz / d->round); diff --git a/usr.bin/aucat/dev.h b/usr.bin/aucat/dev.h index ca34ea0cc5b..58ad1bd0553 100644 --- a/usr.bin/aucat/dev.h +++ b/usr.bin/aucat/dev.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dev.h,v 1.26 2010/06/25 07:32:05 ratchov Exp $ */ +/* $OpenBSD: dev.h,v 1.27 2010/07/06 01:12:45 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org> * @@ -17,8 +17,9 @@ #ifndef DEV_H #define DEV_H +#include "aparams.h" + struct aproc; -struct aparams; struct abuf; struct dev { diff --git a/usr.bin/aucat/midi.c b/usr.bin/aucat/midi.c index 85814906e95..aa2b6884a3f 100644 --- a/usr.bin/aucat/midi.c +++ b/usr.bin/aucat/midi.c @@ -1,4 +1,4 @@ -/* $OpenBSD: midi.c,v 1.27 2010/06/05 16:05:17 ratchov Exp $ */ +/* $OpenBSD: midi.c,v 1.28 2010/07/06 01:12:45 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org> * @@ -95,7 +95,7 @@ thru_flush(struct aproc *p, struct abuf *ibuf, struct abuf *obuf) while (itodo > 0) { if (!ABUF_WOK(obuf)) { #ifdef DEBUG - if (debug_level >= 4) { + if (debug_level >= 3) { aproc_dbg(p); dbg_puts(": overrun, discarding "); dbg_putu(obuf->used); @@ -140,7 +140,7 @@ thru_rt(struct aproc *p, struct abuf *ibuf, struct abuf *obuf, unsigned c) return; if (!ABUF_WOK(obuf)) { #ifdef DEBUG - if (debug_level >= 4) { + if (debug_level >= 3) { aproc_dbg(p); dbg_puts(": overrun, discarding "); dbg_putu(obuf->used); @@ -400,7 +400,7 @@ ctl_sendmsg(struct aproc *p, struct abuf *ibuf, unsigned char *msg, unsigned len while (itodo > 0) { if (!ABUF_WOK(i)) { #ifdef DEBUG - if (debug_level >= 4) { + if (debug_level >= 3) { abuf_dbg(i); dbg_puts(": overrun, discarding "); dbg_putu(i->used); diff --git a/usr.bin/aucat/midicat.1 b/usr.bin/aucat/midicat.1 index 7db3c7539e0..968e5d1421f 100644 --- a/usr.bin/aucat/midicat.1 +++ b/usr.bin/aucat/midicat.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: midicat.1,v 1.11 2010/04/22 18:27:19 jmc Exp $ +.\" $OpenBSD: midicat.1,v 1.12 2010/07/06 01:12:45 ratchov Exp $ .\" .\" Copyright (c) 2006 Alexandre Ratchov <alex@caoua.org> .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: April 22 2010 $ +.Dd $Mdocdate: July 6 2010 $ .Dt MIDICAT 1 .Os .Sh NAME @@ -25,58 +25,93 @@ .Op Fl dl .Op Fl i Ar file .Op Fl o Ar file -.Op Fl q Ar device +.Op Fl q Ar port +.Op Fl s Ar name .Op Fl U Ar unit .Sh DESCRIPTION The .Nm -utility is used to manipulate MIDI data. -It can send and receive MIDI data from MIDI ports, -or it can create software MIDI thru boxes, -allowing any MIDI-capable application to -send MIDI messages to MIDI hardware -or to another application in a uniform way. +utility can merge any number of MIDI inputs and broadcast the result +to any number of MIDI outputs, similarly to a hardware MIDI thru box. +MIDI streams are typically MIDI ports or plain files containing raw MIDI +data. +.Pp +The +.Nm +utility can also act as a MIDI server in which case MIDI streams +correspond to client connections. +The server capability +allows any MIDI-capable application to send MIDI messages to +MIDI hardware or to another application in a uniform way. .Pp The options are as follows: .Bl -tag -width Ds .It Fl d -Do not daemonize. -If this option is specified, +Increase log verbosity. .Nm -will run in the foreground and log to -.Em stderr . +logs on stderr until it daemonizes. .It Fl i Ar file -Send contents of this file to the device. +Read data to send from this file. If the option argument is .Sq - then standard input will be used. .It Fl l -Listen for incoming connections on -.Ux Ns -domain -sockets. -This allows clients to use -.Nm -instead of the regular -.Xr midi 4 -device as a software MIDI thru box. +Detach and become a daemon. .It Fl o Ar file -Store received data from the device into this file. +Write received data into this file. If the option argument is .Sq - then standard output will be used. .It Fl q Ar device -The +Send and receive data from this +.Xr sndio 7 +MIDI port. +.It Fl s Ar name +Expose a MIDI thru box to which MIDI programs +can connect. +Preceding streams +.Pq Fl ioq +are subscribed to this thru box. +The given +.Ar name +corresponds to the +.Dq option +part of the .Xr sndio 7 -MIDI device to use for MIDI input/output. -In server mode, devices are subscribed to the MIDI thru box. +device name string. .It Fl U Ar unit -Use the given unit number when creating a software MIDI thru box. -Only one +Unit number to use when running in server mode. +Each .Nm -process can expose a unit number at a given time. +server instance has an unique unit number, +used in +.Xr sndio 7 +device names. The default is 0. .El .Pp +If files +.Pq Fl io +are specified but no ports +.Pq Fl q +are specified, the default +.Xr sndio 7 +port is used. +If no streams +.Pq Fl ioq +are specified, server mode is assumed and a thru box is created +as if +.Fl s Ar default +was used as last argument. +.Pp +Generally MIDI applications are real-time. +To reduce jitter, especially on busy machines, +the server can be started by the super-user, +in which case it will run with higher priority. +Any user will still be able to connect to it, but +for privacy reasons, only one user may have connections to +it at a given time. +.Pp If .Nm is sent @@ -85,52 +120,47 @@ is sent or .Dv SIGTERM , then processing terminates. -.Sh SERVER MODE -.Nm -can be used in server mode -.Pq Fl l -to create MIDI thru boxes. -A MIDI thru box allows multiple receivers -to receive data from a single source. -Additionaly, -.Nm -software thru boxes allow multiple sources to be connected -to them; in this case MIDI byte-streams are merged, -preserving MIDI message integrity. -This feature is provided to allow multiple applications -acting as sources to keep their connection open while -idling; it does not replace a fully featured MIDI merger. +.Sh EXAMPLES +The following dumps MIDI data received from the default port: +.Bd -literal -offset indent +$ midicat -o - | hexdump -e '1/1 "%x"' +.Ed .Pp -A -.Nm -process may be started by the super-user, -in which case any user will be able to connect to it. -For privacy reasons, only one user may have connections to -it at a given time. +The following sends raw MIDI data to the +.Pa rmidi:5 +port: +.Bd -literal -offset indent +$ midicat -i sysexfile -q rmidi:5 +.Ed .Pp -Alternatively, each user may start its own -.Nm -process. -It is generally not desirable to have multiple instances of -.Nm -running in server mode, so it is good practice to start it thus: +The following connects +.Pa rmidi:5 +and +.Pa rmidi:6 +ports: .Bd -literal -offset indent -$ pgrep -x midicat || midicat -l +$ midicat -q rmidi:5 -q rmidi:6 .Ed .Pp -Generally MIDI applications are real-time. -To reduce jitter, especially on busy machines, the -.Xr renice 8 -command can be used to give a higher priority to the -.Nm -process. -Superuser privileges are required. -For example: +The following creates a MIDI thru box and daemonizes, +allowing MIDI programs to send data to each other instead of +using hardware MIDI ports: .Bd -literal -offset indent $ midicat -l -$ sudo renice -n -20 -p `pgrep -x midicat` +.Ed +.Pp +The following creates a MIDI thru box and subscribes the +.Pa rmidi:5 +port, allowing multiple MIDI programs to use the port +simultaneously: +.Bd -literal -offset indent +$ midicat -q rmidi:5 -s default .Ed .Sh SEE ALSO .Xr aucat 1 , .Xr midi 4 , .Xr sndio 7 +.Sh BUGS +The ability to merge multiple inputs is provided to allow multiple +applications producing MIDI data to keep their connection open while +idling; it does not replace a fully featured MIDI merger. diff --git a/usr.bin/aucat/opt.c b/usr.bin/aucat/opt.c index 6ab06ce677c..4e14b81e391 100644 --- a/usr.bin/aucat/opt.c +++ b/usr.bin/aucat/opt.c @@ -1,4 +1,4 @@ -/* $OpenBSD: opt.c,v 1.9 2010/06/04 06:15:28 ratchov Exp $ */ +/* $OpenBSD: opt.c,v 1.10 2010/07/06 01:12:45 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org> * @@ -18,6 +18,7 @@ #include <stdlib.h> #include <string.h> +#include "dev.h" #include "conf.h" #include "opt.h" #ifdef DEBUG @@ -72,6 +73,8 @@ opt_new(char *name, struct dev *d, struct aparams *wpar, struct aparams *rpar, #ifdef DEBUG if (debug_level >= 2) { dbg_puts(o->name); + dbg_puts("@"); + dbg_puts(o->dev->path); dbg_puts(":"); if (mode & MODE_REC) { dbg_puts(" rec="); |