While refactoring some of our in house dsp codebase that heavily relies on dsp::ProcessorChain etc, I came up with this class:

/** A process context type, suited wherever you want your processor to not
    mutate the samples passed in but to only read them.
    This is particularly useful for analyzers etc. A ProcessContextReplacing
    is implicitly convertible into a ProcessContextReadOnly, which makes it
    suitable as a process context type for any processor that is intended to 
    be held in a ProcessorChain
template <typename ContextSampleType>
struct ProcessContextReadOnly
    /** The type of a single sample (which may be a vector if multichannel). */
    using SampleType          = const ContextSampleType;
    /** The type of audio block that this context handles. */
    using AudioBlockType      = juce::dsp::AudioBlock<SampleType>;
    using ConstAudioBlockType = juce::dsp::AudioBlock<SampleType>;

    /** Creates a ProcessContextReadOnly that uses the given audio block.
        Note that the caller must not delete the block while it is still in use by this object!
    ProcessContextReadOnly (const juce::dsp::AudioBlock<const ContextSampleType>& block) noexcept : constBlock (block) {}
    ProcessContextReadOnly (const juce::dsp::AudioBlock<ContextSampleType>&       block) noexcept : constBlock (block) {}

    ProcessContextReadOnly (const ProcessContextReadOnly&) = default;
    ProcessContextReadOnly (ProcessContextReadOnly&&) = default;

    /** A replacing context can always be converted into a read-only one */
    ProcessContextReadOnly (const juce::dsp::ProcessContextReplacing<ContextSampleType>& replacingContext) noexcept : constBlock (replacingContext.getInputBlock()) {}

    /** Returns the audio block to use as the input to a process function. */
    const ConstAudioBlockType& getInputBlock() const noexcept   { return constBlock; }

    /** Returns the audio block to use as the output to a process function. */
    const ConstAudioBlockType& getOutputBlock() const noexcept  { return constBlock; }

    /** All process context classes will define this constant method so that templated
        code can determine whether the input and output blocks refer to the same buffer,
        or to two different ones.
    static constexpr bool usesSeparateInputAndOutputBlocks()    { return false; }

    /** If set to true, then a processor's process() method is expected to do whatever
        is appropriate for it to be in a bypassed state.
    bool isBypassed = false;

    ConstAudioBlockType constBlock;

It makes a lot of our existing code much cleaner, because it ensures that processors in a chain that are purely analysing audio don’t alter it and make a lot of more complicated dsp code a bit more const correct, which was not always the case with the existing ProcessContext options.

Maybe this is something that could be added to the general JUCE codebase?