float AutomatableModel::controllerValue( int frameOffset ) const { if( m_controllerConnection ) { float v = 0; switch(m_scaleType) { case Linear: v = minValue<float>() + ( range() * controllerConnection()->currentValue( frameOffset ) ); break; case Logarithmic: v = logToLinearScale( controllerConnection()->currentValue( frameOffset )); break; default: qFatal("AutomatableModel::controllerValue(int)" "lacks implementation for a scale type"); break; } if( typeInfo<float>::isEqual( m_step, 1 ) && m_hasStrictStepSize ) { return qRound( v ); } return v; } AutomatableModel* lm = m_linkedModels.first(); if( lm->controllerConnection() ) { return fittedValue( lm->controllerValue( frameOffset ) ); } return fittedValue( lm->m_value ); }
void AutomatableModel::setAutomatedValue( const float value ) { ++m_setValueDepth; const float oldValue = m_value; const float scaled_value = ( m_scaleType == Linear ) ? value : logToLinearScale( // fits value into [0,1]: (value - minValue<float>()) / maxValue<float>() ); m_value = fittedValue( scaled_value ); if( oldValue != m_value ) { // notify linked models for( AutoModelVector::Iterator it = m_linkedModels.begin(); it != m_linkedModels.end(); ++it ) { if( (*it)->m_setValueDepth < 1 && !(*it)->fittedValue( m_value ) != (*it)->m_value ) { // @TOBY: don't take m_value, but better: value, // otherwise, we convert to log twice? (*it)->setAutomatedValue( m_value ); } } emit dataChanged(); } --m_setValueDepth; }
void AutomatableModel::setValue( const float value ) { ++m_setValueDepth; const float old_val = m_value; m_value = fittedValue( value ); if( old_val != m_value ) { // add changes to history so user can undo it addJournalCheckPoint(); // notify linked models for( AutoModelVector::Iterator it = m_linkedModels.begin(); it != m_linkedModels.end(); ++it ) { if( (*it)->m_setValueDepth < 1 && (*it)->fittedValue( value ) != (*it)->m_value ) { bool journalling = (*it)->testAndSetJournalling( isJournalling() ); (*it)->setValue( value ); (*it)->setJournalling( journalling ); } } emit dataChanged(); } else { emit dataUnchanged(); } --m_setValueDepth; }
void AutomatableModel::setAutomatedValue( const float value ) { m_oldValue = m_value; ++m_setValueDepth; const float oldValue = m_value; const float scaled_value = scaledValue( value ); m_value = fittedValue( scaled_value ); if( oldValue != m_value ) { // notify linked models for( AutoModelVector::Iterator it = m_linkedModels.begin(); it != m_linkedModels.end(); ++it ) { if( (*it)->m_setValueDepth < 1 && !(*it)->fittedValue( m_value ) != (*it)->m_value ) { (*it)->setAutomatedValue( value ); } } m_valueChanged = true; emit dataChanged(); } --m_setValueDepth; }
void AutomatableModel::setInitValue( const float value ) { m_initValue = fittedValue( value ); bool journalling = testAndSetJournalling( false ); setValue( value ); setJournalling( journalling ); emit initValueChanged( value ); }
// Get current value, with an offset into the current buffer for sample exactness float Controller::currentValue( int _offset ) { if( _offset == 0 || isSampleExact() ) { m_currentValue = fittedValue( value( _offset ) ); } return m_currentValue; }
float AutomatableModel::globalAutomationValueAt( const MidiTime& time ) { // get patterns that connect to this model QVector<AutomationPattern *> patterns = AutomationPattern::patternsForModel( this ); if( patterns.isEmpty() ) { // if no such patterns exist, return current value return m_value; } else { // of those patterns: // find the patterns which overlap with the miditime position QVector<AutomationPattern *> patternsInRange; for( QVector<AutomationPattern *>::ConstIterator it = patterns.begin(); it != patterns.end(); it++ ) { int s = ( *it )->startPosition(); int e = ( *it )->endPosition(); if( s <= time && e >= time ) { patternsInRange += ( *it ); } } AutomationPattern * latestPattern = NULL; if( ! patternsInRange.isEmpty() ) { // if there are more than one overlapping patterns, just use the first one because // multiple pattern behaviour is undefined anyway latestPattern = patternsInRange[0]; } else // if we find no patterns at the exact miditime, we need to search for the last pattern before time and use that { int latestPosition = 0; for( QVector<AutomationPattern *>::ConstIterator it = patterns.begin(); it != patterns.end(); it++ ) { int e = ( *it )->endPosition(); if( e <= time && e > latestPosition ) { latestPosition = e; latestPattern = ( *it ); } } } if( latestPattern ) { // scale/fit the value appropriately and return it const float value = latestPattern->valueAt( time - latestPattern->startPosition() ); const float scaled_value = scaledValue( value ); return fittedValue( scaled_value ); } // if we still find no pattern, the value at that time is undefined so // just return current value as the best we can do else return m_value; } }
AutomatableModel::AutomatableModel( const float val, const float min, const float max, const float step, Model* parent, const QString & displayName, bool defaultConstructed ) : Model( parent, displayName, defaultConstructed ), m_scaleType( Linear ), m_minValue( min ), m_maxValue( max ), m_step( step ), m_range( max - min ), m_centerValue( m_minValue ), m_valueChanged( false ), m_setValueDepth( 0 ), m_hasStrictStepSize( false ), m_controllerConnection( NULL ), m_valueBuffer( static_cast<int>( Engine::mixer()->framesPerPeriod() ) ), m_lastUpdatedPeriod( -1 ), m_hasSampleExactData( false ) { m_value = fittedValue( val ); setInitValue( val ); }
ValueBuffer * AutomatableModel::valueBuffer() { // if we've already calculated the valuebuffer this period, return the cached buffer if( m_lastUpdatedPeriod == s_periodCounter ) { return m_hasSampleExactData ? &m_valueBuffer : NULL; } QMutexLocker m( &m_valueBufferMutex ); if( m_lastUpdatedPeriod == s_periodCounter ) { return m_hasSampleExactData ? &m_valueBuffer : NULL; } float val = m_value; // make sure our m_value doesn't change midway ValueBuffer * vb; if( m_controllerConnection && m_controllerConnection->getController()->isSampleExact() ) { vb = m_controllerConnection->valueBuffer(); if( vb ) { float * values = vb->values(); float * nvalues = m_valueBuffer.values(); switch( m_scaleType ) { case Linear: for( int i = 0; i < m_valueBuffer.length(); i++ ) { nvalues[i] = minValue<float>() + ( range() * values[i] ); } break; case Logarithmic: for( int i = 0; i < m_valueBuffer.length(); i++ ) { nvalues[i] = logToLinearScale( values[i] ); } break; default: qFatal("AutomatableModel::valueBuffer() " "lacks implementation for a scale type"); break; } m_lastUpdatedPeriod = s_periodCounter; m_hasSampleExactData = true; return &m_valueBuffer; } } AutomatableModel* lm = NULL; if( m_hasLinkedModels ) { lm = m_linkedModels.first(); } if( lm && lm->controllerConnection() && lm->controllerConnection()->getController()->isSampleExact() ) { vb = lm->valueBuffer(); float * values = vb->values(); float * nvalues = m_valueBuffer.values(); for( int i = 0; i < vb->length(); i++ ) { nvalues[i] = fittedValue( values[i] ); } m_lastUpdatedPeriod = s_periodCounter; m_hasSampleExactData = true; return &m_valueBuffer; } if( m_oldValue != val ) { m_valueBuffer.interpolate( m_oldValue, val ); m_oldValue = val; m_lastUpdatedPeriod = s_periodCounter; m_hasSampleExactData = true; return &m_valueBuffer; } // if we have no sample-exact source for a ValueBuffer, return NULL to signify that no data is available at the moment // in which case the recipient knows to use the static value() instead m_lastUpdatedPeriod = s_periodCounter; m_hasSampleExactData = false; return NULL; }