mirror of
https://github.com/LMMS/lmms.git
synced 2026-03-14 12:08:33 -04:00
This saves CPU on certain instruments, like Monstro. There's no point in running the note beyond the end of the volume envelope, ever, so let's not do that.
389 lines
11 KiB
C++
389 lines
11 KiB
C++
/*
|
|
* InstrumentSoundShaping.cpp - implementation of class InstrumentSoundShaping
|
|
*
|
|
* Copyright (c) 2004-2009 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 "InstrumentSoundShaping.h"
|
|
#include "basic_filters.h"
|
|
#include "embed.h"
|
|
#include "engine.h"
|
|
#include "EnvelopeAndLfoParameters.h"
|
|
#include "Instrument.h"
|
|
#include "InstrumentTrack.h"
|
|
#include "NotePlayHandle.h"
|
|
|
|
|
|
|
|
const float CUT_FREQ_MULTIPLIER = 6000.0f;
|
|
const float RES_MULTIPLIER = 2.0f;
|
|
const float RES_PRECISION = 1000.0f;
|
|
|
|
|
|
// names for env- and lfo-targets - first is name being displayed to user
|
|
// and second one is used internally, e.g. for saving/restoring settings
|
|
const QString __targetNames[InstrumentSoundShaping::NumTargets][3] =
|
|
{
|
|
{ InstrumentSoundShaping::tr( "VOLUME" ), "vol",
|
|
InstrumentSoundShaping::tr( "Volume" ) },
|
|
/* InstrumentSoundShaping::tr( "Pan" ),
|
|
InstrumentSoundShaping::tr( "Pitch" ),*/
|
|
{ InstrumentSoundShaping::tr( "CUTOFF" ), "cut",
|
|
InstrumentSoundShaping::tr( "Cutoff frequency" ) },
|
|
{ InstrumentSoundShaping::tr( "RESO" ), "res",
|
|
InstrumentSoundShaping::tr( "Resonance" ) }
|
|
} ;
|
|
|
|
|
|
|
|
InstrumentSoundShaping::InstrumentSoundShaping(
|
|
InstrumentTrack * _instrument_track ) :
|
|
Model( _instrument_track, tr( "Envelopes/LFOs" ) ),
|
|
m_instrumentTrack( _instrument_track ),
|
|
m_filterEnabledModel( false, this ),
|
|
m_filterModel( this, tr( "Filter type" ) ),
|
|
m_filterCutModel( 14000.0, 1.0, 14000.0, 1.0, this, tr( "Cutoff frequency" ) ),
|
|
m_filterResModel( 0.5, basicFilters<>::minQ(), 10.0, 0.01, this, tr( "Q/Resonance" ) )
|
|
{
|
|
for( int i = 0; i < NumTargets; ++i )
|
|
{
|
|
float value_for_zero_amount = 0.0;
|
|
if( i == Volume )
|
|
{
|
|
value_for_zero_amount = 1.0;
|
|
}
|
|
m_envLfoParameters[i] = new EnvelopeAndLfoParameters(
|
|
value_for_zero_amount,
|
|
this );
|
|
m_envLfoParameters[i]->setDisplayName(
|
|
tr( __targetNames[i][2].toUtf8().constData() ) );
|
|
}
|
|
|
|
m_filterModel.addItem( tr( "LowPass" ), new PixmapLoader( "filter_lp" ) );
|
|
m_filterModel.addItem( tr( "HiPass" ), new PixmapLoader( "filter_hp" ) );
|
|
m_filterModel.addItem( tr( "BandPass csg" ), new PixmapLoader( "filter_bp" ) );
|
|
m_filterModel.addItem( tr( "BandPass czpg" ), new PixmapLoader( "filter_bp" ) );
|
|
m_filterModel.addItem( tr( "Notch" ), new PixmapLoader( "filter_notch" ) );
|
|
m_filterModel.addItem( tr( "Allpass" ), new PixmapLoader( "filter_ap" ) );
|
|
m_filterModel.addItem( tr( "Moog" ), new PixmapLoader( "filter_lp" ) );
|
|
m_filterModel.addItem( tr( "2x LowPass" ), new PixmapLoader( "filter_2lp" ) );
|
|
m_filterModel.addItem( tr( "RC LowPass 12dB" ), new PixmapLoader( "filter_lp" ) );
|
|
m_filterModel.addItem( tr( "RC BandPass 12dB" ), new PixmapLoader( "filter_bp" ) );
|
|
m_filterModel.addItem( tr( "RC HighPass 12dB" ), new PixmapLoader( "filter_hp" ) );
|
|
m_filterModel.addItem( tr( "RC LowPass 24dB" ), new PixmapLoader( "filter_lp" ) );
|
|
m_filterModel.addItem( tr( "RC BandPass 24dB" ), new PixmapLoader( "filter_bp" ) );
|
|
m_filterModel.addItem( tr( "RC HighPass 24dB" ), new PixmapLoader( "filter_hp" ) );
|
|
m_filterModel.addItem( tr( "Vocal Formant Filter" ), new PixmapLoader( "filter_hp" ) );
|
|
}
|
|
|
|
|
|
|
|
|
|
InstrumentSoundShaping::~InstrumentSoundShaping()
|
|
{
|
|
}
|
|
|
|
|
|
|
|
|
|
float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t frame )
|
|
{
|
|
f_cnt_t envReleaseBegin = frame - n->releaseFramesDone() + n->framesBeforeRelease();
|
|
|
|
if( n->isReleased() == false )
|
|
{
|
|
envReleaseBegin += engine::mixer()->framesPerPeriod();
|
|
}
|
|
|
|
float level;
|
|
m_envLfoParameters[Volume]->fillLevel( &level, frame, envReleaseBegin, 1 );
|
|
|
|
return level;
|
|
}
|
|
|
|
|
|
|
|
|
|
void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
|
|
const fpp_t frames,
|
|
NotePlayHandle* n )
|
|
{
|
|
const f_cnt_t envTotalFrames = n->totalFramesPlayed();
|
|
f_cnt_t envReleaseBegin = envTotalFrames - n->releaseFramesDone() + n->framesBeforeRelease();
|
|
|
|
if( n->isReleased() == false )
|
|
{
|
|
envReleaseBegin += engine::mixer()->framesPerPeriod();
|
|
}
|
|
|
|
// because of optimizations, there's special code for several cases:
|
|
// - cut- and res-lfo/envelope active
|
|
// - cut-lfo/envelope active
|
|
// - res-lfo/envelope active
|
|
// - no lfo/envelope active but filter is used
|
|
|
|
// only use filter, if it is really needed
|
|
|
|
if( m_filterEnabledModel.value() )
|
|
{
|
|
int old_filter_cut = 0;
|
|
int old_filter_res = 0;
|
|
|
|
if( n->m_filter == NULL )
|
|
{
|
|
n->m_filter = new basicFilters<>( engine::mixer()->processingSampleRate() );
|
|
}
|
|
n->m_filter->setFilterType( m_filterModel.value() );
|
|
|
|
#ifdef __GNUC__
|
|
float cut_buf[frames];
|
|
float res_buf[frames];
|
|
#else
|
|
float * cut_buf = NULL;
|
|
float * res_buf = NULL;
|
|
#endif
|
|
|
|
if( m_envLfoParameters[Cut]->isUsed() )
|
|
{
|
|
#ifndef __GNUC__
|
|
cut_buf = new float[frames];
|
|
#endif
|
|
m_envLfoParameters[Cut]->fillLevel( cut_buf, envTotalFrames, envReleaseBegin, frames );
|
|
}
|
|
if( m_envLfoParameters[Resonance]->isUsed() )
|
|
{
|
|
#ifndef __GNUC__
|
|
res_buf = new float[frames];
|
|
#endif
|
|
m_envLfoParameters[Resonance]->fillLevel( res_buf, envTotalFrames, envReleaseBegin, frames );
|
|
}
|
|
|
|
const float fcv = m_filterCutModel.value();
|
|
const float frv = m_filterResModel.value();
|
|
|
|
if( m_envLfoParameters[Cut]->isUsed() &&
|
|
m_envLfoParameters[Resonance]->isUsed() )
|
|
{
|
|
for( fpp_t frame = 0; frame < frames; ++frame )
|
|
{
|
|
const float new_cut_val = EnvelopeAndLfoParameters::expKnobVal( cut_buf[frame] ) *
|
|
CUT_FREQ_MULTIPLIER + fcv;
|
|
|
|
const float new_res_val = frv + RES_MULTIPLIER * res_buf[frame];
|
|
|
|
if( static_cast<int>( new_cut_val ) != old_filter_cut ||
|
|
static_cast<int>( new_res_val*RES_PRECISION ) != old_filter_res )
|
|
{
|
|
n->m_filter->calcFilterCoeffs( new_cut_val, new_res_val );
|
|
old_filter_cut = static_cast<int>( new_cut_val );
|
|
old_filter_res = static_cast<int>( new_res_val*RES_PRECISION );
|
|
}
|
|
|
|
buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 );
|
|
buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 );
|
|
}
|
|
}
|
|
else if( m_envLfoParameters[Cut]->isUsed() )
|
|
{
|
|
for( fpp_t frame = 0; frame < frames; ++frame )
|
|
{
|
|
float new_cut_val = EnvelopeAndLfoParameters::expKnobVal( cut_buf[frame] ) *
|
|
CUT_FREQ_MULTIPLIER + fcv;
|
|
|
|
if( static_cast<int>( new_cut_val ) != old_filter_cut )
|
|
{
|
|
n->m_filter->calcFilterCoeffs( new_cut_val, frv );
|
|
old_filter_cut = static_cast<int>( new_cut_val );
|
|
}
|
|
|
|
buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 );
|
|
buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 );
|
|
}
|
|
}
|
|
else if( m_envLfoParameters[Resonance]->isUsed() )
|
|
{
|
|
for( fpp_t frame = 0; frame < frames; ++frame )
|
|
{
|
|
float new_res_val = frv + RES_MULTIPLIER * res_buf[frame];
|
|
|
|
if( static_cast<int>( new_res_val*RES_PRECISION ) != old_filter_res )
|
|
{
|
|
n->m_filter->calcFilterCoeffs( fcv, new_res_val );
|
|
old_filter_res = static_cast<int>( new_res_val*RES_PRECISION );
|
|
}
|
|
|
|
buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 );
|
|
buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
n->m_filter->calcFilterCoeffs( fcv, frv );
|
|
|
|
for( fpp_t frame = 0; frame < frames; ++frame )
|
|
{
|
|
buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 );
|
|
buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 );
|
|
}
|
|
}
|
|
|
|
#ifndef __GNUC__
|
|
delete[] cut_buf;
|
|
delete[] res_buf;
|
|
#endif
|
|
}
|
|
|
|
if( m_envLfoParameters[Volume]->isUsed() )
|
|
{
|
|
#ifdef __GNUC__
|
|
float vol_buf[frames];
|
|
#else
|
|
float * vol_buf = new float[frames];
|
|
#endif
|
|
m_envLfoParameters[Volume]->fillLevel( vol_buf, envTotalFrames, envReleaseBegin, frames );
|
|
|
|
for( fpp_t frame = 0; frame < frames; ++frame )
|
|
{
|
|
float vol_level = vol_buf[frame];
|
|
vol_level = vol_level * vol_level;
|
|
buffer[frame][0] = vol_level * buffer[frame][0];
|
|
buffer[frame][1] = vol_level * buffer[frame][1];
|
|
}
|
|
#ifndef __GNUC__
|
|
delete[] vol_buf;
|
|
#endif
|
|
}
|
|
|
|
/* else if( m_envLfoParameters[Volume]->isUsed() == false && m_envLfoParameters[PANNING]->isUsed() )
|
|
{
|
|
// only use panning-envelope...
|
|
for( fpp_t frame = 0; frame < frames; ++frame )
|
|
{
|
|
float vol_level = pan_buf[frame];
|
|
vol_level = vol_level*vol_level;
|
|
for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl )
|
|
{
|
|
buffer[frame][chnl] = vol_level * buffer[frame][chnl];
|
|
}
|
|
}
|
|
}*/
|
|
}
|
|
|
|
|
|
|
|
|
|
f_cnt_t InstrumentSoundShaping::envFrames( const bool _only_vol ) const
|
|
{
|
|
f_cnt_t ret_val = m_envLfoParameters[Volume]->PAHD_Frames();
|
|
|
|
if( _only_vol == false )
|
|
{
|
|
for( int i = Volume+1; i < NumTargets; ++i )
|
|
{
|
|
if( m_envLfoParameters[i]->isUsed() &&
|
|
m_envLfoParameters[i]->PAHD_Frames() > ret_val )
|
|
{
|
|
ret_val = m_envLfoParameters[i]->PAHD_Frames();
|
|
}
|
|
}
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
|
|
|
|
|
|
f_cnt_t InstrumentSoundShaping::releaseFrames() const
|
|
{
|
|
if( m_envLfoParameters[Volume]->isUsed() )
|
|
{
|
|
return m_envLfoParameters[Volume]->releaseFrames();
|
|
}
|
|
f_cnt_t ret_val = m_instrumentTrack->instrument()
|
|
? m_instrumentTrack->instrument()->desiredReleaseFrames()
|
|
: 0;
|
|
|
|
for( int i = Volume+1; i < NumTargets; ++i )
|
|
{
|
|
if( m_envLfoParameters[i]->isUsed() )
|
|
{
|
|
ret_val = qMax( ret_val, m_envLfoParameters[i]->releaseFrames() );
|
|
}
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
|
|
|
|
|
|
void InstrumentSoundShaping::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
|
{
|
|
m_filterModel.saveSettings( _doc, _this, "ftype" );
|
|
m_filterCutModel.saveSettings( _doc, _this, "fcut" );
|
|
m_filterResModel.saveSettings( _doc, _this, "fres" );
|
|
m_filterEnabledModel.saveSettings( _doc, _this, "fwet" );
|
|
|
|
for( int i = 0; i < NumTargets; ++i )
|
|
{
|
|
m_envLfoParameters[i]->saveState( _doc, _this ).setTagName(
|
|
m_envLfoParameters[i]->nodeName() +
|
|
QString( __targetNames[i][1] ).toLower() );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void InstrumentSoundShaping::loadSettings( const QDomElement & _this )
|
|
{
|
|
m_filterModel.loadSettings( _this, "ftype" );
|
|
m_filterCutModel.loadSettings( _this, "fcut" );
|
|
m_filterResModel.loadSettings( _this, "fres" );
|
|
m_filterEnabledModel.loadSettings( _this, "fwet" );
|
|
|
|
QDomNode node = _this.firstChild();
|
|
while( !node.isNull() )
|
|
{
|
|
if( node.isElement() )
|
|
{
|
|
for( int i = 0; i < NumTargets; ++i )
|
|
{
|
|
if( node.nodeName() ==
|
|
m_envLfoParameters[i]->nodeName() +
|
|
QString( __targetNames[i][1] ).
|
|
toLower() )
|
|
{
|
|
m_envLfoParameters[i]->restoreState( node.toElement() );
|
|
}
|
|
}
|
|
}
|
|
node = node.nextSibling();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
#include "moc_InstrumentSoundShaping.cxx"
|
|
|