The default open harness counted SDL_QueueAudio calls by target and no
longer required the device id, so a queue to the wrong device passed in
every mode. Keep the requirement; it is lifted only while the disconnect
mode has removed the default output, where the counts check the target.
No existing mode queues audio, so their results are unchanged.
AudioOut.cpp resets a port with `port = Port{}`, which copy-list-initializes
its HostGainRamp member. With the default argument on the explicit
constructor, clang rejects that ("chosen constructor is explicit in
copy-initialization"), so libSceAudioOut does not build on macOS. The default
constructor is now separate and implicit; the one taking the ramp length
stays explicit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Bring the branch up to the current upstream main. The only conflict was the
AudioOut debt doc, where both sides added a Functional entry; both are kept.
The output path now asks SDL for the status of a port's device. The port
lock and resampling harnesses link the shared library with fake device
IDs, and without a stub the status query reaches the real SDL, which
reports an unknown ID as stopped. The port then reopened its device and
the harnesses' expectations about the calls they see broke.
Both harnesses answer the query as playing, as the other AudioOut
harnesses that stub the device functions do.
The volume, mute and focus state that libSceAudioOut applies and libSceVideoOut changes now lives in libScePad, which both libraries already link, instead of in libSceAudioOut. The tests that compile the state directly and the environment reference follow the file.
libSceVideoOut reads and changes the shared host volume state, and linking libSceAudioOut for it puts a second shared library with its own static SDL2 into libSceVideoOut. Both libraries already link libScePad, so the headers that describe that state move there first; the include paths and guards follow.
They are called across libraries like the other _nid_no_patch and _nid_postfix helpers, so their symbols must not depend on the C++ mangling of a particular compiler.
DeviceCallback applies the context's ramp to the stereo float it just filled, which is the only place the AudioOut2 main mix reaches the speakers; the title's own volumes and the mastering stay before it. At the default volume the ramp is at unity and the call returns without touching the stream. The controller sound card keeps its own path and is not touched.
Every non-vibration port goes through HostGainFilter just before SDL_QueueAudio, after the title's volume and channel order. With the default volume the filter returns the title's buffer untouched. Vibration ports go to the controller's sound card and are not touched.
Register audio_out_host_volume_state. Covers the unity default, each command publishing its gain, focus acting only with the option on, initial values, a refused configuration leaving the state alone, both variables read from the environment with the invalid one named, and readers never seeing a gain above unity while commands run.
The state sits behind a mutex and publishes its gain through an atomic, so the audio threads read it without locking. HostVolumeConfigure replaces the state, parsing ANYPS5_VOLUME and ANYPS5_MUTE_ON_FOCUS_LOSS before changing anything, so an invalid value leaves it untouched and throws an error naming the variable.
Accepts 0 and 1 for the flag and 0 to 100 for the level; refuses empty, padded, signed, fractional, out-of-range and overflowing values with the variable named.
A flag is exactly 0 or 1 and a level a whole number from 0 to 100. Anything else, including an empty value, throws an error that names the variable and quotes the value.
At unity the title's pointer comes back and nothing is copied, a changed gain works on a copy and leaves the title's buffer as it was, and the scratch buffer is processed in place.
HostGainFilter returns the title's own pointer when the ramp is at unity with a unity target, so the default path copies nothing and multiplies nothing. Otherwise it applies the ramp to a scratch copy, or in place when the data already is the scratch buffer, and never writes the title's memory.
Covers the byte-for-byte bypass at unity, the first sample and landing frame matching the 16-bit ramp, no step above the slope, a ramp split across buffers, and the absence of clamping.
The float overload uses the same per-frame gain as the 16-bit one, converted exactly from the fixed-point value, and does not clamp. At unity with a unity target it touches nothing.
Register audio_out_host_gain. Covers the byte-for-byte bypass at unity, the exact first sample and landing frame of a ramp, no step larger than the ramp slope, a ramp split across buffers equal to one buffer, saturation above unity, one gain per frame on every channel, a silent gain, and the return to bypass.
HostGainRamp moves a 16.16 fixed-point gain toward its target by a constant step per frame, so the ramp is exact, continues across buffers and applies one gain to every channel of a frame. 16-bit samples are rounded and saturate. At unity with a unity target nothing is touched.
Covers the option off, a focus loss and gain with it on, the user's own mute and volume surviving a round trip, and the option changing while the window is unfocused.
With the option on, a window without focus counts as muted and gaining focus ends it. The focus mute is derived from the focus and the option, so it never replaces the user's own mute. With the option off, focus changes change nothing.
The vibration harness gains a disconnect mode. It opens a vibration port
on the controller sound card, reports that card as stopped, and writes
one buffer. The output must open the card again, queue the PCM into the
new device and never into the stopped one, then close both devices with
the port.
On a tree without the reopen the mode fails at "the stopped controller
sound card was not reopened".
A vibration port plays through the controller's own sound card, such as
a DualSense over Bluetooth. When the backend disables that device, SDL
keeps the port's device disabled until it is closed (see the default
output fix), so the port's haptic audio stopped for the rest of the run.
Before a vibration port's device is used, close it and open the
controller sound card again when its status is stopped. A failed reopen
leaves the port with no device, so its output runs as a virtual port,
as a vibration port opened with no controller card does today. Such a
port is not retried later.
The default-open harness gains a disconnect mode. It opens the main port
on the default output, reports that device as stopped, and writes one
buffer. The output must open a replacement device, queue into it and
never queue into the stopped one, then close both devices with the port.
On a tree without the reopen the mode fails at "the removed default
output was not replaced".
SDL marks an opened device as disabled when the backend reports it lost
(SDL_OpenedAudioDeviceDisconnected in src/audio/SDL_audio.c of the
bundled 3rdparty/SDL2 at 4b69833bc). The flag is set back only when a
device is opened again, so a disabled device stays silent until it is
closed, and SDL_GetAudioDeviceStatus reports SDL_AUDIO_STOPPED.
AudioOut never checked that status for a port's default output, so
after the output went away the title's audio stayed silent for the rest
of its run while every output call still returned success.
Before a default-output port is used, reopen its device when its status
is stopped. A failed reopen leaves the port with no device, so it runs
as a virtual port, as a port opened with no output does today. Such a
port is not retried later.