Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

___

### Change
`toBitSet (const juce::Array<Track*>&)` now returns a bitset of only the tracks passed in.

#### Possible Issues
Previously it ignored its argument and set a bit for every track in the Edit, so anything built from it addressed the whole Edit. Code that passed a subset of tracks - most visibly `Renderer::Parameters::tracksToDo` and `Renderer::measureStatistics()` - will now render or measure just that subset instead of everything.

#### Workaround
Pass `getAllTracks (edit)` where the whole Edit really is wanted, or leave `Renderer::Parameters::tracksToDo` empty, which already means "all tracks".

#### Rationale
The function used its argument only to reach the Edit and then looped over every track, which contradicted its documented behaviour and silently broke subset rendering. See issue #399.

___

### Change
`Plugin` has a new pure virtual method `getBusses()` which every subclass must implement.

Expand Down
1 change: 1 addition & 0 deletions modules/tracktion_core/tracktion_TestConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#define ENGINE_UNIT_TESTS_DAWPROJECT 1
#define ENGINE_UNIT_TESTS_DELAY_PLUGIN 1
#define ENGINE_UNIT_TESTS_EDIT 1
#define ENGINE_UNIT_TESTS_EDIT_UTILITIES 1
#define ENGINE_UNIT_TESTS_EDITCLIP 1
#define ENGINE_UNIT_TESTS_EDIT_LOADER 1
#define ENGINE_UNIT_TESTS_EDIT_TIME 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
6 changes: 4 additions & 2 deletions modules/tracktion_engine/model/edit/tracktion_EditUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,10 @@ bool containsTrack (const Edit&, const Track&);
/** Returns the TrackOutput if the given track has one. */
TrackOutput* getTrackOutput (Track&);

/** Returns the set of tracks as a BigInteger with each bit corresponding to the
array of all tracks in an Edit. Used in Renderer.
/** Returns the given tracks as a BigInteger, with each set bit corresponding to a
track's index in the Edit's array of all tracks (@see getAllTracks).
The Edit is taken from the first track in the array; any tracks not in that Edit
are ignored. Used in Renderer.
*/
juce::BigInteger toBitSet (const juce::Array<Track*>&);

Expand Down
100 changes: 100 additions & 0 deletions modules/tracktion_engine/model/edit/tracktion_EditUtilities.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
,--. ,--. ,--. ,--.
,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
'-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
| | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
`---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com

Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
*/

#if TRACKTION_UNIT_TESTS && ENGINE_UNIT_TESTS_EDIT_UTILITIES

#include <tracktion_engine/../3rd_party/doctest/tracktion_doctest.hpp>

namespace tracktion::inline engine {

//==============================================================================
//==============================================================================
TEST_SUITE ("tracktion_engine")
{
TEST_CASE ("toBitSet: returns only the tracks it was given")
{
auto& engine = *Engine::getEngines()[0];
auto edit = Edit::createSingleTrackEdit (engine, Edit::EditRole::forRendering);
edit->ensureNumberOfAudioTracks (3);

auto audioTracks = getAudioTracks (*edit);
REQUIRE (audioTracks.size() == 3);

const auto allTracks = getAllTracks (*edit);

SUBCASE ("A single track sets a single bit")
{
const auto bits = toBitSet ({ audioTracks[0] });

CHECK (bits.countNumberOfSetBits() == 1);
CHECK (bits[allTracks.indexOf (audioTracks[0])]);
CHECK (! bits[allTracks.indexOf (audioTracks[1])]);
CHECK (! bits[allTracks.indexOf (audioTracks[2])]);
}

SUBCASE ("A subset sets exactly those bits")
{
const auto bits = toBitSet ({ audioTracks[0], audioTracks[2] });

CHECK (bits.countNumberOfSetBits() == 2);
CHECK (bits[allTracks.indexOf (audioTracks[0])]);
CHECK (! bits[allTracks.indexOf (audioTracks[1])]);
CHECK (bits[allTracks.indexOf (audioTracks[2])]);
}

SUBCASE ("All tracks sets every bit")
{
const auto bits = toBitSet (allTracks);
CHECK (bits.countNumberOfSetBits() == allTracks.size());
}

SUBCASE ("An empty array gives an empty bitset")
{
CHECK (toBitSet ({}).isZero());
}

SUBCASE ("Tracks from another Edit are ignored")
{
auto otherEdit = Edit::createSingleTrackEdit (engine, Edit::EditRole::forRendering);
auto otherTrack = getAudioTracks (*otherEdit)[0];

const auto bits = toBitSet ({ audioTracks[1], otherTrack });

CHECK (bits.countNumberOfSetBits() == 1);
CHECK (bits[allTracks.indexOf (audioTracks[1])]);
}

SUBCASE ("Bit indices match Track::getIndexInEditTrackList")
{
for (auto t : allTracks)
{
const auto bits = toBitSet ({ t });

CHECK (bits.countNumberOfSetBits() == 1);
CHECK (bits[t->getIndexInEditTrackList()]);
}
}

SUBCASE ("toTrackArray round-trips the tracks passed in")
{
const juce::Array<Track*> subset { audioTracks[2], audioTracks[0] };
const auto roundTripped = toTrackArray (*edit, toBitSet (subset));

CHECK (roundTripped.size() == subset.size());

for (auto t : subset)
CHECK (roundTripped.contains (t));
}
}
}

} // namespace tracktion::inline engine

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,6 @@ namespace editnode_test_helpers
return createNodeForEdit (edit, params);
}

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;
}

static Renderer::Statistics logStats (Renderer::Statistics stats)
{
MESSAGE (("Stats: peak " + juce::String (stats.peak) + ", avg " + juce::String (stats.average) + ", duration " + juce::String (stats.audioDuration)).toStdString());
Expand All @@ -51,15 +40,15 @@ namespace editnode_test_helpers
static void expectPeak (Edit& edit, TimeRange tr, juce::Array<Track*> tracks, float expectedPeak)
{
auto blockSize = edit.engine.getDeviceManager().getBlockSize();
auto stats = logStats (Renderer::measureStatistics ("", edit, tr, getTracksMask (tracks), blockSize));
auto stats = logStats (Renderer::measureStatistics ("", edit, tr, toBitSet (tracks), blockSize));
CHECK_MESSAGE (juce::isWithin (stats.peak, expectedPeak, 0.01f),
(juce::String ("Expected peak: ") + juce::String (expectedPeak, 4)).toStdString());
}

static void expectRMS (Edit& edit, TimeRange tr, juce::Array<Track*> tracks, float expectedRMS)
{
auto blockSize = edit.engine.getDeviceManager().getBlockSize();
auto stats = logStats (Renderer::measureStatistics ("", edit, tr, getTracksMask (tracks), blockSize));
auto stats = logStats (Renderer::measureStatistics ("", edit, tr, toBitSet (tracks), blockSize));
CHECK_MESSAGE (juce::isWithin (stats.average, expectedRMS, 0.01f),
(juce::String ("Expected RMS: ") + juce::String (expectedRMS, 4)).toStdString());
}
Expand Down
13 changes: 1 addition & 12 deletions modules/tracktion_engine/plugins/tracktion_Plugins.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,6 @@ TEST_SUITE ("tracktion_engine")
}
}

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;
}

template<typename AudioFormatType>
static std::unique_ptr<juce::TemporaryFile> getSinFile (double sampleRate)
{
Expand Down Expand Up @@ -168,7 +157,7 @@ static std::unique_ptr<juce::TemporaryFile> getSinFile (double sampleRate)
static void expectPeak (Edit& edit, TimeRange tr, juce::Array<Track*> tracks, float expectedPeak)
{
auto blockSize = edit.engine.getDeviceManager().getBlockSize();
auto stats = Renderer::measureStatistics ("PDC Tests", edit, tr, getTracksMask (tracks), blockSize);
auto stats = Renderer::measureStatistics ("PDC Tests", edit, tr, toBitSet (tracks), blockSize);
MESSAGE ("Stats: peak " * juce::String (stats.peak).toStdString()
* ", avg " * juce::String (stats.average).toStdString()
* ", duration " * juce::String (stats.audioDuration).toStdString());
Expand Down
1 change: 1 addition & 0 deletions modules/tracktion_engine/tracktion_engine_model_1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ using namespace std::literals;
#include "model/edit/tracktion_Edit.cpp"
#include "model/edit/tracktion_Edit.test.cpp"
#include "model/edit/tracktion_EditUtilities.cpp"
#include "model/edit/tracktion_EditUtilities.test.cpp"
#include "model/edit/tracktion_Scene.cpp"
#include "model/edit/tracktion_SourceFileReference.cpp"
#include "model/edit/tracktion_SourceFileReference.test.cpp"
Expand Down
Loading