MIDI input: Tune Request is repeated after every real-time byte

On inputs where JUCE parses raw MIDI 1.0 bytes, once a Tune Request (F6) has been received, every following real-time byte (F8–FE) is delivered followed by an extra F6, which is produced by JUCE’s input parsing in BytestreamSysexExtractor.

Example: receiving F6 F8 FA FE delivers F6, F8, F6, FA, F6, FE, F6 instead of F6, F8, FA, FE.

The repeats continue on every real-time byte until another status byte (for example a channel message) is received. It happens both when the bytes arrive in one buffer and when they arrive one message per callback, which is how WinMM delivers them. From reading the code, any input that goes through BytestreamToUMPDispatcher / MidiDataConcatenator should be affected: WinMM, WinRT, the old CoreMIDI API path and the Android bytestream path. Despite its name, BytestreamSysexExtractor (juce_MidiDataConcatenator.h) parses all incoming MIDI 1.0 messages, not only SysEx.

The following test cases added to juce_MidiDataConcatenator_test.cpp fail on develop with the following the failures. The third test demos that System Common messages should cancel running status (MIDI 1.0). Today, stray data bytes after F1/F2/F3 are rebuilt into a new message, e.g. F3 05 07 gives F3 05 and F3 07. The third test covers that.

beginTest ("Realtime messages after a single-byte system common message do not repeat it");
{
    BytestreamSysexExtractor extractor;
    const std::byte message[] { std::byte (0xf6),   // tune request
                                std::byte (0xf8),   // timing clock
                                std::byte (0xfa),   // start
                                std::byte (0xfe) }; // active sensing

    std::vector<std::vector<std::byte>> vectors;
    extractor.push (message, [&] (auto, auto bytes)
    {
        vectors.emplace_back (bytes.begin(), bytes.end());
    });

    expect (vectors == std::vector { std::vector { std::byte (0xf6) },
                                     std::vector { std::byte (0xf8) },
                                     std::vector { std::byte (0xfa) },
                                     std::vector { std::byte (0xfe) } });
}

beginTest ("Realtime messages after a single-byte system common message do not repeat it between calls");
{
    BytestreamSysexExtractor extractor;
    std::vector<std::vector<std::byte>> vectors;

    for (const auto byte : { std::byte (0xf6),   // tune request
                             std::byte (0xf8),   // timing clock
                             std::byte (0xfe) }) // active sensing
    {
        const std::byte message[] { byte };
        extractor.push (message, [&] (auto, auto bytes)
        {
            vectors.emplace_back (bytes.begin(), bytes.end());
        });
    }

    expect (vectors == std::vector { std::vector { std::byte (0xf6) },
                                     std::vector { std::byte (0xf8) },
                                     std::vector { std::byte (0xfe) } });
}

beginTest ("System common messages cancel running status");
{
    BytestreamSysexExtractor extractor;
    const std::byte message[] { std::byte (0xf3),   // song select
                                std::byte (0x05),
                                std::byte (0x07) }; // no running status, so ignored

    std::vector<std::vector<std::byte>> vectors;
    extractor.push (message, [&] (auto, auto bytes)
    {
        vectors.emplace_back (bytes.begin(), bytes.end());
    });

    expect (vectors == std::vector<std::vector<std::byte>> { { std::byte (0xf3), std::byte (0x05) } });
}

I expect this is a rare issue that’s ever encountered in the field, but I figure I’d report it since it’s easy to reproduce with a unit test.

Environment: Windows 11 Pro 10.0.26200, x86_64, Visual Studio 2026. Tested with JUCE 9.0.2 and develop