Repository navigation
Conversation
e091cc0 to
f9e87ad
Compare
A bus's forward_node scaled its output by pan and gain, then
audio_outlet::post_process scaled the same outlet again: a bus at 0.5
played at 0.25, and a stereo pan of {0.5, 1} came out as {0.25, 1}. The
node now only copies, and post_process is the one place gain and pan are
applied, for every audio outlet. has_gain no longer means anything and is
gone, along with the two helpers that scaled while copying.
Buses set below unity in existing sessions will sound louder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX
The gain of /out/main scaled only what was written to /out/main, one writer at a time. It is now a post-sum trim: audio_protocol::apply_main_gain scales every hardware output once the tick has been summed into them, so signals sent straight to /out/N follow the master too. A change ramps over the buffer. Each audio_parameter now says where its gain applies: on what the graph writes (outputs, virtual ports), on what it reads (inputs, whose gain was ignored until now), or outside, after the sum (/out/main). The gain itself is an atomic float, since the UI writes it while the audio thread reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX
A gain or pan change now ramps linearly over one buffer instead of stepping, which clicked. The first buffer an outlet carries is scaled directly: there is nothing to ramp from. Pan ramps on the first 16 channels; past those, a change steps. Channels past the end of the pan weights have a weight of 1. They used to get one pushed onto the vector on the audio thread, which allocated as soon as a signal had more than two channels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX
ossia::telemetry::arena gathers informative data from the execution into one frame at fixed slots, handed to the interface through a triple buffer. Every metering point accumulates peak, sum of squares and clips on its own writer thread; the tick publishes once per period. A frame the interface has not read yet is folded into the next one, so no peak is lost whatever the reading rate, and nothing is allocated or locked on the audio thread. An audio outlet can carry a tap, fed after its gain and pan. The hardware inputs and outputs have taps of their own, fed from the driver's buffers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX
Every program with at most one input and one output runs as a mono effect, one clone per incoming channel. A generator has no input, so with nothing cabled into it it made no channel at all: `process = os.osc(440);` in a box was silent. It now makes one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX
It hands the graph silent inputs and discards its outputs, so that a session without a sound card can still be routed and metered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX
The interface sets the address of a tap that the telemetry arena owns; what the graph writes to the port is accumulated into it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX
The local state keeps an entry for every parameter it ever wrote to and writes to all of them at each commit, so removing a port that a process had written to crashed the next tick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX
audio_engine::run_between_ticks runs a short change on the audio thread at the start of the next callback and waits for it; run_parked, which skipped callbacks, is gone. The audio protocol can defer port changes: its callback walks its own lists, swapped in between two ticks, and a virtual port's channels are made off the audio thread and swapped the same way. The execution state no longer writes to a parameter nothing wrote to during the tick: its entries outlive their parameters. execution_state::forget is removed, as it is not needed any more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX
- triple_buffer: the trivially copyable case has the in-place interface too, so that tuples of floats (tuplet) keep working; publish_if_unread replaces a value only if nobody read it. - telemetry: a frame the interface reads while the next one is being made is no longer delivered twice: the new frame carries the unread levels only if it replaces an unread frame. Frames are written in place, and only the channels in use are copied. RMS is computed per channel, over the frames that channel was there for, in double precision. - audio_protocol: reset_main_gain_ramp, so that a run starts at the master gain instead of ramping from the one of the previous run. - faust: a mono generator with a signal plugged into its inlet makes one channel per incoming channel again; with nothing plugged in, one. - graph: perf_map is called bench, which it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX
f9e87ad to
7e4f52c
Compare
…hes it /out/N applied its gain to what was written to it, before the sum. Sound sent to /out/main or to a mapped port reached the channel without it, so a mixer's output fader did nothing to most of what its channel played. It is now applied with /out/main's, once the tick is summed (gain_stage::external), ramped over the buffer like the master. /in/N likewise only scaled what read /in/N. It now also trims its channel for /in/main and for the mapped input ports: audio_parameter::read_gain gives the gain of each channel a parameter reads, and the mixer meters with it. A mapped port's own gain still applies to what goes through that port only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lc7tYGHRn6TmaxMEfBTswW
|
Hi JM, I pushed 51236ae to this branch, after Edu tested the mixer and decided how the hardware faders should behave. What Edu saw: with sound sent to What changed:
Tests: The score side is ossia/score#2302 08e951e4e1: the per-channel input meter, the help text of the Inputs/Outputs tabs, and the submodule bump. If you'd rather have this as a separate PR on top of #950, tell us and I'll split it out. — Edu's Claude assistant |
The libossia side of the ossia score mixer (ossia/score
feature/mixer). 18 commits, each with its test where one applies.Gain chain
IntervalGainTest).forward_nodenow forwards, and the outlet applies gain and pan once.has_gainis gone.audio_parameterapplies its gain at exactly one point: when the graph writes to it (outputs, virtual ports), when the graph reads it (inputs), or after the whole mix (/out/main). The gain is atomic./out/mainis the master.audio_protocol::apply_main_gaintrims every hardware output once the tick is summed, including what was sent straight to/out/N.reset_main_gain_rampmakes a new run start at the master gain./in/mainscales every input read.audio_outlet::post_process.panis never grown on the audio thread.Telemetry: execution → interface
ossia/dataflow/telemetry.{hpp,cpp}adds an arena of meter, bench and playhead taps.triple_buffer::publish_if_unread(a CAS) makes sure a frame read meanwhile is not delivered twice.Taps sit on audio outlets (after gain and pan), audio inlets, the hardware inputs and outputs, virtual audio parameters, and interval playheads. Node timing goes to bench taps instead of the
bench_mapthat was copied on the audio thread (perf_mapis nowbench).triple_buffergainswrite_buffer/publishandconsume()/read_bufferfor in-place use, in both the general and the trivially copyable case.Real-time safety
audio_engine::run_between_ticks(f)runs a short, allocation-free change at the start of the next callback and waits for it. No callback is skipped. When no callback comes, it runsfon the caller.defer_port_changes(true), the callback walks its own copy of the mapped and virtual port lists, whichswap_portshands over between two ticks.virtual_audio_parameter::make_channels/swap_channelsresize a virtual port the same way.faust_ui_values) instead of the interface reading the DSP's zones. A mono generator makes one channel with nothing plugged in, and follows the channels of a cable plugged into it.Tests
New:
TelemetryTest,TripleBufferTest,MainGainTest,IntervalGainTest,AudioEngineBetweenTicksTest. Also passing:OutletUnitTest,DataflowTest,SoundTest.🤖 Generated with Claude Code
https://claude.ai/code/session_01HKbSZft9tvQ2sm9KeSchMX