diff --git a/include/BBTrackContainer.h b/include/BBTrackContainer.h index 2832de10d65..aaff75c389f 100644 --- a/include/BBTrackContainer.h +++ b/include/BBTrackContainer.h @@ -48,7 +48,7 @@ class EXPORT BBTrackContainer : public TrackContainer return "bbtrackcontainer"; } - tact_t lengthOfBB( int _bb ); + tact_t lengthOfBB( int _bb ) const; inline tact_t lengthOfCurrentBB() { return lengthOfBB( currentBB() ); @@ -62,6 +62,7 @@ class EXPORT BBTrackContainer : public TrackContainer void fixIncorrectPositions(); void createTCOsForBB( int _bb ); + AutomatedValueMap automatedValuesAt(MidiTime time, int tcoNum) const override; public slots: void play(); diff --git a/include/Song.h b/include/Song.h index e38a3668a4f..ad644ed9f09 100644 --- a/include/Song.h +++ b/include/Song.h @@ -204,7 +204,8 @@ class EXPORT Song : public TrackContainer return m_globalAutomationTrack; } - static AutomatedValueMap automatedValuesAt(const Track::tcoVector& tcos, MidiTime time); + //TODO: Add Q_DECL_OVERRIDE when Qt4 is dropped + AutomatedValueMap automatedValuesAt(MidiTime time, int tcoNum = -1) const; // file management void createNewProject(); @@ -326,7 +327,7 @@ private slots: void removeAllControllers(); - void processAutomations(const TrackList& tracks, MidiTime timeStart, fpp_t frames, int tcoNum); + void processAutomations(const TrackList& tracks, MidiTime timeStart, fpp_t frames); AutomationTrack * m_globalAutomationTrack; diff --git a/include/TrackContainer.h b/include/TrackContainer.h index 0b7cd067dbc..33569c9d98e 100644 --- a/include/TrackContainer.h +++ b/include/TrackContainer.h @@ -93,11 +93,14 @@ class EXPORT TrackContainer : public Model, public JournallingObject return m_TrackContainerType; } + virtual AutomatedValueMap automatedValuesAt(MidiTime time, int tcoNum = -1) const; signals: void trackAdded( Track * _track ); protected: + static AutomatedValueMap automatedValuesFromTracks(const TrackList &tracks, MidiTime timeStart, int tcoNum = -1); + mutable QReadWriteLock m_tracksMutex; private: diff --git a/src/core/BBTrackContainer.cpp b/src/core/BBTrackContainer.cpp index f0164b071fd..e9af1f62159 100644 --- a/src/core/BBTrackContainer.cpp +++ b/src/core/BBTrackContainer.cpp @@ -90,7 +90,7 @@ void BBTrackContainer::updateAfterTrackAdd() -tact_t BBTrackContainer::lengthOfBB( int _bb ) +tact_t BBTrackContainer::lengthOfBB( int _bb ) const { MidiTime max_length = MidiTime::ticksPerTact(); @@ -239,6 +239,20 @@ void BBTrackContainer::createTCOsForBB( int _bb ) } } +AutomatedValueMap BBTrackContainer::automatedValuesAt(MidiTime time, int tcoNum) const +{ + Q_ASSERT(tcoNum >= 0); + Q_ASSERT(time.getTicks() >= 0); + + auto length_tacts = lengthOfBB(tcoNum); + auto length_ticks = length_tacts * MidiTime::ticksPerTact(); + if (time > length_ticks) { + time = length_ticks; + } + + return TrackContainer::automatedValuesAt(time + (MidiTime::ticksPerTact() * tcoNum), tcoNum); +} + diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 568794a5435..1af2dc2f3c3 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -378,7 +378,7 @@ void Song::processNextBuffer() if( ( f_cnt_t ) currentFrame == 0 ) { - processAutomations(trackList, m_playPos[m_playMode], framesToPlay, tcoNum); + processAutomations(trackList, m_playPos[m_playMode], framesToPlay); // loop through all tracks and play them for( int i = 0; i < trackList.size(); ++i ) @@ -401,58 +401,45 @@ void Song::processNextBuffer() } } -void Song::processAutomations(const TrackList &tracklist, MidiTime timeStart, fpp_t frames, int tcoNum) + +void Song::processAutomations(const TrackList &tracklist, MidiTime timeStart, fpp_t) { - QVector tracks; + AutomatedValueMap values; - if(m_playMode == Mode_PlaySong) - { - tracks << m_globalAutomationTrack; - } - for( Track* track : tracklist) - { - if (track->type() == Track::AutomationTrack || track->type() == Track::HiddenAutomationTrack) - { - tracks << dynamic_cast(track); - } - } - std::remove_if(tracks.begin(), tracks.end(), std::mem_fn(&Track::isMuted)); + QSet recordedModels; - Track::tcoVector tcos; - AutomatedValueMap values; + TrackContainer* container = this; + int tcoNum = -1; - if (tcoNum < 0) + switch (m_playMode) { - // Collect all relevant patterns, sorted by start position - MidiTime timeEnd = timeStart + static_cast(frames / Engine::framesPerTick()); - for (AutomationTrack* track: tracks) - { - track->getTCOsInRange(tcos, 0, timeEnd); - } - - values = automatedValuesAt(tcos, timeStart); - } - else + case Mode_PlaySong: + break; + case Mode_PlayBB: { - if (tracklist.size() != 1) - { - qWarning() << "processAutomations called with specified tcoNum but not exactly one track"; - } + Q_ASSERT(tracklist.size() == 1); + Q_ASSERT(tracklist.at(0)->type() == Track::BBTrack); + auto bbTrack = dynamic_cast(tracklist.at(0)); + auto bbContainer = Engine::getBBTrackContainer(); + container = bbContainer; + tcoNum = bbTrack->index(); + } + break; + default: + return; + } - for (AutomationTrack* track: tracks) - { - TrackContentObject* tco = track->getTCO(tcoNum); - auto p = dynamic_cast(tco); + values = container->automatedValuesAt(timeStart, tcoNum); + TrackList tracks = container->tracks(); - for (AutomatableModel* object : p->objects()) - { - values[object] = p->valueAt(timeStart); - } - tcos << tco; + Track::tcoVector tcos; + for (Track* track : tracks) + { + if (track->type() == Track::AutomationTrack) { + track->getTCOsInRange(tcos, 0, timeStart); } } - QSet recordedModels; // Process recording for (TrackContentObject* tco : tcos) { @@ -849,35 +836,10 @@ AutomationPattern * Song::tempoAutomationPattern() return AutomationPattern::globalAutomationPattern( &m_tempoModel ); } -AutomatedValueMap Song::automatedValuesAt(const Track::tcoVector &tcos, MidiTime time) -{ - AutomatedValueMap valueMap; - for(TrackContentObject* tco : tcos) - { - if (tco->isMuted() || tco->startPosition() > time) { - continue; - } - AutomationPattern* p = dynamic_cast(tco); - if (!p) { - qCritical() << "automatedValuesAt: tco passed is not an automation pattern"; - continue; - } - - if (! p->hasAutomation()) { - continue; - } - - MidiTime relTime = time - p->startPosition(); - float value = p->valueAt(relTime); - - for (AutomatableModel* model : p->objects()) - { - valueMap[model] = value; - } - } - - return valueMap; +AutomatedValueMap Song::automatedValuesAt(MidiTime time, int tcoNum) const +{ + return TrackContainer::automatedValuesFromTracks(TrackList(tracks()) << m_globalAutomationTrack, time, tcoNum); } diff --git a/src/core/TrackContainer.cpp b/src/core/TrackContainer.cpp index 87f92ec0534..9d8254cd953 100644 --- a/src/core/TrackContainer.cpp +++ b/src/core/TrackContainer.cpp @@ -29,12 +29,16 @@ #include #include +#include "AutomationPattern.h" +#include "AutomationTrack.h" +#include "BBTrack.h" +#include "BBTrackContainer.h" #include "TrackContainer.h" #include "InstrumentTrack.h" -#include "GuiApplication.h" -#include "MainWindow.h" #include "Song.h" +#include "GuiApplication.h" +#include "MainWindow.h" TrackContainer::TrackContainer() : Model( NULL ), @@ -234,6 +238,85 @@ bool TrackContainer::isEmpty() const +AutomatedValueMap TrackContainer::automatedValuesAt(MidiTime time, int tcoNum) const +{ + return automatedValuesFromTracks(tracks(), time, tcoNum); +} + + +AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tracks, MidiTime time, int tcoNum) +{ + Track::tcoVector tcos; + + for (Track* track: tracks) + { + if (track->isMuted()) { + continue; + } + + switch(track->type()) + { + case Track::AutomationTrack: + case Track::HiddenAutomationTrack: + case Track::BBTrack: + if (tcoNum < 0) { + track->getTCOsInRange(tcos, 0, time); + } else { + Q_ASSERT(track->numOfTCOs() > tcoNum); + tcos << track->getTCO(tcoNum); + } + default: + break; + } + } + + AutomatedValueMap valueMap; + + Q_ASSERT(std::is_sorted(tcos.begin(), tcos.end(), TrackContentObject::comparePosition)); + + for(TrackContentObject* tco : tcos) + { + if (tco->isMuted() || tco->startPosition() > time) { + continue; + } + + if (auto* p = dynamic_cast(tco)) + { + if (! p->hasAutomation()) { + continue; + } + MidiTime relTime = time - p->startPosition(); + float value = p->valueAt(relTime); + + for (AutomatableModel* model : p->objects()) + { + valueMap[model] = value; + } + } + else if (auto* bb = dynamic_cast(tco)) + { + auto bbIndex = dynamic_cast(bb->getTrack())->index(); + auto bbContainer = Engine::getBBTrackContainer(); + + MidiTime bbTime = time - tco->startPosition(); + bbTime = std::min(bbTime, tco->length()); + bbTime = bbTime % (bbContainer->lengthOfBB(bbIndex) * MidiTime::ticksPerTact()); + + auto bbValues = bbContainer->automatedValuesAt(bbTime, bbIndex); + for (auto it=bbValues.begin(); it != bbValues.end(); it++) + { + // override old values, bb track with the highest index takes precedence + valueMap[it.key()] = it.value(); + } + } + else + { + continue; + } + } + + return valueMap; +}; diff --git a/tests/src/tracks/AutomationTrackTest.cpp b/tests/src/tracks/AutomationTrackTest.cpp index 63e85d7e0bf..542815dccfc 100644 --- a/tests/src/tracks/AutomationTrackTest.cpp +++ b/tests/src/tracks/AutomationTrackTest.cpp @@ -28,6 +28,8 @@ #include "AutomationPattern.h" #include "AutomationTrack.h" +#include "BBTrack.h" +#include "BBTrackContainer.h" #include "TrackContainer.h" #include "Engine.h" @@ -69,35 +71,77 @@ private slots: QCOMPARE(p.valueAt(150), 1.0f); } - void testTrack() + void testPatterns() { FloatModel model; - AutomationPattern p1(nullptr); + auto song = Engine::getSong(); + AutomationTrack track(song); + + AutomationPattern p1(&track); p1.setProgressionType(AutomationPattern::LinearProgression); p1.putValue(0, 0.0, false); p1.putValue(10, 1.0, false); p1.movePosition(0); p1.addObject(&model); - AutomationPattern p2(nullptr); + AutomationPattern p2(&track); p2.setProgressionType(AutomationPattern::LinearProgression); p2.putValue(0, 0.0, false); p2.putValue(100, 1.0, false); p2.movePosition(100); p2.addObject(&model); - AutomationPattern p3(nullptr); + AutomationPattern p3(&track); p3.addObject(&model); //XXX: Why is this even necessary? p3.clear(); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 0)[&model], 0.0f); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 5)[&model], 0.5f); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 10)[&model], 1.0f); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 50)[&model], 1.0f); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 100)[&model], 0.0f); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 150)[&model], 0.5f); + QCOMPARE(song->automatedValuesAt( 0)[&model], 0.0f); + QCOMPARE(song->automatedValuesAt( 5)[&model], 0.5f); + QCOMPARE(song->automatedValuesAt( 10)[&model], 1.0f); + QCOMPARE(song->automatedValuesAt( 50)[&model], 1.0f); + QCOMPARE(song->automatedValuesAt(100)[&model], 0.0f); + QCOMPARE(song->automatedValuesAt(150)[&model], 0.5f); + } + + void testBBTrack() + { + auto song = Engine::getSong(); + auto bbContainer = Engine::getBBTrackContainer(); + BBTrack bbTrack(song); + AutomationTrack automationTrack(bbContainer); + bbTrack.createTCOsForBB(bbTrack.index()); + + QVERIFY(automationTrack.numOfTCOs()); + AutomationPattern* p1 = dynamic_cast(automationTrack.getTCO(0)); + QVERIFY(p1); + + FloatModel model; + + p1->setProgressionType(AutomationPattern::LinearProgression); + p1->putValue(0, 0.0, false); + p1->putValue(10, 1.0, false); + p1->addObject(&model); + + QCOMPARE(bbContainer->automatedValuesAt( 0, bbTrack.index())[&model], 0.0f); + QCOMPARE(bbContainer->automatedValuesAt( 5, bbTrack.index())[&model], 0.5f); + QCOMPARE(bbContainer->automatedValuesAt(10, bbTrack.index())[&model], 1.0f); + QCOMPARE(bbContainer->automatedValuesAt(50, bbTrack.index())[&model], 1.0f); + + BBTrack bbTrack2(song); + bbTrack.createTCOsForBB(bbTrack2.index()); + + QCOMPARE(bbContainer->automatedValuesAt(5, bbTrack.index())[&model], 0.5f); + QVERIFY(! bbContainer->automatedValuesAt(5, bbTrack2.index()).size()); + + BBTCO tco(&bbTrack); + tco.changeLength(MidiTime::ticksPerTact() * 2); + tco.movePosition(0); + + QCOMPARE(song->automatedValuesAt(0)[&model], 0.0f); + QCOMPARE(song->automatedValuesAt(5)[&model], 0.5f); + QCOMPARE(song->automatedValuesAt(MidiTime::ticksPerTact() + 5)[&model], 0.5f); } } AutomationTrackTest;