Skip to content

[Bug]: toBitSet() ignores its argument and returns a bitset of every track #399

Description

@EugineKh

Detailed steps on how to reproduce the bug

toBitSet (const juce::Array<Track*>&)
(modules/tracktion_engine/model/edit/tracktion_EditUtilities.cpp:250) uses its argument
only to reach the Edit. The loop walks allTracks instead of tracks, so a bit is set for
every track in the Edit and the result never depends on what was asked for:

juce::BigInteger toBitSet (const juce::Array<Track*>& tracks)
{
    juce::BigInteger bitset;

    if (auto first = tracks[0])
    {
        auto allTracks = getAllTracks (first->edit);

        for (auto t : allTracks)          // <-- should be `tracks`
            if (int index = allTracks.indexOf (t); index >= 0)
                bitset.setBit (index);
    }

    return bitset;
}

Steps: create an Edit with three audio tracks, call toBitSet with an array holding only
the first one, and read back the bitset — all three bits are set.

The mask does reach the renderer — Renderer::Parameters::tracksToDo becomes
CreateNodeParams::allowedTracks (Renderer.cpp:38) — so this is not a value that gets
ignored downstream; a caller asking for a subset silently renders or measures the whole Edit.

This is easy to miss in-engine: Renderer::renderToFile (Renderer.cpp:672) passes
getAllTracks (edit), for which "every track" happens to be the right answer, and the one
in-engine call that does pass a subset — tracktion_NodeRendering.test.cpp:203,
toBitSet ({ t }) — is on an Edit with a single audio track. TestUtilities.h:112
(expectPeakmeasureStatistics) and host code are where it bites.

The engine's own tests already work around it. Both
tracktion_EditNodeBuilder.test.cpp:34 and tracktion_Plugins.test.cpp:117 carry a private
getTracksMask() built from Track::getIndexInEditTrackList() rather than calling
toBitSet, and each asserts exactly the invariant toBitSet breaks:

static juce::BigInteger getTracksMask (const juce::Array<Track*>& tracks)
{
    juce::BigInteger tracksMask;

    for (auto t : tracks)
        tracksMask.setBit (t->getIndexInEditTrackList());

    jassert (tracksMask.countNumberOfSetBits() == tracks.size());
    return tracksMask;
}

The two index spaces agree, so the fix below is equivalent to that helper:
getIndexInEditTrackList() walks visitAllTracksRecursive, and getAllTracks is
visitAllTracks (f, true), which dispatches to the same visitAllRecursive
(tracktion_TrackUtils.cpp:123).

Observed on develop at 0d55ef0c.

What is the expected behaviour?

The returned bitset holds exactly the tracks passed in, as Renderer.h:54 describes
("An bitset of tracks to render"), so that Renderer::Parameters::tracksToDo and
measureStatistics can address a subset — and so that the two private getTracksMask()
copies in the test files can go back to calling the shared helper.

Unit test to reproduce the error?

class ToBitSetTests  : public juce::UnitTest
{
public:
    ToBitSetTests()
        : juce::UnitTest ("ToBitSet", "tracktion_engine")
    {
    }

    void runTest() override
    {
        using namespace tracktion::engine;
        auto& engine = *Engine::getEngines()[0];
        auto edit = Edit::createSingleTrackEdit (engine);
        edit->ensureNumberOfAudioTracks (3);
        auto audioTracks = getAudioTracks (*edit);

        beginTest ("toBitSet returns only the tracks it was given");
        {
            const auto allTracks = getAllTracks (*edit);
            juce::Array<Track*> justTheFirst { static_cast<Track*> (audioTracks[0]) };

            const auto bits = toBitSet (justTheFirst);
            expectEquals (bits.countNumberOfSetBits(), 1);
            expect (bits[allTracks.indexOf (audioTracks[0])]);
            expect (! bits[allTracks.indexOf (audioTracks[1])]);   // FAILS: every bit is set
        }
    }
};

static ToBitSetTests toBitSetTests;

Suggested fix

diff --git a/modules/tracktion_engine/model/edit/tracktion_EditUtilities.cpp b/modules/tracktion_engine/model/edit/tracktion_EditUtilities.cpp
index 0d15e1c9..fa964b88 100644
--- a/modules/tracktion_engine/model/edit/tracktion_EditUtilities.cpp
+++ b/modules/tracktion_engine/model/edit/tracktion_EditUtilities.cpp
@@ -255,7 +255,7 @@ juce::BigInteger toBitSet (const juce::Array<Track*>& tracks)
     {
         auto allTracks = getAllTracks (first->edit);
 
-        for (auto t : allTracks)
+        for (auto t : tracks)
             if (int index = allTracks.indexOf (t); index >= 0)
                 bitset.setBit (index);
     }

The README says third-party pull requests cannot be taken directly, so this is here as an
issue — happy to send it however suits you.

Operating systems

Linux

What versions of the operating systems?

Debian 13 (trixie), kernel 6.16, GCC 14.2

Architectures

Intel/AMD 64-bit

Testing on the develop branch

The bug is present on the develop branch (0d55ef0c).

Code of Conduct

  • I agree to follow the Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions