Fix automation processing in BB tracks (#3481)

Fixes #3464
This commit is contained in:
Lukas W
2017-04-06 23:10:00 +02:00
committed by GitHub
parent b5ac3161c9
commit 96a00300e8
7 changed files with 201 additions and 93 deletions

View File

@@ -48,7 +48,7 @@ public:
return "bbtrackcontainer";
}
tact_t lengthOfBB( int _bb );
tact_t lengthOfBB( int _bb ) const;
inline tact_t lengthOfCurrentBB()
{
return lengthOfBB( currentBB() );
@@ -62,6 +62,7 @@ public:
void fixIncorrectPositions();
void createTCOsForBB( int _bb );
AutomatedValueMap automatedValuesAt(MidiTime time, int tcoNum) const override;
public slots:
void play();

View File

@@ -204,7 +204,8 @@ public:
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:
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;

View File

@@ -93,11 +93,14 @@ public:
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:

View File

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

View File

@@ -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<AutomationTrack*> tracks;
if(m_playMode == Mode_PlaySong)
{
tracks << m_globalAutomationTrack;
}
for( Track* track : tracklist)
{
if (track->type() == Track::AutomationTrack || track->type() == Track::HiddenAutomationTrack)
{
tracks << dynamic_cast<AutomationTrack*>(track);
}
}
std::remove_if(tracks.begin(), tracks.end(), std::mem_fn(&Track::isMuted));
Track::tcoVector tcos;
AutomatedValueMap values;
if (tcoNum < 0)
{
// Collect all relevant patterns, sorted by start position
MidiTime timeEnd = timeStart + static_cast<int>(frames / Engine::framesPerTick());
for (AutomationTrack* track: tracks)
{
track->getTCOsInRange(tcos, 0, timeEnd);
}
values = automatedValuesAt(tcos, timeStart);
}
else
{
if (tracklist.size() != 1)
{
qWarning() << "processAutomations called with specified tcoNum but not exactly one track";
}
for (AutomationTrack* track: tracks)
{
TrackContentObject* tco = track->getTCO(tcoNum);
auto p = dynamic_cast<AutomationPattern *>(tco);
for (AutomatableModel* object : p->objects())
{
values[object] = p->valueAt(timeStart);
}
tcos << tco;
}
}
QSet<const AutomatableModel*> recordedModels;
TrackContainer* container = this;
int tcoNum = -1;
switch (m_playMode)
{
case Mode_PlaySong:
break;
case Mode_PlayBB:
{
Q_ASSERT(tracklist.size() == 1);
Q_ASSERT(tracklist.at(0)->type() == Track::BBTrack);
auto bbTrack = dynamic_cast<BBTrack*>(tracklist.at(0));
auto bbContainer = Engine::getBBTrackContainer();
container = bbContainer;
tcoNum = bbTrack->index();
}
break;
default:
return;
}
values = container->automatedValuesAt(timeStart, tcoNum);
TrackList tracks = container->tracks();
Track::tcoVector tcos;
for (Track* track : tracks)
{
if (track->type() == Track::AutomationTrack) {
track->getTCOsInRange(tcos, 0, timeStart);
}
}
// 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 Song::automatedValuesAt(MidiTime time, int tcoNum) const
{
AutomatedValueMap valueMap;
for(TrackContentObject* tco : tcos)
{
if (tco->isMuted() || tco->startPosition() > time) {
continue;
}
AutomationPattern* p = dynamic_cast<AutomationPattern *>(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;
return TrackContainer::automatedValuesFromTracks(TrackList(tracks()) << m_globalAutomationTrack, time, tcoNum);
}

View File

@@ -29,12 +29,16 @@
#include <QDomElement>
#include <QWriteLocker>
#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<AutomationPattern *>(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<BBTCO *>(tco))
{
auto bbIndex = dynamic_cast<class BBTrack*>(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;
};

View File

@@ -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<AutomationPattern*>(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;