Initial implementation of sample-exact models and controllers

Also featuring a very efficient buffer-based system for transporting sample-exact control data
Also interpolation for automations
The native Amplifier is a reference implementation for taking advantage of sample-exact data and is currently
the only one that does so, it can be used to test things out, and as documentation/example for implementing the
same elsewhere
This commit is contained in:
Vesa
2014-05-20 01:43:23 +03:00
parent 3a833d2ad8
commit 44f1d3df85
16 changed files with 485 additions and 127 deletions

View File

@@ -48,8 +48,11 @@ AutomatableModel::AutomatableModel( DataType type,
m_range( max - min ),
m_centerValue( m_minValue ),
m_setValueDepth( 0 ),
m_strictStepSize( false ),
m_hasLinkedModels( false ),
m_controllerConnection( NULL )
m_controllerConnection( NULL ),
m_valueBuffer( static_cast<int>( engine::mixer()->framesPerPeriod() ) )
{
setInitValue( val );
}
@@ -69,6 +72,8 @@ AutomatableModel::~AutomatableModel()
{
delete m_controllerConnection;
}
m_valueBuffer.clear();
emit destroyed( id() );
}
@@ -81,7 +86,38 @@ bool AutomatableModel::isAutomated() const
return AutomationPattern::isAutomated( this );
}
bool AutomatableModel::hasSampleExactData() const
{
// if a controller is connected...
if( m_controllerConnection != NULL )
{
// ...and is sample-exact, then return true
if( m_controllerConnection->getController()->isSampleExact() )
{
return true;
}
}
// check also the same for the first linked model
if( hasLinkedModels() )
{
AutomatableModel* lm = m_linkedModels.first();
if( lm->m_controllerConnection != NULL )
{
if( lm->m_controllerConnection->getController()->isSampleExact() )
{
return true;
}
}
}
// if we have values we can interpolate return true
if( m_oldValue != m_value )
{
return true;
}
// otherwise, return false
return false;
}
void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, const QString& name )
@@ -215,10 +251,11 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString&
void AutomatableModel::setValue( const float value )
{
m_oldValue = m_value;
++m_setValueDepth;
const float old_val = m_value;
m_value = fittedValue( value );
m_value = fittedValue( value, true );
if( old_val != m_value )
{
// add changes to history so user can undo it
@@ -290,6 +327,7 @@ void AutomatableModel::roundAt( T& value, const T& where ) const
void AutomatableModel::setAutomatedValue( const float value )
{
m_oldValue = m_value;
++m_setValueDepth;
const float oldValue = m_value;
@@ -363,11 +401,11 @@ void AutomatableModel::setStep( const float step )
float AutomatableModel::fittedValue( float value ) const
float AutomatableModel::fittedValue( float value, bool forceStep ) const
{
value = tLimit<float>( value, m_minValue, m_maxValue );
if( m_step != 0 )
if( m_step != 0 && ( m_strictStepSize || forceStep ) )
{
value = nearbyintf( value / m_step ) * m_step;
}
@@ -488,7 +526,7 @@ float AutomatableModel::controllerValue( int frameOffset ) const
"lacks implementation for a scale type");
break;
}
if( typeInfo<float>::isEqual( m_step, 1 ) )
if( typeInfo<float>::isEqual( m_step, 1 ) && m_strictStepSize )
{
return qRound( v );
}
@@ -505,6 +543,67 @@ float AutomatableModel::controllerValue( int frameOffset ) const
}
ValueBuffer * AutomatableModel::valueBuffer()
{
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;
}
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], false );
}
return &m_valueBuffer;
}
if( m_oldValue != m_value )
{
m_valueBuffer.interpolate( m_oldValue, m_value );
m_oldValue = m_value;
return &m_valueBuffer;
}
// if we have no sample-exact source for a ValueBuffer, create one and fill it with current value
// ideally, recipients should check first if we hasSampleExactData before fetching ValueBuffers
m_valueBuffer.fill( m_value );
return &m_valueBuffer;
}
void AutomatableModel::unlinkControllerConnection()

View File

@@ -40,7 +40,7 @@
#include "PeakController.h"
unsigned int Controller::s_frames = 0;
unsigned int Controller::s_periods = 0;
QVector<Controller *> Controller::s_controllers;
@@ -49,6 +49,8 @@ Controller::Controller( ControllerTypes _type, Model * _parent,
const QString & _display_name ) :
Model( _parent, _display_name ),
JournallingObject(),
m_valueBuffer( engine::mixer()->framesPerPeriod() ),
m_bufferLastUpdated( 0 ),
m_connectionCount( 0 ),
m_type( _type )
{
@@ -97,6 +99,7 @@ Controller::~Controller()
engine::getSong()->removeController( this );
}
m_valueBuffer.clear();
// Remove connections by destroyed signal
}
@@ -115,17 +118,41 @@ float Controller::currentValue( int _offset )
float Controller::value( int _offset )
float Controller::value( int offset )
{
return 0.5f;
if( m_bufferLastUpdated != s_periods )
{
updateValueBuffer();
}
return m_valueBuffer.values()[ offset ];
}
ValueBuffer * Controller::valueBuffer()
{
if( m_bufferLastUpdated != s_periods )
{
updateValueBuffer();
}
return &m_valueBuffer;
}
void Controller::updateValueBuffer()
{
float * values = m_valueBuffer.values();
for( int i = 0; i < m_valueBuffer.length(); i++ )
{
values[i] = 0.5f;
}
m_bufferLastUpdated = s_periods;
}
// Get position in frames
unsigned int Controller::runningFrames()
{
return s_frames;
return s_periods * engine::mixer()->framesPerPeriod();
}
@@ -133,7 +160,7 @@ unsigned int Controller::runningFrames()
// Get position in seconds
float Controller::runningTime()
{
return s_frames / engine::mixer()->processingSampleRate();
return runningFrames() / engine::mixer()->processingSampleRate();
}
@@ -149,7 +176,7 @@ void Controller::triggerFrameCounter()
emit s_controllers.at(i)->valueChanged();
}
s_frames += engine::mixer()->framesPerPeriod();
s_periods ++;
//emit s_signaler.triggerValueChanged();
}
@@ -157,7 +184,7 @@ void Controller::triggerFrameCounter()
void Controller::resetFrameCounter()
{
s_frames = 0;
s_periods = 0;
}

View File

@@ -34,6 +34,7 @@
#include "Mixer.h"
#include "LfoController.h"
#include "ControllerDialog.h"
#include "lmms_math.h"
//const float TWO_PI = 6.28318531f;
@@ -47,14 +48,26 @@ LfoController::LfoController( Model * _parent ) :
this, tr( "Oscillator waveform" ) ),
m_multiplierModel( 0, 0, 2, this, tr( "Frequency Multiplier" ) ),
m_duration( 1000 ),
m_phaseCorrection( 0 ),
m_phaseOffset( 0 ),
m_phaseOffset( 0 ),
m_currentPhase( 0 ),
m_sampleFunction( &Oscillator::sinSample ),
m_userDefSampleBuffer( new SampleBuffer )
{
setSampleExact( true );
connect( &m_waveModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleFunction() ) );
connect( &m_speedModel, SIGNAL( dataChanged() ),
this, SLOT( updateDuration() ) );
connect( &m_multiplierModel, SIGNAL( dataChanged() ),
this, SLOT( updateDuration() ) );
connect( engine::mixer(), SIGNAL( sampleRateChanged() ),
this, SLOT( updateDuration() ) );
connect( engine::getSong(), SIGNAL( playbackStateChanged() ),
this, SLOT( updatePhase() ) );
updateDuration();
}
@@ -72,84 +85,56 @@ LfoController::~LfoController()
}
// This code took forever to get right. It can
// definately be optimized.
// The code should probably be integrated with the oscillator class. But I
// don't know how to use oscillator because it is so confusing
float LfoController::value( int _offset )
void LfoController::updateValueBuffer()
{
int frame = runningFrames() + _offset + m_phaseCorrection;
m_phaseOffset = m_phaseModel.value() / 360.0;
float * values = m_valueBuffer.values();
float phase = m_currentPhase + m_phaseOffset;
for( int i = 0; i < m_valueBuffer.length(); i++ )
{
const float currentSample = m_sampleFunction != NULL
? m_sampleFunction( phase )
: m_userDefSampleBuffer->userWaveSample( phase );
values[i] = m_baseModel.value() + ( m_amountModel.value() * currentSample / 2.0f );
//If the song is playing, sync the value with the time of the song.
if( engine::getSong()->isPlaying() || engine::getSong()->isExporting() )
{
// The new duration in frames
// (Samples/Second) / (periods/second) = (Samples/cycle)
float newDurationF =
engine::mixer()->processingSampleRate() *
m_speedModel.value();
switch(m_multiplierModel.value() )
{
case 1:
newDurationF /= 100.0;
break;
case 2:
newDurationF *= 100.0;
break;
default:
break;
}
m_phaseOffset = qRound(
m_phaseModel.value() * newDurationF / 360.0 );
int newDuration = static_cast<int>( newDurationF );
m_phaseCorrection = static_cast<int>(engine::getSong()->getTicks()*engine::framesPerTick())%newDuration
+ m_phaseOffset;
// re-run the first calculation again
frame = m_phaseCorrection + _offset;
phase += 1.0 / m_duration;
}
m_currentPhase = absFraction( phase - m_phaseOffset );
m_bufferLastUpdated = s_periods;
}
// speedModel 0..1 fast..slow 0ms..20000ms
// duration m_duration
//
// frames / (20seconds of frames)
float sampleFrame = float( frame+m_phaseOffset ) /
(engine::mixer()->processingSampleRate() * m_speedModel.value() );
void LfoController::updatePhase()
{
m_currentPhase = ( engine::getSong()->getTicks() * engine::framesPerTick() ) / m_duration;
}
void LfoController::updateDuration()
{
float newDurationF = engine::mixer()->processingSampleRate() * m_speedModel.value();
switch(m_multiplierModel.value() )
{
case 1:
sampleFrame *= 100.0;
newDurationF /= 100.0;
break;
case 2:
sampleFrame /= 100.0;
newDurationF *= 100.0;
break;
default:
break;
}
// 44100 frames/sec
return m_baseModel.value() + ( m_amountModel.value() *
( m_sampleFunction != NULL ?
m_sampleFunction(sampleFrame):
m_userDefSampleBuffer->userWaveSample(sampleFrame) )
/ 2.0f );
m_duration = newDurationF;
}
void LfoController::updateSampleFunction()
{
switch( m_waveModel.value() )

View File

@@ -51,6 +51,7 @@ PeakController::PeakController( Model * _parent,
Controller( Controller::PeakController, _parent, tr( "Peak Controller" ) ),
m_peakEffect( _peak_effect )
{
setSampleExact( true );
if( m_peakEffect )
{
connect( m_peakEffect, SIGNAL( destroyed( ) ),
@@ -74,18 +75,20 @@ PeakController::~PeakController()
}
float PeakController::value( int _offset )
void PeakController::updateValueBuffer()
{
if( m_peakEffect )
{
return m_peakEffect->lastSample();
m_valueBuffer.interpolate( m_peakEffect->previousSample(), m_peakEffect->lastSample() );
}
return( 0 );
else
{
m_valueBuffer.fill( 0 );
}
m_bufferLastUpdated = s_periods;
}
void PeakController::handleDestroyedEffect( )
{
// possible race condition...

View File

@@ -41,6 +41,7 @@ MidiController::MidiController( Model * _parent ) :
engine::mixer()->midiClient(), this, this, MidiPort::Input ),
m_lastValue( 0.0f )
{
setSampleExact( true );
connect( &m_midiPort, SIGNAL( modeChanged() ),
this, SLOT( updateName() ) );
}
@@ -55,14 +56,21 @@ MidiController::~MidiController()
float MidiController::value( int _offset )
void MidiController::updateValueBuffer()
{
return m_lastValue;
if( m_previousValue != m_lastValue )
{
m_valueBuffer.interpolate( m_previousValue, m_lastValue );
m_previousValue = m_lastValue;
}
else
{
m_valueBuffer.fill( m_lastValue );
}
m_bufferLastUpdated = s_periods;
}
void MidiController::updateName()
{
setName( QString("MIDI ch%1 ctrl%2").
@@ -86,6 +94,7 @@ void MidiController::processInEvent( const MidiEvent& event, const MidiTime& tim
m_midiPort.inputChannel() == 0 ) )
{
unsigned char val = event.controllerValue();
m_previousValue = m_lastValue;
m_lastValue = (float)( val ) / 127.0f;
emit valueChanged();
}