Remote usage of audio-placed-player.rkt

This commit is contained in:
2026-06-08 15:46:27 +02:00
parent 17846e068c
commit 6ed566c6cd
9 changed files with 624 additions and 91 deletions
+60 -16
View File
@@ -5,6 +5,8 @@
racket/contract
racket/place
racket/async-channel
uni-channel
port-channel
"../audio-placed-player.rkt"
"../audio-player.rkt"))
@@ -30,9 +32,11 @@ The placed player is implemented as a command loop around a decoder, an
asynchronous libao output handle, and a small amount of state that is reported
back to the controlling side. In normal use it runs in a Racket place, so that
the audio side has a separate Racket VM. The same function can also run in a
normal Racket thread with async channels. That mode is useful for debugging,
because the player then stays in the same process and can be inspected more
easily.
normal Racket thread with async channels. Both modes are mediated through
@racketmodname[uni-channel]: each of the existing logical channels is wrapped as
a uni-channel endpoint, so the worker no longer depends directly on
@racket[place-channel-put], @racket[place-channel-get],
@racket[async-channel-put], or @racket[async-channel-get].
It is normally run in a separate place so that audio decoding and feeding are
isolated from scheduling delays in the main Racket VM, such as GUI activity,
@@ -40,22 +44,58 @@ debugging, or interaction with DrRacket.
@section{Interface}
@defproc[(placed-player [ch-in (or/c place-channel? async-channel?)]) void?]{
Runs the placed-player command loop on @racket[ch-in]. The channel may be a
place channel or an async channel. The command loop receives list commands,
@defproc[(placed-player
[ch-in (or/c uni-channel? place-channel? async-channel? port-channel?)]
[ch-out (or/c #f uni-channel? place-channel? async-channel? port-channel?) #f]
[ch-evt (or/c #f uni-channel? place-channel? async-channel? port-channel?) #f])
void?]{
Runs the placed-player command loop on @racket[ch-in]. Each channel may already
be a @racket[uni-channel?] or may be a supported raw channel that can be wrapped
with @racket[make-uni-channel]. The command loop receives list commands,
initializes its reply and event channels, and then processes playback commands
until it receives @racket['quit].
The function is designed to be started either by @racket[dynamic-place] or by
@racket[thread]. In place mode, all three channels are place channels. In
thread mode, all three channels are async channels. The implementation detects
the kind of channel and uses @racket[place-channel-put],
@racket[place-channel-get], @racket[async-channel-put], or
@racket[async-channel-get] as appropriate.}
When @racket[ch-out] and @racket[ch-evt] are @racket[#f], the command loop
expects an initial @racket['init] command containing the raw reply and event
channels. This remains the normal path for @racket[dynamic-place], because a
@racket[uni-channel] value contains procedures and must not be sent through a
place channel. In that case the controlling side sends the raw place channels
and the worker wraps them locally.
When all three channels are supplied, the worker starts initialized. This is
used by the standard-port worker entry point and can also be used by custom
launchers.}
@defproc[(placed-player/stdio [#:log-file log-file (or/c #f path-string?) (racket-sound-log-file 'placed-audio-player-stdio)])
void?]{
Runs @racket[placed-player] using the process standard streams as three logical
channels: @racket[current-input-port] is the command channel,
@racket[current-output-port] is the reply channel, and
@racket[current-error-port] is the asynchronous event channel. The streams are
wrapped through @racket[make-port-channel] and then through
@racket[make-uni-channel].
Because stdout and stderr are protocol streams in this mode, ordinary display
and log output is redirected while the worker is running. By default, output is
appended to a log file under Racket's standard cache directory, for example
@filepath{~/.cache/racket/racket-audio/placed-audio-player-stdio.log} on many
Unix-like systems. Pass @racket[#f] explicitly to discard ordinary output, or
pass a path to choose a different log file.
The module also has a command-line entry point. A remote or local subprocess
worker can be started with:
@verbatim{
racket -l racket-audio/audio-placed-player -- --stdio
}
This is the command used by @racket[make-audio-player] when SSH remote playback
is enabled, unless the caller supplies a custom remote command.}
The public wrapper in @racketmodname[racket-audio/audio-player] creates the channels,
sends the initial @racket['init] command, starts an event thread, and exposes a
contracted API. The placed player itself only exports @racket[placed-player].
sends the initial @racket['init] command when needed, starts an event thread, and exposes a
contracted API. The placed player exports @racket[placed-player] and the
standard-port worker entry point @racket[placed-player/stdio].
@section{Overall state model}
@@ -91,7 +131,10 @@ installed.
The controlling side sends commands as lists on @racket[ch-in]. The result of
an RPC-style command is sent on the reply channel installed by
@racket['init]. Asynchronous events are sent on the event channel.
@racket['init] or supplied directly to @racket[placed-player]. Asynchronous
events are sent on the event channel. These are logical channels; the concrete
transport may be place channels, async channels, or port channels wrapped as
uni-channels.
@itemlist[#:style 'compact
@item{@racket[(list 'init ch-out ch-evt)] installs @racket[ch-out] and
@@ -252,7 +295,8 @@ place or thread then terminates.
@section{Running in a place or in a thread}
The normal path in @racket[make-audio-player] uses @racket[dynamic-place] when
places are enabled. This gives the audio side its own Racket VM and isolates
places are enabled. The command, reply, and event channels are wrapped with
@racket[make-uni-channel] on each side. This gives the audio side its own Racket VM and isolates
it from the main controller, while the command and event protocol stays the
same.