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
(expectPeak → measureStatistics) 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
Detailed steps on how to reproduce the bug
toBitSet (const juce::Array<Track*>&)(
modules/tracktion_engine/model/edit/tracktion_EditUtilities.cpp:250) uses its argumentonly to reach the Edit. The loop walks
allTracksinstead oftracks, so a bit is set forevery track in the Edit and the result never depends on what was asked for:
Steps: create an Edit with three audio tracks, call
toBitSetwith an array holding onlythe first one, and read back the bitset — all three bits are set.
The mask does reach the renderer —
Renderer::Parameters::tracksToDobecomesCreateNodeParams::allowedTracks(Renderer.cpp:38) — so this is not a value that getsignored 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) passesgetAllTracks (edit), for which "every track" happens to be the right answer, and the onein-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(
expectPeak→measureStatistics) and host code are where it bites.The engine's own tests already work around it. Both
tracktion_EditNodeBuilder.test.cpp:34andtracktion_Plugins.test.cpp:117carry a privategetTracksMask()built fromTrack::getIndexInEditTrackList()rather than callingtoBitSet, and each asserts exactly the invarianttoBitSetbreaks:The two index spaces agree, so the fix below is equivalent to that helper:
getIndexInEditTrackList()walksvisitAllTracksRecursive, andgetAllTracksisvisitAllTracks (f, true), which dispatches to the samevisitAllRecursive(
tracktion_TrackUtils.cpp:123).Observed on
developat0d55ef0c.What is the expected behaviour?
The returned bitset holds exactly the tracks passed in, as
Renderer.h:54describes("An bitset of tracks to render"), so that
Renderer::Parameters::tracksToDoandmeasureStatisticscan address a subset — and so that the two privategetTracksMask()copies in the test files can go back to calling the shared helper.
Unit test to reproduce the error?
Suggested fix
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
developbranchThe bug is present on the
developbranch (0d55ef0c).Code of Conduct