Files
lmms/src/core/EnvelopeAndLfoParameters.cpp
Tobias Doerffel 908a917021 EnvelopeAndLfoParameters: moved global instances management into helper class
It's a bad style to manage global instances of an object in the object
itself. Therefore introduced a nested helper class which manages all
instances of EnvelopeAndLfoParameters globally. It is now responsible
for global reset/triggers of LFOs. In contrast to previous state, this
is now done thread-safe. Fixes crashes for example while importing
MIDI files.
(cherry picked from commit 5f6c42f19c)
2010-05-21 14:14:00 +02:00

517 lines
13 KiB
C++

/*
* EnvelopeAndLfoParameters.cpp - class EnvelopeAndLfoParameters
*
* Copyright (c) 2004-2010 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <QtXml/QDomElement>
#include "EnvelopeAndLfoParameters.h"
#include "debug.h"
#include "engine.h"
#include "mixer.h"
#include "mmp.h"
#include "Oscillator.h"
// how long should be each envelope-segment maximal (e.g. attack)?
extern const float SECS_PER_ENV_SEGMENT = 5.0f;
// how long should be one LFO-oscillation maximal?
extern const float SECS_PER_LFO_OSCILLATION = 20.0f;
EnvelopeAndLfoParameters::LfoInstances * EnvelopeAndLfoParameters::s_lfoInstances = NULL;
void EnvelopeAndLfoParameters::LfoInstances::trigger()
{
QMutexLocker m( &m_lfoListMutex );
for( LfoList::Iterator it = m_lfos.begin();
it != m_lfos.end(); ++it )
{
( *it )->m_lfoFrame +=
engine::getMixer()->framesPerPeriod();
( *it )->m_bad_lfoShapeData = true;
}
}
void EnvelopeAndLfoParameters::LfoInstances::reset()
{
QMutexLocker m( &m_lfoListMutex );
for( LfoList::Iterator it = m_lfos.begin();
it != m_lfos.end(); ++it )
{
( *it )->m_lfoFrame = 0;
( *it )->m_bad_lfoShapeData = true;
}
}
void EnvelopeAndLfoParameters::LfoInstances::add( EnvelopeAndLfoParameters * lfo )
{
QMutexLocker m( &m_lfoListMutex );
m_lfos.append( lfo );
}
void EnvelopeAndLfoParameters::LfoInstances::remove( EnvelopeAndLfoParameters * lfo )
{
QMutexLocker m( &m_lfoListMutex );
m_lfos.removeAll( lfo );
}
EnvelopeAndLfoParameters::EnvelopeAndLfoParameters(
float _value_for_zero_amount,
Model * _parent ) :
Model( _parent ),
m_used( false ),
m_predelayModel( 0.0, 0.0, 1.0, 0.001, this, tr( "Predelay" ) ),
m_attackModel( 0.0, 0.0, 1.0, 0.001, this, tr( "Attack" ) ),
m_holdModel( 0.5, 0.0, 1.0, 0.001, this, tr( "Hold" ) ),
m_decayModel( 0.5, 0.0, 1.0, 0.001, this, tr( "Decay" ) ),
m_sustainModel( 0.5, 0.0, 1.0, 0.001, this, tr( "Sustain" ) ),
m_releaseModel( 0.1, 0.0, 1.0, 0.001, this, tr( "Release" ) ),
m_amountModel( 0.0, -1.0, 1.0, 0.005, this, tr( "Modulation" ) ),
m_valueForZeroAmount( _value_for_zero_amount ),
m_pahdEnv( NULL ),
m_rEnv( NULL ),
m_lfoPredelayModel( 0.0, 0.0, 1.0, 0.001, this, tr( "LFO Predelay" ) ),
m_lfoAttackModel( 0.0, 0.0, 1.0, 0.001, this, tr( "LFO Attack" ) ),
m_lfoSpeedModel( 0.1, 0.001, 1.0, 0.0001,
SECS_PER_LFO_OSCILLATION * 1000.0, this,
tr( "LFO speed" ) ),
m_lfoAmountModel( 0.0, -1.0, 1.0, 0.005, this, tr( "LFO Modulation" ) ),
m_lfoWaveModel( SineWave, 0, NumLfoShapes, this, tr( "LFO Wave Shape" ) ),
m_x100Model( false, this, tr( "Freq x 100" ) ),
m_controlEnvAmountModel( false, this, tr( "Modulate Env-Amount" ) ),
m_lfoFrame( 0 ),
m_lfoAmountIsZero( false ),
m_lfoShapeData( NULL )
{
if( s_lfoInstances == NULL )
{
s_lfoInstances = new LfoInstances();
}
instances()->add( this );
connect( &m_predelayModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_attackModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_holdModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_decayModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_sustainModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_releaseModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_amountModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_lfoPredelayModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_lfoAttackModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_lfoSpeedModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_lfoAmountModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_lfoWaveModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( &m_x100Model, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ) );
connect( engine::getMixer(), SIGNAL( sampleRateChanged() ),
this, SLOT( updateSampleVars() ) );
m_lfoShapeData =
new sample_t[engine::getMixer()->framesPerPeriod()];
updateSampleVars();
}
EnvelopeAndLfoParameters::~EnvelopeAndLfoParameters()
{
m_predelayModel.disconnect( this );
m_attackModel.disconnect( this );
m_holdModel.disconnect( this );
m_decayModel.disconnect( this );
m_sustainModel.disconnect( this );
m_releaseModel.disconnect( this );
m_amountModel.disconnect( this );
m_lfoPredelayModel.disconnect( this );
m_lfoAttackModel.disconnect( this );
m_lfoSpeedModel.disconnect( this );
m_lfoAmountModel.disconnect( this );
m_lfoWaveModel.disconnect( this );
m_x100Model.disconnect( this );
delete[] m_pahdEnv;
delete[] m_rEnv;
delete[] m_lfoShapeData;
instances()->remove( this );
if( instances()->isEmpty() )
{
delete instances();
s_lfoInstances = NULL;
}
}
inline sample_t EnvelopeAndLfoParameters::lfoShapeSample( fpp_t _frame_offset )
{
f_cnt_t frame = ( m_lfoFrame + _frame_offset ) % m_lfoOscillationFrames;
const float phase = frame / static_cast<float>(
m_lfoOscillationFrames );
sample_t shape_sample;
switch( m_lfoWaveModel.value() )
{
case TriangleWave:
shape_sample = Oscillator::triangleSample( phase );
break;
case SquareWave:
shape_sample = Oscillator::squareSample( phase );
break;
case SawWave:
shape_sample = Oscillator::sawSample( phase );
break;
case UserDefinedWave:
shape_sample = m_userWave.userWaveSample( phase );
break;
case SineWave:
default:
shape_sample = Oscillator::sinSample( phase );
break;
}
return shape_sample * m_lfoAmount;
}
void EnvelopeAndLfoParameters::updateLfoShapeData()
{
const fpp_t frames = engine::getMixer()->framesPerPeriod();
for( fpp_t offset = 0; offset < frames; ++offset )
{
m_lfoShapeData[offset] = lfoShapeSample( offset );
}
m_bad_lfoShapeData = false;
}
inline void EnvelopeAndLfoParameters::fillLfoLevel( float * _buf,
f_cnt_t _frame,
const fpp_t _frames )
{
if( m_lfoAmountIsZero || _frame <= m_lfoPredelayFrames )
{
for( fpp_t offset = 0; offset < _frames; ++offset )
{
*_buf++ = 0.0f;
}
return;
}
_frame -= m_lfoPredelayFrames;
if( m_bad_lfoShapeData )
{
updateLfoShapeData();
}
fpp_t offset = 0;
const float lafI = 1.0f / m_lfoAttackFrames;
for( ; offset < _frames && _frame < m_lfoAttackFrames; ++offset,
++_frame )
{
*_buf++ = m_lfoShapeData[offset] * _frame * lafI;
}
for( ; offset < _frames; ++offset )
{
*_buf++ = m_lfoShapeData[offset];
}
}
void EnvelopeAndLfoParameters::fillLevel( float * _buf, f_cnt_t _frame,
const f_cnt_t _release_begin,
const fpp_t _frames )
{
if( _frame < 0 || _release_begin < 0 )
{
return;
}
fillLfoLevel( _buf, _frame, _frames );
for( fpp_t offset = 0; offset < _frames; ++offset, ++_buf, ++_frame )
{
float env_level;
if( _frame < _release_begin )
{
if( _frame < m_pahdFrames )
{
env_level = m_pahdEnv[_frame];
}
else
{
env_level = m_sustainLevel;
}
}
else if( ( _frame - _release_begin ) < m_rFrames )
{
env_level = m_rEnv[_frame - _release_begin] *
( ( _release_begin < m_pahdFrames ) ?
m_pahdEnv[_release_begin] : m_sustainLevel );
}
else
{
env_level = 0.0f;
}
// at this point, *_buf is LFO level
*_buf = m_controlEnvAmountModel.value() ?
env_level * ( 0.5f + *_buf ) :
env_level + *_buf;
}
}
void EnvelopeAndLfoParameters::saveSettings( QDomDocument & _doc,
QDomElement & _parent )
{
m_predelayModel.saveSettings( _doc, _parent, "pdel" );
m_attackModel.saveSettings( _doc, _parent, "att" );
m_holdModel.saveSettings( _doc, _parent, "hold" );
m_decayModel.saveSettings( _doc, _parent, "dec" );
m_sustainModel.saveSettings( _doc, _parent, "sus" );
m_releaseModel.saveSettings( _doc, _parent, "rel" );
m_amountModel.saveSettings( _doc, _parent, "amt" );
m_lfoWaveModel.saveSettings( _doc, _parent, "lshp" );
m_lfoPredelayModel.saveSettings( _doc, _parent, "lpdel" );
m_lfoAttackModel.saveSettings( _doc, _parent, "latt" );
m_lfoSpeedModel.saveSettings( _doc, _parent, "lspd" );
m_lfoAmountModel.saveSettings( _doc, _parent, "lamt" );
m_x100Model.saveSettings( _doc, _parent, "x100" );
m_controlEnvAmountModel.saveSettings( _doc, _parent, "ctlenvamt" );
_parent.setAttribute( "userwavefile", m_userWave.audioFile() );
}
void EnvelopeAndLfoParameters::loadSettings( const QDomElement & _this )
{
m_predelayModel.loadSettings( _this, "pdel" );
m_attackModel.loadSettings( _this, "att" );
m_holdModel.loadSettings( _this, "hold" );
m_decayModel.loadSettings( _this, "dec" );
m_sustainModel.loadSettings( _this, "sus" );
m_releaseModel.loadSettings( _this, "rel" );
m_amountModel.loadSettings( _this, "amt" );
m_lfoWaveModel.loadSettings( _this, "lshp" );
m_lfoPredelayModel.loadSettings( _this, "lpdel" );
m_lfoAttackModel.loadSettings( _this, "latt" );
m_lfoSpeedModel.loadSettings( _this, "lspd" );
m_lfoAmountModel.loadSettings( _this, "lamt" );
m_x100Model.loadSettings( _this, "x100" );
m_controlEnvAmountModel.loadSettings( _this, "ctlenvamt" );
// ### TODO:
/* // Keep compatibility with version 2.1 file format
if( _this.hasAttribute( "lfosyncmode" ) )
{
m_lfoSpeedKnob->setSyncMode(
( TempoSyncKnob::TtempoSyncMode ) _this.attribute(
"lfosyncmode" ).toInt() );
}*/
m_userWave.setAudioFile( _this.attribute( "userwavefile" ) );
updateSampleVars();
}
void EnvelopeAndLfoParameters::updateSampleVars()
{
engine::getMixer()->lock();
const float frames_per_env_seg = SECS_PER_ENV_SEGMENT *
engine::getMixer()->processingSampleRate();
// TODO: Remove the expKnobVals, time should be linear
const f_cnt_t predelay_frames = static_cast<f_cnt_t>(
frames_per_env_seg *
expKnobVal( m_predelayModel.value() ) );
const f_cnt_t attack_frames = static_cast<f_cnt_t>( frames_per_env_seg *
expKnobVal( m_attackModel.value() ) );
const f_cnt_t hold_frames = static_cast<f_cnt_t>( frames_per_env_seg *
expKnobVal( m_holdModel.value() ) );
const f_cnt_t decay_frames = static_cast<f_cnt_t>( frames_per_env_seg *
expKnobVal( m_decayModel.value() *
m_sustainModel.value() ) );
m_sustainLevel = 1.0f - m_sustainModel.value();
m_amount = m_amountModel.value();
if( m_amount >= 0 )
{
m_amountAdd = ( 1.0f - m_amount ) * m_valueForZeroAmount;
}
else
{
m_amountAdd = m_valueForZeroAmount;
}
m_pahdFrames = predelay_frames + attack_frames + hold_frames +
decay_frames;
m_rFrames = static_cast<f_cnt_t>( frames_per_env_seg *
expKnobVal( m_releaseModel.value() ) );
if( static_cast<int>( floorf( m_amount * 1000.0f ) ) == 0 )
{
//m_pahdFrames = 0;
m_rFrames = 0;
}
delete[] m_pahdEnv;
delete[] m_rEnv;
m_pahdEnv = new sample_t[m_pahdFrames];
m_rEnv = new sample_t[m_rFrames];
const float aa = m_amountAdd;
for( f_cnt_t i = 0; i < predelay_frames; ++i )
{
m_pahdEnv[i] = aa;
}
f_cnt_t add = predelay_frames;
const float afI = ( 1.0f / attack_frames ) * m_amount;
for( f_cnt_t i = 0; i < attack_frames; ++i )
{
m_pahdEnv[add+i] = i * afI + aa;
}
add += attack_frames;
const float amsum = m_amount + m_amountAdd;
for( f_cnt_t i = 0; i < hold_frames; ++i )
{
m_pahdEnv[add + i] = amsum;
}
add += hold_frames;
const float dfI = (1.0 / decay_frames)*(m_sustainLevel-1)*m_amount;
for( f_cnt_t i = 0; i < decay_frames; ++i )
{
/*
m_pahdEnv[add + i] = ( m_sustainLevel + ( 1.0f -
(float)i / decay_frames ) *
( 1.0f - m_sustainLevel ) ) *
m_amount + m_amountAdd;
*/
m_pahdEnv[add + i] = amsum + i*dfI;
}
const float rfI = ( 1.0f / m_rFrames ) * m_amount;
for( f_cnt_t i = 0; i < m_rFrames; ++i )
{
m_rEnv[i] = (float)( m_rFrames - i ) * rfI;
}
// save this calculation in real-time-part
m_sustainLevel = m_sustainLevel * m_amount + m_amountAdd;
const float frames_per_lfo_oscillation = SECS_PER_LFO_OSCILLATION *
engine::getMixer()->processingSampleRate();
m_lfoPredelayFrames = static_cast<f_cnt_t>( frames_per_lfo_oscillation *
expKnobVal( m_lfoPredelayModel.value() ) );
m_lfoAttackFrames = static_cast<f_cnt_t>( frames_per_lfo_oscillation *
expKnobVal( m_lfoAttackModel.value() ) );
m_lfoOscillationFrames = static_cast<f_cnt_t>(
frames_per_lfo_oscillation *
m_lfoSpeedModel.value() );
if( m_x100Model.value() )
{
m_lfoOscillationFrames /= 100;
}
m_lfoAmount = m_lfoAmountModel.value() * 0.5f;
m_used = true;
if( static_cast<int>( floorf( m_lfoAmount * 1000.0f ) ) == 0 )
{
m_lfoAmountIsZero = true;
if( static_cast<int>( floorf( m_amount * 1000.0f ) ) == 0 )
{
m_used = false;
}
}
else
{
m_lfoAmountIsZero = false;
}
m_bad_lfoShapeData = true;
emit dataChanged();
engine::getMixer()->unlock();
}
#include "moc_EnvelopeAndLfoParameters.cxx"