Include past automation patterns in processing (#3382)
Fixes #662 * Include past automation tracks in processing * Track::getTCOsInRange: Use binary search, fix doc * Automation refactorings * Add automation tests
This commit is contained in:
@@ -110,16 +110,9 @@ AutomationPattern::~AutomationPattern()
|
||||
|
||||
bool AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup )
|
||||
{
|
||||
if( _search_dup )
|
||||
if( _search_dup && m_objects.contains(_obj) )
|
||||
{
|
||||
for( objectVector::iterator it = m_objects.begin();
|
||||
it != m_objects.end(); ++it )
|
||||
{
|
||||
if( *it == _obj )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// the automation track is unconnected and there is nothing in the track
|
||||
@@ -184,6 +177,11 @@ const AutomatableModel * AutomationPattern::firstObject() const
|
||||
return &_fm;
|
||||
}
|
||||
|
||||
const AutomationPattern::objectVector& AutomationPattern::objects() const
|
||||
{
|
||||
return m_objects;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -272,6 +270,21 @@ void AutomationPattern::removeValue( const MidiTime & time )
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::recordValue(MidiTime time, float value)
|
||||
{
|
||||
if( value != m_lastRecordedValue )
|
||||
{
|
||||
putValue( time, value, true );
|
||||
m_lastRecordedValue = value;
|
||||
}
|
||||
else if( valueAt( time ) != value )
|
||||
{
|
||||
removeValue( time );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set the position of the point that is being dragged.
|
||||
@@ -627,44 +640,6 @@ const QString AutomationPattern::name() const
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::processMidiTime( const MidiTime & time )
|
||||
{
|
||||
if( ! isRecording() )
|
||||
{
|
||||
if( time >= 0 && hasAutomation() )
|
||||
{
|
||||
const float val = valueAt( time );
|
||||
for( objectVector::iterator it = m_objects.begin();
|
||||
it != m_objects.end(); ++it )
|
||||
{
|
||||
if( *it )
|
||||
{
|
||||
( *it )->setAutomatedValue( val );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( time >= 0 && ! m_objects.isEmpty() )
|
||||
{
|
||||
const float value = static_cast<float>( firstObject()->value<float>() );
|
||||
if( value != m_lastRecordedValue )
|
||||
{
|
||||
putValue( time, value, true );
|
||||
m_lastRecordedValue = value;
|
||||
}
|
||||
else if( valueAt( time ) != value )
|
||||
{
|
||||
removeValue( time );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
TrackContentObjectView * AutomationPattern::createView( TrackView * _tv )
|
||||
{
|
||||
return new AutomationPatternView( this, _tv );
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "AutomationTrack.h"
|
||||
#include "AutomationEditor.h"
|
||||
#include "BBEditor.h"
|
||||
@@ -376,13 +378,7 @@ void Song::processNextBuffer()
|
||||
|
||||
if( ( f_cnt_t ) currentFrame == 0 )
|
||||
{
|
||||
if( m_playMode == Mode_PlaySong )
|
||||
{
|
||||
m_globalAutomationTrack->play(
|
||||
m_playPos[m_playMode],
|
||||
framesToPlay,
|
||||
framesPlayed, tcoNum );
|
||||
}
|
||||
processAutomations(trackList, m_playPos[m_playMode], framesToPlay, tcoNum);
|
||||
|
||||
// loop through all tracks and play them
|
||||
for( int i = 0; i < trackList.size(); ++i )
|
||||
@@ -405,6 +401,82 @@ void Song::processNextBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
void Song::processAutomations(const TrackList &tracklist, MidiTime timeStart, fpp_t frames, int tcoNum)
|
||||
{
|
||||
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;
|
||||
// Process recording
|
||||
for (TrackContentObject* tco : tcos)
|
||||
{
|
||||
auto p = dynamic_cast<AutomationPattern *>(tco);
|
||||
MidiTime relTime = timeStart - p->startPosition();
|
||||
if (p->isRecording() && relTime >= 0 && relTime < p->length())
|
||||
{
|
||||
const AutomatableModel* recordedModel = p->firstObject();
|
||||
p->recordValue(relTime, recordedModel->value<float>());
|
||||
|
||||
recordedModels << recordedModel;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply values
|
||||
for (auto it = values.begin(); it != values.end(); it++)
|
||||
{
|
||||
if (! recordedModels.contains(it.key()))
|
||||
{
|
||||
it.key()->setAutomatedValue(it.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Song::isExportDone() const
|
||||
{
|
||||
if ( m_renderBetweenMarkers )
|
||||
@@ -777,6 +849,37 @@ 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<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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -166,6 +166,11 @@ void TrackContentObject::changeLength( const MidiTime & length )
|
||||
emit lengthChanged();
|
||||
}
|
||||
|
||||
bool TrackContentObject::comparePosition(const TrackContentObject *a, const TrackContentObject *b)
|
||||
{
|
||||
return a->startPosition() < b->startPosition();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2269,10 +2274,8 @@ int Track::getTCONum( const TrackContentObject * tco )
|
||||
|
||||
/*! \brief Retrieve a list of trackContentObjects that fall within a period.
|
||||
*
|
||||
* Here we're interested in a range of trackContentObjects that fall
|
||||
* completely within a given time period - their start must be no earlier
|
||||
* than the given start time and their end must be no later than the given
|
||||
* end time.
|
||||
* Here we're interested in a range of trackContentObjects that intersect
|
||||
* the given time period.
|
||||
*
|
||||
* We return the TCOs we find in order by time, earliest TCOs first.
|
||||
*
|
||||
@@ -2283,33 +2286,16 @@ int Track::getTCONum( const TrackContentObject * tco )
|
||||
void Track::getTCOsInRange( tcoVector & tcoV, const MidiTime & start,
|
||||
const MidiTime & end )
|
||||
{
|
||||
for( tcoVector::iterator itO = m_trackContentObjects.begin();
|
||||
itO != m_trackContentObjects.end(); ++itO )
|
||||
for( TrackContentObject* tco : m_trackContentObjects )
|
||||
{
|
||||
TrackContentObject * tco = ( *itO );
|
||||
int s = tco->startPosition();
|
||||
int e = tco->endPosition();
|
||||
if( ( s <= end ) && ( e >= start ) )
|
||||
{
|
||||
// ok, TCO is posated within given range
|
||||
// now let's search according position for TCO in list
|
||||
// -> list is ordered by TCO's position afterwards
|
||||
bool inserted = false;
|
||||
for( tcoVector::iterator it = tcoV.begin();
|
||||
it != tcoV.end(); ++it )
|
||||
{
|
||||
if( ( *it )->startPosition() >= s )
|
||||
{
|
||||
tcoV.insert( it, tco );
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( inserted == false )
|
||||
{
|
||||
// no TCOs found posated behind current TCO...
|
||||
tcoV.push_back( tco );
|
||||
}
|
||||
// TCO is within given range
|
||||
// Insert sorted by TCO's position
|
||||
tcoV.insert(std::upper_bound(tcoV.begin(), tcoV.end(), tco, TrackContentObject::comparePosition),
|
||||
tco);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,40 +50,9 @@ AutomationTrack::~AutomationTrack()
|
||||
|
||||
|
||||
|
||||
bool AutomationTrack::play( const MidiTime & _start, const fpp_t _frames,
|
||||
bool AutomationTrack::play( const MidiTime & time_start, const fpp_t _frames,
|
||||
const f_cnt_t _frame_base, int _tco_num )
|
||||
{
|
||||
if( isMuted() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
tcoVector tcos;
|
||||
if( _tco_num >= 0 )
|
||||
{
|
||||
TrackContentObject * tco = getTCO( _tco_num );
|
||||
tcos.push_back( tco );
|
||||
}
|
||||
else
|
||||
{
|
||||
getTCOsInRange( tcos, _start, _start + static_cast<int>(
|
||||
_frames / Engine::framesPerTick()) );
|
||||
}
|
||||
|
||||
for( tcoVector::iterator it = tcos.begin(); it != tcos.end(); ++it )
|
||||
{
|
||||
AutomationPattern * p = dynamic_cast<AutomationPattern *>( *it );
|
||||
if( p == NULL || ( *it )->isMuted() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
MidiTime cur_start = _start;
|
||||
if( _tco_num < 0 )
|
||||
{
|
||||
cur_start -= p->startPosition();
|
||||
}
|
||||
p->processMidiTime( cur_start );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user