It turns out to be really easy to route all your audio through a compressor in web audio: You just create a DynamicCompressorNode, connect it to the context destination, and have everything else go connect to the compressor node, even though MDN says it takes only one input.
Unfortunately, it doesn't solve the problem I had, but it's good to know for the future.