code:
/*
 * ZynAddSubFx.cpp - ZynAddSubxFX-embedding plugin
 *
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * 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 "lmmsconfig.h"

#include <QDir>
#include <QDomDocument>
#include <QTemporaryFile>
#include <QtGlobal>
#include <QDropEvent>
#include <QGridLayout>
#include <QPushButton>

#include "ZynAddSubFx.h"
#include "ConfigManager.h"
#include "Engine.h"
#include "Knob.h"
#include "LedCheckbox.h"
#include "DataFile.h"
#include "InstrumentPlayHandle.h"
#include "InstrumentTrack.h"
#include "gui_templates.h"
#include "Song.h"
#include "StringPairDrag.h"
#include "RemoteZynAddSubFx.h"
#include "LocalZynAddSubFx.h"
#include "Mixer.h"
#include "ControllerConnection.h"
#include "Clipboard.h"

#include "embed.h"
#include "plugin_export.h"

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT zynaddsubfx_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"ZynAddSubFX",
	QT_TRANSLATE_NOOP( "PluginBrowser",
			"Embedded ZynAddSubFX" ),
	"Tobias Doerffel <tobydox/at/users.sf.net>",
	0x0100,
	Plugin::Instrument,
	new PluginPixmapLoader( "logo" ),
	"xiz",
	NULL,
} ;

}

ZynAddSubFxRemotePlugin::ZynAddSubFxRemotePlugin() :
	RemotePlugin()
{
	init( "RemoteZynAddSubFx", false );
}

ZynAddSubFxRemotePlugin::~ZynAddSubFxRemotePlugin()
{
}

bool ZynAddSubFxRemotePlugin::processMessage( const message & _m )
{
	switch( _m.id )
	{
		case IdHideUI:
			emit clickedCloseButton();
			return true;
		default:
			break;
	}

	return RemotePlugin::processMessage( _m );
}

ZynAddSubFxInstrument::ZynAddSubFxInstrument(
									InstrumentTrack * _instrumentTrack ) :
	Instrument( _instrumentTrack, &zynaddsubfx_plugin_descriptor ),
	m_hasGUI( false ),
	m_plugin( NULL ),
	m_remotePlugin( NULL ),
	m_portamentoModel( 0, 0, 127, 1, this, tr( "Portamento" ) ),
	m_filterFreqModel( 64, 0, 127, 1, this, tr( "Filter frequency" ) ),
	m_filterQModel( 64, 0, 127, 1, this, tr( "Filter resonance" ) ),
	m_bandwidthModel( 64, 0, 127, 1, this, tr( "Bandwidth" ) ),
	m_fmGainModel( 127, 0, 127, 1, this, tr( "FM gain" ) ),
	m_resCenterFreqModel( 64, 0, 127, 1, this, tr( "Resonance center frequency" ) ),
	m_resBandwidthModel( 64, 0, 127, 1, this, tr( "Resonance bandwidth" ) ),
	m_forwardMidiCcModel( true, this, tr( "Forward MIDI control change events" ) )
{
	initPlugin();

	connect( &m_portamentoModel, SIGNAL( dataChanged() ),
			this, SLOT( updatePortamento() ), Qt::DirectConnection );
	connect( &m_filterFreqModel, SIGNAL( dataChanged() ),
			this, SLOT( updateFilterFreq() ), Qt::DirectConnection );
	connect( &m_filterQModel, SIGNAL( dataChanged() ),
			this, SLOT( updateFilterQ() ), Qt::DirectConnection );
	connect( &m_bandwidthModel, SIGNAL( dataChanged() ),
			this, SLOT( updateBandwidth() ), Qt::DirectConnection );
	connect( &m_fmGainModel, SIGNAL( dataChanged() ),
			this, SLOT( updateFmGain() ), Qt::DirectConnection );
	connect( &m_resCenterFreqModel, SIGNAL( dataChanged() ),
			this, SLOT( updateResCenterFreq() ), Qt::DirectConnection );
	connect( &m_resBandwidthModel, SIGNAL( dataChanged() ),
			this, SLOT( updateResBandwidth() ), Qt::DirectConnection );

	// now we need a play-handle which cares for calling play()
	InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrumentTrack );
	Engine::mixer()->addPlayHandle( iph );

	connect( Engine::mixer(), SIGNAL( sampleRateChanged() ),
			this, SLOT( reloadPlugin() ) );

	connect( instrumentTrack()->pitchRangeModel(), SIGNAL( dataChanged() ),
			this, SLOT( updatePitchRange() ), Qt::DirectConnection );
}

ZynAddSubFxInstrument::~ZynAddSubFxInstrument()
{
	Engine::mixer()->removePlayHandlesOfTypes( instrumentTrack(),
				PlayHandle::TypeNotePlayHandle
				| PlayHandle::TypeInstrumentPlayHandle );

	m_pluginMutex.lock();
	delete m_plugin;
	delete m_remotePlugin;
	m_plugin = NULL;
	m_remotePlugin = NULL;
	m_pluginMutex.unlock();
}

void ZynAddSubFxInstrument::saveSettings( QDomDocument & _doc,
											QDomElement & _this )
{
	m_portamentoModel.saveSettings( _doc, _this, "portamento" );
	m_filterFreqModel.saveSettings( _doc, _this, "filterfreq" );
	m_filterQModel.saveSettings( _doc, _this, "filterq" );
	m_bandwidthModel.saveSettings( _doc, _this, "bandwidth" );
	m_fmGainModel.saveSettings( _doc, _this, "fmgain" );
	m_resCenterFreqModel.saveSettings( _doc, _this, "rescenterfreq" );
	m_resBandwidthModel.saveSettings( _doc, _this, "resbandwidth" );

	QString modifiedControllers;
	for( QMap<int, bool>::ConstIterator it = m_modifiedControllers.begin();
									it != m_modifiedControllers.end(); ++it )
	{
		if( it.value() )
		{
			modifiedControllers += QString( "%1," ).arg( it.key() );
		}
	}
	_this.setAttribute( "modifiedcontrollers", modifiedControllers );

	m_forwardMidiCcModel.saveSettings( _doc, _this, "forwardmidicc" );

	QTemporaryFile tf;
	if( tf.open() )
	{
		const std::string fn = QSTR_TO_STDSTR(
									QDir::toNativeSeparators( tf.fileName() ) );
		m_pluginMutex.lock();
		if( m_remotePlugin )
		{
			m_remotePlugin->lock();
			m_remotePlugin->sendMessage( RemotePlugin::message( IdSaveSettingsToFile ).addString( fn ) );
			m_remotePlugin->waitForMessage( IdSaveSettingsToFile );
			m_remotePlugin->unlock();
		}
		else
		{
			m_plugin->saveXML( fn );
		}
		m_pluginMutex.unlock();
		QByteArray a = tf.readAll();
		QDomDocument doc( "mydoc" );
		if( doc.setContent( a ) )
		{
			QDomNode n = _doc.importNode( doc.documentElement(), true );
			_this.appendChild( n );
		}
	}
}

void ZynAddSubFxInstrument::loadSettings( const QDomElement & _this )
{
	if( !_this.hasChildNodes() )
	{
		return;
	}

	m_portamentoModel.loadSettings( _this, "portamento" );
	m_filterFreqModel.loadSettings( _this, "filterfreq" );
	m_filterQModel.loadSettings( _this, "filterq" );
	m_bandwidthModel.loadSettings( _this, "bandwidth" );
	m_fmGainModel.loadSettings( _this, "fmgain" );
	m_resCenterFreqModel.loadSettings( _this, "rescenterfreq" );
	m_resBandwidthModel.loadSettings( _this, "resbandwidth" );
	m_forwardMidiCcModel.loadSettings( _this, "forwardmidicc" );

	QDomDocument doc;
	QDomElement data = _this.firstChildElement( "ZynAddSubFX-data" );
	if( data.isNull() )
	{
		data = _this.firstChildElement();
	}
	doc.appendChild( doc.importNode( data, true ) );

	QTemporaryFile tf;
	if( tf.open() )
	{
		QByteArray a = doc.toString( 0 ).toUtf8();
		tf.write( a );
		tf.flush();

		const std::string fn = QSTR_TO_STDSTR( QDir::toNativeSeparators( tf.fileName() ) );
		m_pluginMutex.lock();
		if( m_remotePlugin )
		{
			m_remotePlugin->lock();
			m_remotePlugin->sendMessage( RemotePlugin::message( IdLoadSettingsFromFile ).addString( fn ) );
			m_remotePlugin->waitForMessage( IdLoadSettingsFromFile );
			m_remotePlugin->unlock();
		}
		else
		{
			m_plugin->loadXML( fn );
		}
		m_pluginMutex.unlock();

		m_modifiedControllers.clear();
		for( const QString & c : _this.attribute( "modifiedcontrollers" ).split( ',' ) )
		{
			if( !c.isEmpty() )
			{
				switch( c.toInt() )
				{
					case C_portamento: updatePortamento(); break;
					case C_filtercutoff: updateFilterFreq(); break;
					case C_filterq: updateFilterQ(); break;
					case C_bandwidth: updateBandwidth(); break;
					case C_fmamp: updateFmGain(); break;
					case C_resonance_center: updateResCenterFreq(); break;
					case C_resonance_bandwidth: updateResBandwidth(); break;
					default:
						break;
				}
			}
		}

		emit settingsChanged();
	}
	emit instrumentTrack()->pitchModel()->dataChanged();
}

void ZynAddSubFxInstrument::loadFile( const QString & _file )
{
	const std::string fn = QSTR_TO_STDSTR( _file );
	if( m_remotePlugin )
	{
		m_remotePlugin->lock();
		m_remotePlugin->sendMessage( RemotePlugin::message( IdLoadPresetFile ).addString( fn ) );
		m_remotePlugin->waitForMessage( IdLoadPresetFile );
		m_remotePlugin->unlock();
	}
	else
	{
		m_pluginMutex.lock();
		m_plugin->loadPreset( fn );
		m_pluginMutex.unlock();
	}

	instrumentTrack()->setName( QFileInfo( _file ).baseName().replace( QRegExp( "^[0-9]{4}-" ), QString() ) );

	m_modifiedControllers.clear();

	emit settingsChanged();
}

QString ZynAddSubFxInstrument::nodeName() const
{
	return zynaddsubfx_plugin_descriptor.name;
}

void ZynAddSubFxInstrument::play( sampleFrame * _buf )
{
	if (!m_pluginMutex.tryLock(Engine::getSong()->isExporting() ? -1 : 0)) {return;}
	if( m_remotePlugin )
	{
		m_remotePlugin->process( NULL, _buf );
	}
	else
	{
		m_plugin->processAudio( _buf );
	}
	m_pluginMutex.unlock();
	instrumentTrack()->processAudioBuffer( _buf, Engine::mixer()->framesPerPeriod(), NULL );
}

bool ZynAddSubFxInstrument::handleMidiEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset )
{
	// do not forward external MIDI Control Change events if the according
	// LED is not checked
	if( event.type() == MidiControlChange &&
		event.sourcePort() != this &&
		m_forwardMidiCcModel.value() == false )
	{
		return true;
	}

	MidiEvent localEvent = event;
	localEvent.setChannel( 0 );
	m_pluginMutex.lock();
	if( m_remotePlugin )
	{
		m_remotePlugin->processMidiEvent( localEvent, 0 );
	}
	else
	{
		m_plugin->processMidiEvent( localEvent );
	}
	m_pluginMutex.unlock();

	return true;
}

void ZynAddSubFxInstrument::reloadPlugin()
{
	// save state of current plugin instance
	DataFile m( DataFile::InstrumentTrackSettings );
	saveSettings( m, m.content() );

	// init plugin (will delete current one and create a new instance)
	initPlugin();

	// and load the settings again
	loadSettings( m.content() );
}

void ZynAddSubFxInstrument::updatePitchRange()
{
	m_pluginMutex.lock();
	if( m_remotePlugin )
	{
		m_remotePlugin->sendMessage( RemotePlugin::message( IdZasfSetPitchWheelBendRange ).
											addInt( instrumentTrack()->midiPitchRange() ) );
	}
	else
	{
		m_plugin->setPitchWheelBendRange( instrumentTrack()->midiPitchRange() );
	}
	m_pluginMutex.unlock();
}

#define GEN_CC_SLOT(slotname,midictl,modelname)						\
			void ZynAddSubFxInstrument::slotname()					\
			{														\
				sendControlChange( midictl, modelname.value() );	\
				m_modifiedControllers[midictl] = true;				\
			}

GEN_CC_SLOT(updatePortamento,C_portamento,m_portamentoModel);
GEN_CC_SLOT(updateFilterFreq,C_filtercutoff,m_filterFreqModel);
GEN_CC_SLOT(updateFilterQ,C_filterq,m_filterQModel);
GEN_CC_SLOT(updateBandwidth,C_bandwidth,m_bandwidthModel);
GEN_CC_SLOT(updateFmGain,C_fmamp,m_fmGainModel);
GEN_CC_SLOT(updateResCenterFreq,C_resonance_center,m_resCenterFreqModel);
GEN_CC_SLOT(updateResBandwidth,C_resonance_bandwidth,m_resBandwidthModel);

void ZynAddSubFxInstrument::initPlugin()
{
	m_pluginMutex.lock();
	delete m_plugin;
	delete m_remotePlugin;
	m_plugin = NULL;
	m_remotePlugin = NULL;

	if( m_hasGUI )
	{
		m_remotePlugin = new ZynAddSubFxRemotePlugin();
		m_remotePlugin->lock();
		m_remotePlugin->waitForInitDone( false );

		m_remotePlugin->sendMessage(
			RemotePlugin::message( IdZasfLmmsWorkingDirectory ).
				addString(
					QSTR_TO_STDSTR(
						QString( ConfigManager::inst()->workingDir() ) ) ) );
		m_remotePlugin->sendMessage(
			RemotePlugin::message( IdZasfPresetDirectory ).
				addString(
					QSTR_TO_STDSTR(
						QDir( ConfigManager::inst()->factoryPresetsDir() +
								"/ZynAddSubFX" ).absolutePath() ) ) );

		m_remotePlugin->updateSampleRate( Engine::mixer()->processingSampleRate() );

		// temporary workaround until the VST synchronization feature gets stripped out of the RemotePluginClient class
		// causing not to send buffer size information requests
		m_remotePlugin->sendMessage( RemotePlugin::message( IdBufferSizeInformation ).addInt( Engine::mixer()->framesPerPeriod() ) );

		m_remotePlugin->showUI();
		m_remotePlugin->unlock();
	}
	else
	{
		m_plugin = new LocalZynAddSubFx;
		m_plugin->setSampleRate( Engine::mixer()->processingSampleRate() );
		m_plugin->setBufferSize( Engine::mixer()->framesPerPeriod() );
	}

	m_pluginMutex.unlock();
}

void ZynAddSubFxInstrument::sendControlChange( MidiControllers midiCtl, float value )
{
	handleMidiEvent( MidiEvent( MidiControlChange, instrumentTrack()->midiPort()->realOutputChannel(), midiCtl, (int) value, this ) );
}

PluginView * ZynAddSubFxInstrument::instantiateView( QWidget * _parent )
{
	return new ZynAddSubFxView( this, _parent );
}

ZynAddSubFxView::ZynAddSubFxView( Instrument * _instrument, QWidget * _parent ) :
        InstrumentViewFixedSize( _instrument, _parent )
{
	setAutoFillBackground( true );
	QPalette pal;
	pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap(
								"artwork" ) );
	setPalette( pal );

	QGridLayout * l = new QGridLayout( this );
	l->setContentsMargins( 20, 80, 10, 10 );
	l->setVerticalSpacing( 16 );
	l->setHorizontalSpacing( 10 );

	m_portamento = new Knob( knobBright_26, this );
	m_portamento->setHintText( tr( "Portamento:" ), "" );
	m_portamento->setLabel( tr( "PORT" ) );

	m_filterFreq = new Knob( knobBright_26, this );
	m_filterFreq->setHintText( tr( "Filter frequency:" ), "" );
	m_filterFreq->setLabel( tr( "FREQ" ) );

	m_filterQ = new Knob( knobBright_26, this );
	m_filterQ->setHintText( tr( "Filter resonance:" ), "" );
	m_filterQ->setLabel( tr( "RES" ) );

	m_bandwidth = new Knob( knobBright_26, this );
	m_bandwidth->setHintText( tr( "Bandwidth:" ), "" );
	m_bandwidth->setLabel( tr( "BW" ) );

	m_fmGain = new Knob( knobBright_26, this );
	m_fmGain->setHintText( tr( "FM gain:" ), "" );
	m_fmGain->setLabel( tr( "FM GAIN" ) );

	m_resCenterFreq = new Knob( knobBright_26, this );
	m_resCenterFreq->setHintText( tr( "Resonance center frequency:" ), "" );
	m_resCenterFreq->setLabel( tr( "RES CF" ) );

	m_resBandwidth = new Knob( knobBright_26, this );
	m_resBandwidth->setHintText( tr( "Resonance bandwidth:" ), "" );
	m_resBandwidth->setLabel( tr( "RES BW" ) );

	m_forwardMidiCC = new LedCheckBox( tr( "Forward MIDI control changes" ), this );

	m_toggleUIButton = new QPushButton( tr( "Show GUI" ), this );
	m_toggleUIButton->setCheckable( true );
	m_toggleUIButton->setChecked( false );
	m_toggleUIButton->setIcon( embed::getIconPixmap( "zoom" ) );
	m_toggleUIButton->setFont( pointSize<8>( m_toggleUIButton->font() ) );
	connect( m_toggleUIButton, SIGNAL( toggled( bool ) ), this,
							SLOT( toggleUI() ) );

	l->addWidget( m_toggleUIButton, 0, 0, 1, 4 );
	l->setRowStretch( 1, 5 );
	l->addWidget( m_portamento, 2, 0 );
	l->addWidget( m_filterFreq, 2, 1 );
	l->addWidget( m_filterQ, 2, 2 );
	l->addWidget( m_bandwidth, 2, 3 );
	l->addWidget( m_fmGain, 3, 0 );
	l->addWidget( m_resCenterFreq, 3, 1 );
	l->addWidget( m_resBandwidth, 3, 2 );
	l->addWidget( m_forwardMidiCC, 4, 0, 1, 4 );

	l->setRowStretch( 5, 10 );
	l->setColumnStretch( 4, 10 );

	setAcceptDrops( true );
}

ZynAddSubFxView::~ZynAddSubFxView()
{
}

void ZynAddSubFxView::dragEnterEvent( QDragEnterEvent * _dee )
{
	// For mimeType() and MimeType enum class
	using namespace Clipboard;

	if( _dee->mimeData()->hasFormat( mimeType( MimeType::StringPair ) ) )
	{
		QString txt = _dee->mimeData()->data(
						mimeType( MimeType::StringPair ) );
		if( txt.section( ':', 0, 0 ) == "pluginpresetfile" )
		{
			_dee->acceptProposedAction();
		}
		else
		{
			_dee->ignore();
		}
	}
	else
	{
		_dee->ignore();
	}
}

void ZynAddSubFxView::dropEvent( QDropEvent * _de )
{
	const QString type = StringPairDrag::decodeKey( _de );
	const QString value = StringPairDrag::decodeValue( _de );
	if( type == "pluginpresetfile" )
	{
		castModel<ZynAddSubFxInstrument>()->loadFile( value );
		_de->accept();
		return;
	}
	_de->ignore();
}

void ZynAddSubFxView::modelChanged()
{
	ZynAddSubFxInstrument * m = castModel<ZynAddSubFxInstrument>();

	// set models for controller knobs
	m_portamento->setModel( &m->m_portamentoModel );
	m_filterFreq->setModel( &m->m_filterFreqModel );
	m_filterQ->setModel( &m->m_filterQModel );
	m_bandwidth->setModel( &m->m_bandwidthModel );
	m_fmGain->setModel( &m->m_fmGainModel );
	m_resCenterFreq->setModel( &m->m_resCenterFreqModel );
	m_resBandwidth->setModel( &m->m_resBandwidthModel );

	m_forwardMidiCC->setModel( &m->m_forwardMidiCcModel );

	m_toggleUIButton->setChecked( m->m_hasGUI );
}

void ZynAddSubFxView::toggleUI()
{
	ZynAddSubFxInstrument * model = castModel<ZynAddSubFxInstrument>();
	if( model->m_hasGUI != m_toggleUIButton->isChecked() )
	{
		model->m_hasGUI = m_toggleUIButton->isChecked();
		model->reloadPlugin();

		if( model->m_remotePlugin )
		{
			connect( model->m_remotePlugin, SIGNAL( clickedCloseButton() ),
						m_toggleUIButton, SLOT( toggle() ) );
		}
	}
}

extern "C"
{

// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main(Model * m, void *)
{
	return new ZynAddSubFxInstrument(static_cast<InstrumentTrack *>(m));
}

}


code:
/*
 * ZynAddSubFx.h - ZynAddSubFX-embedding plugin
 *
 * Copyright (c) 2008-2010 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * 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.
 *
 */

#ifndef ZYNADDSUBFX_H
#define ZYNADDSUBFX_H

#include <QMap>
#include <QMutex>

#include "AutomatableModel.h"
#include "Instrument.h"
#include "InstrumentView.h"
#include "RemotePlugin.h"
#include "zynaddsubfx/src/globals.h"

class QPushButton;

class LocalZynAddSubFx;
class ZynAddSubFxView;
class NotePlayHandle;
class Knob;
class LedCheckBox;

class ZynAddSubFxRemotePlugin : public RemotePlugin
{
	Q_OBJECT
public:
	ZynAddSubFxRemotePlugin();
	virtual ~ZynAddSubFxRemotePlugin();

	virtual bool processMessage( const message & _m );

signals:
	void clickedCloseButton();

} ;

class ZynAddSubFxInstrument : public Instrument
{
	Q_OBJECT
public:
	ZynAddSubFxInstrument( InstrumentTrack * _instrument_track );
	virtual ~ZynAddSubFxInstrument();

	virtual void play( sampleFrame * _working_buffer );

	virtual bool handleMidiEvent( const MidiEvent& event, const TimePos& time = TimePos(), f_cnt_t offset = 0 );

	virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent );
	virtual void loadSettings( const QDomElement & _this );

	virtual void loadFile( const QString & _file );

	virtual QString nodeName() const;

	virtual Flags flags() const
	{
		return IsSingleStreamed | IsMidiBased;
	}

	virtual PluginView * instantiateView( QWidget * _parent );

private slots:
	void reloadPlugin();

	void updatePitchRange();

	void updatePortamento();
	void updateFilterFreq();
	void updateFilterQ();
	void updateBandwidth();
	void updateFmGain();
	void updateResCenterFreq();
	void updateResBandwidth();

private:
	void initPlugin();
	void sendControlChange( MidiControllers midiCtl, float value );

	bool m_hasGUI;
	QMutex m_pluginMutex;
	LocalZynAddSubFx * m_plugin;
	ZynAddSubFxRemotePlugin * m_remotePlugin;

	FloatModel m_portamentoModel;
	FloatModel m_filterFreqModel;
	FloatModel m_filterQModel;
	FloatModel m_bandwidthModel;
	FloatModel m_fmGainModel;
	FloatModel m_resCenterFreqModel;
	FloatModel m_resBandwidthModel;
	BoolModel m_forwardMidiCcModel;

	QMap<int, bool> m_modifiedControllers;

	friend class ZynAddSubFxView;

signals:
	void settingsChanged();

} ;

class ZynAddSubFxView : public InstrumentViewFixedSize
{
	Q_OBJECT
public:
	ZynAddSubFxView( Instrument * _instrument, QWidget * _parent );
	virtual ~ZynAddSubFxView();

protected:
	virtual void dragEnterEvent( QDragEnterEvent * _dee );
	virtual void dropEvent( QDropEvent * _de );

private:
	void modelChanged();

	QPushButton * m_toggleUIButton;
	Knob * m_portamento;
	Knob * m_filterFreq;
	Knob * m_filterQ;
	Knob * m_bandwidth;
	Knob * m_fmGain;
	Knob * m_resCenterFreq;
	Knob * m_resBandwidth;
	LedCheckBox * m_forwardMidiCC;

private slots:
	void toggleUI();

} ;

#endif

repo_name: CYBERDEViL/LMMS

license: mit


code:
zynaddsubfx_icon ICON zynaddsubfx/zynaddsubfx.ico
#include <windows.h>

VS_VERSION_INFO	VERSIONINFO
  FILEVERSION	0,2,3,0
  FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
  FILEOS	VOS_NT_WINDOWS32
  FILETYPE	VFT_APP
  FILESUBTYPE	VFT2_UNKNOWN
BEGIN
  BLOCK "StringFileInfo"
  BEGIN
    BLOCK "040904E4"
    //language ID = U.S. English, charset = Windows, Multilingual
    BEGIN
      VALUE "Comments",		"ZynAddSubFX Plugin - LMMS (http://lmms.sf.net)\0"
      VALUE "CompanyName",	"LMMS Developers\0"
      VALUE "FileDescription",	"ZynAddSubFX LMMS Plugin\0"
      VALUE "FileVersion",	"2.3.0/@VERSION@\0"
      VALUE "LegalCopyright",	"Copyright (c) Nasca Octavian Paul, Mark McCurry, Harald Hvaal, Tobias Doerffel\0"
      VALUE "OriginalFilename",	"RemoteZynAddSubFx.exe\0"
      VALUE "ProductName",	"ZynAddSubFX/LMMS\0"
      VALUE "ProductVersion",	"2.3.0/@VERSION@\0"
    END
  END
END


code:
IF(LMMS_BUILD_LINUX AND WANT_VST)
	set(BUILD_SHARED_LIBS OFF)
	add_subdirectory(qt5-x11embed)
ENDIF()

ADD_SUBDIRECTORY(rpmalloc)
ADD_SUBDIRECTORY(weakjack)

# The lockless ring buffer library is compiled as part of the core
SET(RINGBUFFER_DIR "${CMAKE_SOURCE_DIR}/src/3rdparty/ringbuffer/")
SET(RINGBUFFER_DIR ${RINGBUFFER_DIR} PARENT_SCOPE)
# Create a dummy ringbuffer_export.h, since ringbuffer is not compiled as a library
FILE(WRITE ${CMAKE_BINARY_DIR}/src/ringbuffer_export.h
	"#include \"${CMAKE_BINARY_DIR}/src/lmms_export.h\"\n
	#define RINGBUFFER_EXPORT LMMS_EXPORT")
# Enable MLOCK support for ringbuffer if available
INCLUDE(CheckIncludeFiles)
CHECK_INCLUDE_FILES(sys/mman.h HAVE_SYS_MMAN)
IF(HAVE_SYS_MMAN AND NOT CMAKE_SYSTEM_NAME MATCHES "Haiku")
    SET(USE_MLOCK ON)
ELSE()
    SET(USE_MLOCK OFF)
ENDIF()
# Generate ringbuffer configuration headers
CONFIGURE_FILE(${RINGBUFFER_DIR}/src/ringbuffer-config.h.in  ${CMAKE_BINARY_DIR}/src/ringbuffer-config.h)
CONFIGURE_FILE(${RINGBUFFER_DIR}/src/ringbuffer-version.h.in ${CMAKE_BINARY_DIR}/src/ringbuffer-version.h)


code:
add_library(rpmalloc STATIC
	rpmalloc/rpmalloc/rpmalloc.c
	rpmalloc/rpmalloc/rpmalloc.h
)

target_include_directories(rpmalloc PUBLIC
	${CMAKE_CURRENT_SOURCE_DIR}/rpmalloc/rpmalloc
)

set_property(TARGET rpmalloc PROPERTY C_STANDARD 11)

IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
	target_compile_options(rpmalloc
		PRIVATE -Wno-unused-variable
	)
endif()

if (NOT LMMS_BUILD_WIN32)
	target_compile_definitions(rpmalloc
		PRIVATE -D_GNU_SOURCE
	)
endif()

if(MINGW)
	target_compile_definitions(rpmalloc
		PRIVATE -D_WIN32_WINNT=0x600
	)
endif()

if (CMAKE_BUILD_TYPE STREQUAL "Debug")
	# rpmalloc uses GCC builtin "__builtin_umull_overflow" with ENABLE_VALIDATE_ARGS,
	# which is only available starting with GCC 5
	if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5)
		set(ENABLE_VALIDATE_ARGS OFF)
	else ()
		set(ENABLE_VALIDATE_ARGS ON)
	endif()
	target_compile_definitions(rpmalloc
		PRIVATE -DENABLE_ASSERTS=1 -DENABLE_VALIDATE_ARGS=${ENABLE_VALIDATE_ARGS}
	)
endif()

option(LMMS_ENABLE_MALLOC_STATS "Enables statistics for rpmalloc" OFF)

if (LMMS_ENABLE_MALLOC_STATS)
	target_compile_definitions(rpmalloc
		PRIVATE -DENABLE_STATISTICS=1
	)
endif()


code:
# Use weak jack library linking
IF(LMMS_HAVE_WEAKJACK)
	SET(CMAKE_C_FLAGS "-std=c11")

	# Enable weakjack, disable metadata support
	ADD_DEFINITIONS(-DUSE_WEAK_JACK=1 -DNO_JACK_METADATA=1)

	# Library stub for AppImages running on systems without jack
	ADD_LIBRARY(weakjack MODULE weakjack/weak_libjack.c weakjack/weak_libjack.h)
	TARGET_INCLUDE_DIRECTORIES(weakjack PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/weakjack ${JACK_INCLUDE_DIRS})
	INSTALL(TARGETS weakjack LIBRARY DESTINATION "${PLUGIN_DIR}/optional")
ENDIF()


code:
ADD_SUBDIRECTORY(3rdparty)

CONFIGURE_FILE("lmmsconfig.h.in"        "${CMAKE_BINARY_DIR}/lmmsconfig.h")
CONFIGURE_FILE("lmmsversion.h.in"       "${CMAKE_BINARY_DIR}/lmmsversion.h")

SET(LMMS_SRCS "")
SET(LMMS_UIS "")

SET(CMAKE_AUTOMOC ON)
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

# Enable C++14
SET(CMAKE_CXX_STANDARD 14)

IF(LMMS_BUILD_APPLE)
	SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
ENDIF()

ADD_SUBDIRECTORY(core)
ADD_SUBDIRECTORY(gui)
ADD_SUBDIRECTORY(tracks)

QT5_WRAP_UI(LMMS_UI_OUT ${LMMS_UIS})
INCLUDE_DIRECTORIES(
	"${CMAKE_CURRENT_BINARY_DIR}"
	"${CMAKE_BINARY_DIR}"
	"${CMAKE_BINARY_DIR}/include"
	"${CMAKE_SOURCE_DIR}"
	"${CMAKE_SOURCE_DIR}/include"
	"${RINGBUFFER_DIR}/include"
)

IF(WIN32 AND MSVC)
	SET(WINRC "${CMAKE_BINARY_DIR}/lmms.rc")
ELSEIF(WIN32)
	SET(WINRC "${CMAKE_BINARY_DIR}/lmmsrc.obj")
	ADD_CUSTOM_COMMAND(OUTPUT "${WINRC}"
				COMMAND "${CMAKE_RC_COMPILER}"
					"-I\"${CMAKE_SOURCE_DIR}\""
					"-o\"${CMAKE_BINARY_DIR}/lmmsrc.obj\""
					"-i\"${CMAKE_BINARY_DIR}/lmms.rc\""
				DEPENDS "${CMAKE_BINARY_DIR}/lmms.rc")
ENDIF()

INCLUDE(GenQrc)
ADD_GEN_QRC(LMMS_RCC_OUT lmms.qrc
	"${CMAKE_SOURCE_DIR}/doc/AUTHORS"
	"${CMAKE_SOURCE_DIR}/LICENSE.txt"
	"${CONTRIBUTORS}"
)

# Paths relative to lmms executable
FILE(RELATIVE_PATH LIB_DIR_RELATIVE "/${BIN_DIR}" "/${LIB_DIR}")
FILE(RELATIVE_PATH PLUGIN_DIR_RELATIVE "/${BIN_DIR}" "/${PLUGIN_DIR}")
ADD_DEFINITIONS(-DLIB_DIR="${LIB_DIR_RELATIVE}" -DPLUGIN_DIR="${PLUGIN_DIR_RELATIVE}" ${PULSEAUDIO_DEFINITIONS} ${PORTAUDIO_DEFINITIONS})
INCLUDE_DIRECTORIES(
	${JACK_INCLUDE_DIRS}
	${SAMPLERATE_INCLUDE_DIRS}
	${SNDFILE_INCLUDE_DIRS}
	${SNDIO_INCLUDE_DIRS}
	${FFTW3F_INCLUDE_DIRS}
)

IF(NOT ("${SDL2_INCLUDE_DIR}" STREQUAL ""))
	INCLUDE_DIRECTORIES("${SDL2_INCLUDE_DIR}")
ELSEIF(NOT ("${SDL_INCLUDE_DIR}" STREQUAL ""))
	INCLUDE_DIRECTORIES("${SDL_INCLUDE_DIR}")
ENDIF()

IF(LMMS_HAVE_WEAKJACK)
	LIST(APPEND LMMS_SRCS "${WEAKJACK_INCLUDE_DIRS}/weak_libjack.c")
	LIST(APPEND LMMS_INCLUDES "${WEAKJACK_INCLUDE_DIRS}/weak_libjack.h")
	INCLUDE_DIRECTORIES("${WEAKJACK_INCLUDE_DIRS}")
	ADD_DEFINITIONS(-DUSE_WEAK_JACK=1 -DNO_JACK_METADATA=1)
ENDIF()

IF(NOT ("${PORTAUDIO_INCLUDE_DIR}" STREQUAL ""))
	INCLUDE_DIRECTORIES("${PORTAUDIO_INCLUDE_DIR}")
ENDIF()

IF(NOT ("${PULSEAUDIO_INCLUDE_DIR}" STREQUAL ""))
	INCLUDE_DIRECTORIES("${PULSEAUDIO_INCLUDE_DIR}")
ENDIF()

IF(NOT ("${OGGVORBIS_INCLUDE_DIR}" STREQUAL ""))
	INCLUDE_DIRECTORIES("${OGGVORBIS_INCLUDE_DIR}")
ENDIF()

IF(NOT ("${LAME_INCLUDE_DIRS}" STREQUAL ""))
	INCLUDE_DIRECTORIES("${LAME_INCLUDE_DIRS}")
ENDIF()

IF(NOT ("${LV2_INCLUDE_DIRS}" STREQUAL ""))
	INCLUDE_DIRECTORIES(${LV2_INCLUDE_DIRS})
ENDIF()

IF(NOT ("${LILV_INCLUDE_DIRS}" STREQUAL ""))
	INCLUDE_DIRECTORIES(${LILV_INCLUDE_DIRS})
ENDIF()

IF(NOT ("${SUIL_INCLUDE_DIRS}" STREQUAL ""))
	INCLUDE_DIRECTORIES(${SUIL_INCLUDE_DIRS})
ENDIF()
LIST(APPEND LMMS_SRCS "${RINGBUFFER_DIR}/src/lib/ringbuffer.cpp")

# Use libraries in non-standard directories (e.g., another version of Qt)
IF(LMMS_BUILD_LINUX)
	LINK_LIBRARIES(-Wl,--enable-new-dtags)
	SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
ENDIF()
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

ADD_LIBRARY(lmmsobjs OBJECT
	${LMMS_SRCS}
	${LMMS_INCLUDES}
	${LMMS_UI_OUT}
	${LMMS_RCC_OUT}
)

GENERATE_EXPORT_HEADER(lmmsobjs
	BASE_NAME lmms
)

ADD_EXECUTABLE(lmms
	core/main.cpp
	$<TARGET_OBJECTS:lmmsobjs>
	"${WINRC}"
)
TARGET_INCLUDE_DIRECTORIES(lmms
	PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
)

# CMake doesn't define target_EXPORTS for OBJECT libraries.
# See the documentation of DEFINE_SYMBOL for details.
# Also add LMMS_STATIC_DEFINE for targets linking against it.
TARGET_COMPILE_DEFINITIONS(lmmsobjs
	PRIVATE -Dlmmsobjs_EXPORTS
	INTERFACE -DLMMS_STATIC_DEFINE
)
TARGET_COMPILE_DEFINITIONS(lmms
	PRIVATE $<TARGET_PROPERTY:lmmsobjs,INTERFACE_COMPILE_DEFINITIONS>
)

# Set Visual Studio startup project to lmms
# https://stackoverflow.com/a/37994396/8166701
IF(NOT CMAKE_VERSION VERSION_LESS 3.6)
	SET_PROPERTY(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT lmms)
ENDIF()

SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${LMMS_RCC_OUT} ${LMMS_UI_OUT} lmmsconfig.h lmms.1.gz")

IF(LMMS_BUILD_WIN32)
	SET(EXTRA_LIBRARIES "winmm")
ENDIF()

IF(LMMS_BUILD_APPLE)
	SET(EXTRA_LIBRARIES "-framework CoreMIDI -framework CoreFoundation")
ENDIF()

if(LMMS_HAVE_OSS AND LMMS_BUILD_OPENBSD)
    SET(EXTRA_LIBRARIES "-lossaudio")
endif()

IF(LMMS_BUILD_HAIKU)
	SET(EXTRA_LIBRARIES "-lnetwork")
ENDIF()

SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS}
	${CMAKE_THREAD_LIBS_INIT}
	${QT_LIBRARIES}
	${ASOUND_LIBRARY}
	${SDL_LIBRARY}
	${SDL2_LIBRARY}
	${PORTAUDIO_LIBRARIES}
	${SOUNDIO_LIBRARY}
	${SNDIO_LIBRARIES}
	${PULSEAUDIO_LIBRARIES}
	${JACK_LIBRARIES}
	${OGGVORBIS_LIBRARIES}
	${LAME_LIBRARIES}
	${LV2_LIBRARIES}
	${SUIL_LIBRARIES}
	${LILV_LIBRARIES}
	${SAMPLERATE_LIBRARIES}
	${SNDFILE_LIBRARIES}
	${EXTRA_LIBRARIES}
	rpmalloc
)

# Expose required libs for tests binary
SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} PARENT_SCOPE)

TARGET_LINK_LIBRARIES(lmms
	${LMMS_REQUIRED_LIBS}
)

FOREACH(LIB ${LMMS_REQUIRED_LIBS})
	IF(TARGET ${LIB})
		GET_TARGET_PROPERTY(INCLUDE_DIRS ${LIB} INTERFACE_INCLUDE_DIRECTORIES)
		IF(INCLUDE_DIRS)
			TARGET_INCLUDE_DIRECTORIES(lmmsobjs PRIVATE ${INCLUDE_DIRS})
		ENDIF()
	ENDIF()
ENDFOREACH()

IF(LMMS_BUILD_WIN32)
	SET_TARGET_PROPERTIES(lmms PROPERTIES
		ENABLE_EXPORTS ON
	)
	IF(LMMS_BUILD_MSYS)
		# ENABLE_EXPORTS property has no effect in some MSYS2 configurations.
		# Add the linker flag manually to create liblmms.dll.a import library
		SET_PROPERTY(TARGET lmms
			APPEND_STRING PROPERTY LINK_FLAGS " -Wl,--out-implib,liblmms.dll.a"
		)
	ENDIF()
ELSE()
	IF(NOT LMMS_BUILD_APPLE)
		SET_TARGET_PROPERTIES(lmms PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Wl,-E")
	ENDIF(NOT LMMS_BUILD_APPLE)

	if(CMAKE_INSTALL_MANDIR)
		SET(INSTALL_MANDIR ${CMAKE_INSTALL_MANDIR})
	ELSE(CMAKE_INSTALL_MANDIR)
		SET(INSTALL_MANDIR ${CMAKE_INSTALL_PREFIX}/share/man)
	ENDIF(CMAKE_INSTALL_MANDIR)
	INSTALL(FILES "${CMAKE_BINARY_DIR}/lmms.1.gz"
			DESTINATION "${INSTALL_MANDIR}/man1/"
			PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)
ENDIF()

INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")


code:
/*
 * AutomatableModel.cpp - some implementations of AutomatableModel-class
 *
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * 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 "AutomatableModel.h"

#include "lmms_math.h"

#include "AutomationPattern.h"
#include "ControllerConnection.h"
#include "LocaleHelper.h"
#include "Mixer.h"
#include "ProjectJournal.h"
#include "Song.h"

long AutomatableModel::s_periodCounter = 0;

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 );
}

AutomatableModel::~AutomatableModel()
{
	while( m_linkedModels.empty() == false )
	{
		m_linkedModels.last()->unlinkModel( this );
		m_linkedModels.erase( m_linkedModels.end() - 1 );
	}

	if( m_controllerConnection )
	{
		delete m_controllerConnection;
	}

	m_valueBuffer.clear();

	emit destroyed( id() );
}

bool AutomatableModel::isAutomated() const
{
	return AutomationPattern::isAutomated( this );
}

bool AutomatableModel::mustQuoteName(const QString& name)
{
	QRegExp reg("^[A-Za-z0-9._-]+$");
	return !reg.exactMatch(name);
}

void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, const QString& name )
{
	bool mustQuote = mustQuoteName(name);

	if( isAutomated() || m_scaleType != Linear )
	{
		// automation needs tuple of data (name, id, value)
		// scale type also needs an extra value
		// => it must be appended as a node

		QDomElement me = doc.createElement( mustQuote ? QString("automatablemodel") : name );
		me.setAttribute( "id", ProjectJournal::idToSave( id() ) );
		me.setAttribute( "value", m_value );
		me.setAttribute( "scale_type", m_scaleType == Logarithmic ? "log" : "linear" );
		if(mustQuote) {
			me.setAttribute( "nodename", name );
		}
		element.appendChild( me );
	}
	else
	{
		if(mustQuote)
		{
			QDomElement me = doc.createElement( "automatablemodel" );
			me.setAttribute( "nodename", name );
			me.setAttribute( "value", m_value );
			element.appendChild( me );
		}
		else
		{
			// non automation, linear scale (default), can be saved as attribute
			element.setAttribute( name, m_value );
		}
	}

	// Skip saving MIDI connections if we're saving project and
	// the discardMIDIConnections option is true.
	auto controllerType = m_controllerConnection
			? m_controllerConnection->getController()->type()
			: Controller::DummyController;
	bool skipMidiController = Engine::getSong()->isSavingProject()
							  && Engine::getSong()->getSaveOptions().discardMIDIConnections.value();
	if (m_controllerConnection && controllerType != Controller::DummyController
		&& !(skipMidiController && controllerType == Controller::MidiController))
	{
		QDomElement controllerElement;

		// get "connection" element (and create it if needed)
		QDomNode node = element.namedItem( "connection" );
		if( node.isElement() )
		{
			controllerElement = node.toElement();
		}
		else
		{
			controllerElement = doc.createElement( "connection" );
			element.appendChild( controllerElement );
		}

		bool mustQuote = mustQuoteName(name);
		QString elementName = mustQuote ? "controllerconnection"
						: name;

		QDomElement element = doc.createElement( elementName );
		if(mustQuote)
			element.setAttribute( "nodename", name );
		m_controllerConnection->saveSettings( doc, element );

		controllerElement.appendChild( element );
	}
}

void AutomatableModel::loadSettings( const QDomElement& element, const QString& name )
{
	// compat code
	QDomNode node = element.namedItem( AutomationPattern::classNodeName() );
	if( node.isElement() )
	{
		node = node.namedItem( name );
		if( node.isElement() )
		{
			AutomationPattern * p = AutomationPattern::globalAutomationPattern( this );
			p->loadSettings( node.toElement() );
			setValue( p->valueAt( 0 ) );
			// in older projects we sometimes have odd automations
			// with just one value in - eliminate if necessary
			if( !p->hasAutomation() )
			{
				delete p;
			}
			return;
		}
		// logscales were not existing at this point of time
		// so they can be ignored
	}

	QDomNode connectionNode = element.namedItem( "connection" );
	// reads controller connection
	if( connectionNode.isElement() )
	{
		QDomNode thisConnection = connectionNode.toElement().namedItem( name );
		if( !thisConnection.isElement() )
		{
			thisConnection = connectionNode.toElement().namedItem( "controllerconnection" );
			QDomElement tcElement = thisConnection.toElement();
			// sanity check
			if( tcElement.isNull() || tcElement.attribute( "nodename" ) != name )
			{
				// no, that wasn't it, act as if we never found one
				thisConnection.clear();
			}
		}
		if( thisConnection.isElement() )
		{
			setControllerConnection( new ControllerConnection( (Controller*)NULL ) );
			m_controllerConnection->loadSettings( thisConnection.toElement() );
			//m_controllerConnection->setTargetName( displayName() );
		}
	}

	// models can be stored as elements (port00) or attributes (port10):
	// <ladspacontrols port10="4.41">
	//   <port00 value="4.41" id="4249278"/>
	// </ladspacontrols>
	// element => there is automation data, or scaletype information

	node = element.namedItem( name ); // maybe we have luck?

	// either: no node with name "name" found
	//  => look for nodes with attribute name="nodename"
	// or: element with namedItem() "name" was found, but it's real nodename
	// is given as attribute and does not match
	//  => look for the right node
	if(node.isNull() ||
		( node.isElement() &&
		node.toElement().hasAttribute("nodename") &&
		node.toElement().attribute("nodename") != name))
	{
		for(QDomElement othernode = element.firstChildElement();
			!othernode.isNull();
			othernode = othernode.nextSiblingElement())
		{
			if((!othernode.hasAttribute("nodename") &&
				othernode.nodeName() == name) ||
				othernode.attribute("nodename") == name)
			{
				node = othernode;
				break;
			}
		}
	}
	if( node.isElement() )
	{
		QDomElement nodeElement = node.toElement();
		changeID( nodeElement.attribute( "id" ).toInt() );
		setValue( LocaleHelper::toFloat( nodeElement.attribute( "value" ) ) );
		if( nodeElement.hasAttribute( "scale_type" ) )
		{
			if( nodeElement.attribute( "scale_type" ) == "linear" )
			{
				setScaleType( Linear );
			}
			else if( nodeElement.attribute( "scale_type" ) == "log" )
			{
				setScaleType( Logarithmic );
			}
		}
	}
	else
	{

		setScaleType( Linear );

		if( element.hasAttribute( name ) )
			// attribute => read the element's value from the attribute list
		{
			setInitValue( LocaleHelper::toFloat( element.attribute( name ) ) );
		}
		else
		{
			reset();
		}
	}
}

void AutomatableModel::setValue( const float value )
{
	m_oldValue = m_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 );
			}
		}
		m_valueChanged = true;
		emit dataChanged();
	}
	else
	{
		emit dataUnchanged();
	}
	--m_setValueDepth;
}

template<class T> T AutomatableModel::logToLinearScale( T value ) const
{
	return castValue<T>( ::logToLinearScale( minValue<float>(), maxValue<float>(), static_cast<float>( value ) ) );
}

float AutomatableModel::scaledValue( float value ) const
{
	return m_scaleType == Linear
		? value
		: logToLinearScale<float>( ( value - minValue<float>() ) / m_range );
}

float AutomatableModel::inverseScaledValue( float value ) const
{
	return m_scaleType == Linear
		? value
		: ::linearToLogScale( minValue<float>(), maxValue<float>(), value );
}

//! @todo: this should be moved into a maths header
template<class T>
void roundAt( T& value, const T& where, const T& step_size )
{
	if( qAbs<float>( value - where )
		< typeInfo<float>::minEps() * qAbs<float>( step_size ) )
	{
		value = where;
	}
}

template<class T>
void AutomatableModel::roundAt( T& value, const T& where ) const
{
	::roundAt(value, where, m_step);
}

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::setRange( const float min, const float max,
							const float step )
{
	if( ( m_maxValue != max ) || ( m_minValue != min ) )
	{
		m_minValue = min;
		m_maxValue = max;
		if( m_minValue > m_maxValue )
		{
			qSwap<float>( m_minValue, m_maxValue );
		}
		m_range = m_maxValue - m_minValue;

		setStep( step );

		// re-adjust value
		setValue( value<float>() );

		emit propertiesChanged();
	}
}

void AutomatableModel::setStep( const float step )
{
	if( m_step != step )
	{
		m_step = step;
		emit propertiesChanged();
	}
}

float AutomatableModel::fittedValue( float value ) const
{
	value = qBound<float>( m_minValue, value, m_maxValue );

	if( m_step != 0 && m_hasStrictStepSize )
	{
		value = nearbyintf( value / m_step ) * m_step;
	}

	roundAt( value, m_maxValue );
	roundAt( value, m_minValue );
	roundAt( value, 0.0f );

	if( value < m_minValue )
	{
		return m_minValue;
	}
	else if( value > m_maxValue )
	{
		return m_maxValue;
	}

	return value;
}

void AutomatableModel::linkModel( AutomatableModel* model )
{
	if( !m_linkedModels.contains( model ) && model != this )
	{
		m_linkedModels.push_back( model );

		if( !model->hasLinkedModels() )
		{
			QObject::connect( this, SIGNAL( dataChanged() ),
					model, SIGNAL( dataChanged() ), Qt::DirectConnection );
		}
	}
}

void AutomatableModel::unlinkModel( AutomatableModel* model )
{
	AutoModelVector::Iterator it = std::find( m_linkedModels.begin(), m_linkedModels.end(), model );
	if( it != m_linkedModels.end() )
	{
		m_linkedModels.erase( it );
	}
}

void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 )
{
	if (!model1->m_linkedModels.contains( model2 ) && model1 != model2)
	{
		// copy data
		model1->m_value = model2->m_value;
		if (model1->valueBuffer() && model2->valueBuffer())
		{
			std::copy_n(model2->valueBuffer()->data(),
				model1->valueBuffer()->length(),
				model1->valueBuffer()->data());
		}
		// send dataChanged() before linking (because linking will
		// connect the two dataChanged() signals)
		emit model1->dataChanged();
		// finally: link the models
		model1->linkModel( model2 );
		model2->linkModel( model1 );
	}
}

void AutomatableModel::unlinkModels( AutomatableModel* model1, AutomatableModel* model2 )
{
	model1->unlinkModel( model2 );
	model2->unlinkModel( model1 );
}

void AutomatableModel::unlinkAllModels()
{
	for( AutomatableModel* model : m_linkedModels )
	{
		unlinkModels( this, model );
	}
}

void AutomatableModel::setControllerConnection( ControllerConnection* c )
{
	m_controllerConnection = c;
	if( c )
	{
		QObject::connect( m_controllerConnection, SIGNAL( valueChanged() ),
				this, SIGNAL( dataChanged() ), Qt::DirectConnection );
		QObject::connect( m_controllerConnection, SIGNAL( destroyed() ), this, SLOT( unlinkControllerConnection() ) );
		m_valueChanged = true;
		emit dataChanged();
	}
}

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 );
}

ValueBuffer * AutomatableModel::valueBuffer()
{
	QMutexLocker m( &m_valueBufferMutex );
	// if we've already calculated the valuebuffer this period, return the cached buffer
	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( 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;
}

void AutomatableModel::unlinkControllerConnection()
{
	if( m_controllerConnection )
	{
		m_controllerConnection->disconnect( this );
	}

	m_controllerConnection = NULL;
}

void AutomatableModel::setInitValue( const float value )
{
	m_initValue = fittedValue( value );
	bool journalling = testAndSetJournalling( false );
	setValue( value );
	m_oldValue = m_value;
	setJournalling( journalling );
	emit initValueChanged( value );
}

void AutomatableModel::reset()
{
	setValue( initValue<float>() );
}

float AutomatableModel::globalAutomationValueAt( const TimePos& 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 time 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 time, 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;
	}
}

float FloatModel::getRoundedValue() const
{
	return qRound( value() / step<float>() ) * step<float>();
}

int FloatModel::getDigitCount() const
{
	float steptemp = step<float>();
	int digits = 0;
	while ( steptemp < 1 )
	{
		steptemp = steptemp * 10.0f;
		digits++;
	}
	return digits;
}

QString FloatModel::displayValue( const float val ) const
{
	return QString::number( castValue<float>( scaledValue( val ) ) );
}

QString IntModel::displayValue( const float val ) const
{
	return QString::number( castValue<int>( scaledValue( val ) ) );
}

QString BoolModel::displayValue( const float val ) const
{
	return QString::number( castValue<bool>( scaledValue( val ) ) );
}


code:
/*
 * AutomationPattern.cpp - implementation of class AutomationPattern which
 *                         holds dynamic values
 *
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 * Copyright (c) 2006-2008 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * 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 "AutomationPattern.h"

#include "AutomationPatternView.h"
#include "AutomationTrack.h"
#include "LocaleHelper.h"
#include "Note.h"
#include "ProjectJournal.h"
#include "BBTrackContainer.h"
#include "Song.h"

#include <cmath>

int AutomationPattern::s_quantization = 1;
const float AutomationPattern::DEFAULT_MIN_VALUE = 0;
const float AutomationPattern::DEFAULT_MAX_VALUE = 1;

AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) :
	TrackContentObject( _auto_track ),
	m_autoTrack( _auto_track ),
	m_objects(),
	m_tension( 1.0 ),
	m_progressionType( DiscreteProgression ),
	m_dragging( false ),
	m_isRecording( false ),
	m_lastRecordedValue( 0 )
{
	changeLength( TimePos( 1, 0 ) );
	if( getTrack() )
	{
		switch( getTrack()->trackContainer()->type() )
		{
			case TrackContainer::BBContainer:
				setAutoResize( true );
				break;

			case TrackContainer::SongContainer:
				// move down
			default:
				setAutoResize( false );
				break;
		}
	}
}

AutomationPattern::AutomationPattern( const AutomationPattern & _pat_to_copy ) :
	TrackContentObject( _pat_to_copy.m_autoTrack ),
	m_autoTrack( _pat_to_copy.m_autoTrack ),
	m_objects( _pat_to_copy.m_objects ),
	m_tension( _pat_to_copy.m_tension ),
	m_progressionType( _pat_to_copy.m_progressionType )
{
	for( timeMap::const_iterator it = _pat_to_copy.m_timeMap.begin();
				it != _pat_to_copy.m_timeMap.end(); ++it )
	{
		m_timeMap[it.key()] = it.value();
		m_tangents[it.key()] = _pat_to_copy.m_tangents[it.key()];
	}
	switch( getTrack()->trackContainer()->type() )
	{
		case TrackContainer::BBContainer:
			setAutoResize( true );
			break;

		case TrackContainer::SongContainer:
			// move down
		default:
			setAutoResize( false );
			break;
	}
}

bool AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup )
{
	if( _search_dup && m_objects.contains(_obj) )
	{
		return false;
	}

	// the automation track is unconnected and there is nothing in the track
	if( m_objects.isEmpty() && hasAutomation() == false )
	{
		// then initialize first value
		putValue( TimePos(0), _obj->inverseScaledValue( _obj->value<float>() ), false );
	}

	m_objects += _obj;

	connect( _obj, SIGNAL( destroyed( jo_id_t ) ),
			this, SLOT( objectDestroyed( jo_id_t ) ),
						Qt::DirectConnection );

	emit dataChanged();

	return true;
}

void AutomationPattern::setProgressionType(
					ProgressionTypes _new_progression_type )
{
	if ( _new_progression_type == DiscreteProgression ||
		_new_progression_type == LinearProgression ||
		_new_progression_type == CubicHermiteProgression )
	{
		m_progressionType = _new_progression_type;
		emit dataChanged();
	}
}

void AutomationPattern::setTension( QString _new_tension )
{
	bool ok;
	float nt = LocaleHelper::toFloat(_new_tension, & ok);

	if( ok && nt > -0.01 && nt < 1.01 )
	{
		m_tension = nt;
	}
}

const AutomatableModel * AutomationPattern::firstObject() const
{
	AutomatableModel * m;
	if( !m_objects.isEmpty() && ( m = m_objects.first() ) != NULL )
	{
		return m;
	}

	static FloatModel _fm( 0, DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, 0.001 );
	return &_fm;
}

const AutomationPattern::objectVector& AutomationPattern::objects() const
{
	return m_objects;
}

TimePos AutomationPattern::timeMapLength() const
{
	TimePos one_bar = TimePos(1, 0);
	if (m_timeMap.isEmpty()) { return one_bar; }

	timeMap::const_iterator it = m_timeMap.end();
	tick_t last_tick = static_cast<tick_t>((it-1).key());
	// if last_tick is 0 (single item at tick 0)
	// return length as a whole bar to prevent disappearing TCO
	if (last_tick == 0) { return one_bar; }

	return TimePos(last_tick);
}

void AutomationPattern::updateLength()
{
	// Do not resize down in case user manually extended up
	changeLength(qMax(length(), timeMapLength()));
}

TimePos AutomationPattern::putValue( const TimePos & time,
					const float value,
					const bool quantPos,
					const bool ignoreSurroundingPoints )
{
	cleanObjects();

	TimePos newTime = quantPos ?
				Note::quantized( time, quantization() ) :
				time;

	m_timeMap[ newTime ] = value;
	timeMap::const_iterator it = m_timeMap.find( newTime );

	// Remove control points that are covered by the new points
	// quantization value. Control Key to override
	if( ! ignoreSurroundingPoints )
	{
		for( int i = newTime + 1; i < newTime + quantization(); ++i )
		{
			AutomationPattern::removeValue( i );
		}
	}
	if( it != m_timeMap.begin() )
	{
		--it;
	}
	generateTangents( it, 3 );

	updateLength();

	emit dataChanged();

	return newTime;
}

void AutomationPattern::removeValue( const TimePos & time )
{
	cleanObjects();

	m_timeMap.remove( time );
	m_tangents.remove( time );
	timeMap::const_iterator it = m_timeMap.lowerBound( time );
	if( it != m_timeMap.begin() )
	{
		--it;
	}
	generateTangents(it, 3);

	updateLength();

	emit dataChanged();
}

void AutomationPattern::recordValue(TimePos 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.
 *        Calling this function will also automatically set m_dragging to true,
 *        which applyDragValue() have to be called to m_dragging.
 * @param the time(x position) of the point being dragged
 * @param the value(y position) of the point being dragged
 * @param true to snip x position
 * @return
 */
TimePos AutomationPattern::setDragValue( const TimePos & time,
						const float value,
						const bool quantPos,
						const bool controlKey )
{
	if( m_dragging == false )
	{
		TimePos newTime = quantPos  ?
				Note::quantized( time, quantization() ) :
							time;
		this->removeValue( newTime );
		m_oldTimeMap = m_timeMap;
		m_dragging = true;
	}

	//Restore to the state before it the point were being dragged
	m_timeMap = m_oldTimeMap;

	for( timeMap::const_iterator it = m_timeMap.begin(); it != m_timeMap.end(); ++it )
	{
		generateTangents( it, 3 );
	}

	return this->putValue( time, value, quantPos, controlKey );

}

/**
 * @brief After the point is dragged, this function is called to apply the change.
 */
void AutomationPattern::applyDragValue()
{
	m_dragging = false;
}

float AutomationPattern::valueAt( const TimePos & _time ) const
{
	if( m_timeMap.isEmpty() )
	{
		return 0;
	}

	if( m_timeMap.contains( _time ) )
	{
		return m_timeMap[_time];
	}

	// lowerBound returns next value with greater key, therefore we take
	// the previous element to get the current value
	timeMap::ConstIterator v = m_timeMap.lowerBound( _time );

	if( v == m_timeMap.begin() )
	{
		return 0;
	}
	if( v == m_timeMap.end() )
	{
		return (v-1).value();
	}

	return valueAt( v-1, _time - (v-1).key() );
}

float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const
{
	if( m_progressionType == DiscreteProgression || v == m_timeMap.end() )
	{
		return v.value();
	}
	else if( m_progressionType == LinearProgression )
	{
		float slope = ((v+1).value() - v.value()) /
							((v+1).key() - v.key());
		return v.value() + offset * slope;
	}
	else /* CubicHermiteProgression */
	{
		// Implements a Cubic Hermite spline as explained at:
		// http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Unit_interval_.280.2C_1.29
		//
		// Note that we are not interpolating a 2 dimensional point over
		// time as the article describes.  We are interpolating a single
		// value: y.  To make this work we map the values of x that this
		// segment spans to values of t for t = 0.0 -> 1.0 and scale the
		// tangents _m1 and _m2
		int numValues = ((v+1).key() - v.key());
		float t = (float) offset / (float) numValues;
		float m1 = (m_tangents[v.key()]) * numValues * m_tension;
		float m2 = (m_tangents[(v+1).key()]) * numValues * m_tension;

		return ( 2*pow(t,3) - 3*pow(t,2) + 1 ) * v.value()
				+ ( pow(t,3) - 2*pow(t,2) + t) * m1
				+ ( -2*pow(t,3) + 3*pow(t,2) ) * (v+1).value()
				+ ( pow(t,3) - pow(t,2) ) * m2;
	}
}

float *AutomationPattern::valuesAfter( const TimePos & _time ) const
{
	timeMap::ConstIterator v = m_timeMap.lowerBound( _time );
	if( v == m_timeMap.end() || (v+1) == m_timeMap.end() )
	{
		return NULL;
	}

	int numValues = (v+1).key() - v.key();
	float *ret = new float[numValues];

	for( int i = 0; i < numValues; i++ )
	{
		ret[i] = valueAt( v, i );
	}

	return ret;
}

void AutomationPattern::flipY( int min, int max )
{
	timeMap tempMap = m_timeMap;
	timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
	float tempValue = 0;

	int numPoints = 0;

	for( int i = 0; ( iterate + i + 1 ) != m_timeMap.end() && ( iterate + i ) != m_timeMap.end() ; i++)
	{
		numPoints++;
	}

	for( int i = 0; i <= numPoints; i++ )
	{

		if ( min < 0 )
		{
			tempValue = valueAt( ( iterate + i ).key() ) * -1;
			putValue( TimePos( (iterate + i).key() ) , tempValue, false);
		}
		else
		{
			tempValue = max - valueAt( ( iterate + i ).key() );
			putValue( TimePos( (iterate + i).key() ) , tempValue, false);
		}
	}

	generateTangents();
	emit dataChanged();
}

void AutomationPattern::flipY()
{
	flipY(getMin(), getMax());
}

void AutomationPattern::flipX( int length )
{
	timeMap tempMap;

	timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
	float tempValue = 0;
	int numPoints = 0;

	for( int i = 0; ( iterate + i + 1 ) != m_timeMap.end() && ( iterate + i ) != m_timeMap.end() ; i++)
	{
		numPoints++;
	}

	float realLength = ( iterate + numPoints ).key();

	if ( length != -1 && length != realLength)
	{
		if ( realLength < length )
		{
			tempValue = valueAt( ( iterate + numPoints ).key() );
			putValue( TimePos( length ) , tempValue, false);
			numPoints++;
			for( int i = 0; i <= numPoints; i++ )
			{
				tempValue = valueAt( ( iterate + i ).key() );
				TimePos newTime = TimePos( length - ( iterate + i ).key() );
				tempMap[newTime] = tempValue;
			}
		}
		else
		{
			for( int i = 0; i <= numPoints; i++ )
			{
				tempValue = valueAt( ( iterate + i ).key() );
				TimePos newTime;

				if ( ( iterate + i ).key() <= length )
				{
					newTime = TimePos( length - ( iterate + i ).key() );
				}
				else
				{
					newTime = TimePos( ( iterate + i ).key() );
				}
				tempMap[newTime] = tempValue;
			}
		}
	}
	else
	{
		for( int i = 0; i <= numPoints; i++ )
		{
			tempValue = valueAt( ( iterate + i ).key() );
			cleanObjects();
			TimePos newTime = TimePos( realLength - ( iterate + i ).key() );
			tempMap[newTime] = tempValue;
		}
	}

	m_timeMap.clear();

	m_timeMap = tempMap;

	generateTangents();
	emit dataChanged();
}

void AutomationPattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
	_this.setAttribute( "pos", startPosition() );
	_this.setAttribute( "len", length() );
	_this.setAttribute( "name", name() );
	_this.setAttribute( "prog", QString::number( progressionType() ) );
	_this.setAttribute( "tens", QString::number( getTension() ) );
	_this.setAttribute( "mute", QString::number( isMuted() ) );

	if( usesCustomClipColor() )
	{
		_this.setAttribute( "color", color().name() );
	}

	for( timeMap::const_iterator it = m_timeMap.begin();
						it != m_timeMap.end(); ++it )
	{
		QDomElement element = _doc.createElement( "time" );
		element.setAttribute( "pos", it.key() );
		element.setAttribute( "value", it.value() );
		_this.appendChild( element );
	}

	for( objectVector::const_iterator it = m_objects.begin();
						it != m_objects.end(); ++it )
	{
		if( *it )
		{
			QDomElement element = _doc.createElement( "object" );
			element.setAttribute( "id",
				ProjectJournal::idToSave( ( *it )->id() ) );
			_this.appendChild( element );
		}
	}
}

void AutomationPattern::loadSettings( const QDomElement & _this )
{
	clear();

	movePosition( _this.attribute( "pos" ).toInt() );
	setName( _this.attribute( "name" ) );
	setProgressionType( static_cast<ProgressionTypes>( _this.attribute(
							"prog" ).toInt() ) );
	setTension( _this.attribute( "tens" ) );
	setMuted(_this.attribute( "mute", QString::number( false ) ).toInt() );

	for( QDomNode node = _this.firstChild(); !node.isNull();
						node = node.nextSibling() )
	{
		QDomElement element = node.toElement();
		if( element.isNull()  )
		{
			continue;
		}
		if( element.tagName() == "time" )
		{
			m_timeMap[element.attribute( "pos" ).toInt()]
				= LocaleHelper::toFloat(element.attribute("value"));
		}
		else if( element.tagName() == "object" )
		{
			m_idsToResolve << element.attribute( "id" ).toInt();
		}
	}

	if( _this.hasAttribute( "color" ) )
	{
		useCustomClipColor( true );
		setColor( _this.attribute( "color" ) );
	}

	int len = _this.attribute( "len" ).toInt();
	if( len <= 0 )
	{
		// TODO: Handle with an upgrade method
		updateLength();
	}
	else
	{
		changeLength( len );
	}
	generateTangents();
}

const QString AutomationPattern::name() const
{
	if( !TrackContentObject::name().isEmpty() )
	{
		return TrackContentObject::name();
	}
	if( !m_objects.isEmpty() && m_objects.first() != NULL )
	{
		return m_objects.first()->fullDisplayName();
	}
	return tr( "Drag a control while pressing <%1>" ).arg(UI_CTRL_KEY);
}

TrackContentObjectView * AutomationPattern::createView( TrackView * _tv )
{
	return new AutomationPatternView( this, _tv );
}

bool AutomationPattern::isAutomated( const AutomatableModel * _m )
{
	TrackContainer::TrackList l;
	l += Engine::getSong()->tracks();
	l += Engine::getBBTrackContainer()->tracks();
	l += Engine::getSong()->globalAutomationTrack();

	for( TrackContainer::TrackList::ConstIterator it = l.begin(); it != l.end(); ++it )
	{
		if( ( *it )->type() == Track::AutomationTrack ||
			( *it )->type() == Track::HiddenAutomationTrack )
		{
			const Track::tcoVector & v = ( *it )->getTCOs();
			for( Track::tcoVector::ConstIterator j = v.begin(); j != v.end(); ++j )
			{
				const AutomationPattern * a = dynamic_cast<const AutomationPattern *>( *j );
				if( a && a->hasAutomation() )
				{
					for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k )
					{
						if( *k == _m )
						{
							return true;
						}
					}
				}
			}
		}
	}
	return false;
}

/*! \brief returns a list of all the automation patterns everywhere that are connected to a specific model
 *  \param _m the model we want to look for
 */
QVector<AutomationPattern *> AutomationPattern::patternsForModel( const AutomatableModel * _m )
{
	QVector<AutomationPattern *> patterns;
	TrackContainer::TrackList l;
	l += Engine::getSong()->tracks();
	l += Engine::getBBTrackContainer()->tracks();
	l += Engine::getSong()->globalAutomationTrack();

	// go through all tracks...
	for( TrackContainer::TrackList::ConstIterator it = l.begin(); it != l.end(); ++it )
	{
		// we want only automation tracks...
		if( ( *it )->type() == Track::AutomationTrack ||
			( *it )->type() == Track::HiddenAutomationTrack )
		{
			// get patterns in those tracks....
			const Track::tcoVector & v = ( *it )->getTCOs();
			// go through all the patterns...
			for( Track::tcoVector::ConstIterator j = v.begin(); j != v.end(); ++j )
			{
				AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
				// check that the pattern has automation
				if( a && a->hasAutomation() )
				{
					// now check is the pattern is connected to the model we want by going through all the connections
					// of the pattern
					bool has_object = false;
					for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k )
					{
						if( *k == _m )
						{
							has_object = true;
						}
					}
					// if the patterns is connected to the model, add it to the list
					if( has_object ) { patterns += a; }
				}
			}
		}
	}
	return patterns;
}

AutomationPattern * AutomationPattern::globalAutomationPattern(
							AutomatableModel * _m )
{
	AutomationTrack * t = Engine::getSong()->globalAutomationTrack();
	Track::tcoVector v = t->getTCOs();
	for( Track::tcoVector::const_iterator j = v.begin(); j != v.end(); ++j )
	{
		AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
		if( a )
		{
			for( objectVector::const_iterator k = a->m_objects.begin();
												k != a->m_objects.end(); ++k )
			{
				if( *k == _m )
				{
					return a;
				}
			}
		}
	}

	AutomationPattern * a = new AutomationPattern( t );
	a->addObject( _m, false );
	return a;
}

void AutomationPattern::resolveAllIDs()
{
	TrackContainer::TrackList l = Engine::getSong()->tracks() +
				Engine::getBBTrackContainer()->tracks();
	l += Engine::getSong()->globalAutomationTrack();
	for( TrackContainer::TrackList::iterator it = l.begin();
							it != l.end(); ++it )
	{
		if( ( *it )->type() == Track::AutomationTrack ||
			 ( *it )->type() == Track::HiddenAutomationTrack )
		{
			Track::tcoVector v = ( *it )->getTCOs();
			for( Track::tcoVector::iterator j = v.begin();
							j != v.end(); ++j )
			{
				AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
				if( a )
				{
					for( QVector<jo_id_t>::Iterator k = a->m_idsToResolve.begin();
									k != a->m_idsToResolve.end(); ++k )
					{
						JournallingObject * o = Engine::projectJournal()->
														journallingObject( *k );
						if( o && dynamic_cast<AutomatableModel *>( o ) )
						{
							a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
						}
						else
						{
							// FIXME: Remove this block once the automation system gets fixed
							// This is a temporary fix for https://github.com/LMMS/lmms/issues/3781
							o = Engine::projectJournal()->journallingObject(ProjectJournal::idFromSave(*k));
							if( o && dynamic_cast<AutomatableModel *>( o ) )
							{
								a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
							}
							else
							{
								// FIXME: Remove this block once the automation system gets fixed
								// This is a temporary fix for https://github.com/LMMS/lmms/issues/4781
								o = Engine::projectJournal()->journallingObject(ProjectJournal::idToSave(*k));
								if( o && dynamic_cast<AutomatableModel *>( o ) )
								{
									a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
								}
							}
						}
					}
					a->m_idsToResolve.clear();
					a->dataChanged();
				}
			}
		}
	}
}

void AutomationPattern::clear()
{
	m_timeMap.clear();
	m_tangents.clear();

	emit dataChanged();
}

void AutomationPattern::objectDestroyed( jo_id_t _id )
{
	// TODO: distict between temporary removal (e.g. LADSPA controls
	// when switching samplerate) and real deletions because in the latter
	// case we had to remove ourselves if we're the global automation
	// pattern of the destroyed object
	m_idsToResolve += _id;

	for( objectVector::Iterator objIt = m_objects.begin();
		objIt != m_objects.end(); objIt++ )
	{
		Q_ASSERT( !(*objIt).isNull() );
		if( (*objIt)->id() == _id )
		{
			//Assign to objIt so that this loop work even break; is removed.
			objIt = m_objects.erase( objIt );
			break;
		}
	}

	emit dataChanged();
}

void AutomationPattern::cleanObjects()
{
	for( objectVector::iterator it = m_objects.begin(); it != m_objects.end(); )
	{
		if( *it )
		{
			++it;
		}
		else
		{
			it = m_objects.erase( it );
		}
	}
}

void AutomationPattern::generateTangents()
{
	generateTangents(m_timeMap.begin(), m_timeMap.size());
}

void AutomationPattern::generateTangents( timeMap::const_iterator it,
							int numToGenerate )
{
	if( m_timeMap.size() < 2 && numToGenerate > 0 )
	{
		m_tangents[it.key()] = 0;
		return;
	}

	for( int i = 0; i < numToGenerate; i++ )
	{
		if( it == m_timeMap.begin() )
		{
			m_tangents[it.key()] =
					( (it+1).value() - (it).value() ) /
						( (it+1).key() - (it).key() );
		}
		else if( it+1 == m_timeMap.end() )
		{
			m_tangents[it.key()] = 0;
			return;
		}
		else
		{
			m_tangents[it.key()] =
					( (it+1).value() - (it-1).value() ) /
						( (it+1).key() - (it-1).key() );
		}
		it++;
	}
}


code:
/*
 * BBTrackContainer.cpp - model-component of BB-Editor
 *
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * 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 "BBTrackContainer.h"
#include "BBTrack.h"
#include "Engine.h"
#include "Song.h"

BBTrackContainer::BBTrackContainer() :
	TrackContainer(),
	m_bbComboBoxModel(this)
{
	connect(&m_bbComboBoxModel, SIGNAL(dataChanged()),
			this, SLOT(currentBBChanged()));
	// we *always* want to receive updates even in case BB actually did
	// not change upon setCurrentBB()-call
	connect(&m_bbComboBoxModel, SIGNAL(dataUnchanged()),
			this, SLOT(currentBBChanged()));
	setType(BBContainer);
}

BBTrackContainer::~BBTrackContainer()
{
}

bool BBTrackContainer::play(TimePos start, fpp_t frames, f_cnt_t offset, int tcoNum)
{
	bool notePlayed = false;

	if (lengthOfBB(tcoNum) <= 0)
	{
		return false;
	}

	start = start % (lengthOfBB(tcoNum) * TimePos::ticksPerBar());

	TrackList tl = tracks();
	for (Track * t : tl)
	{
		if (t->play(start, frames, offset, tcoNum))
		{
			notePlayed = true;
		}
	}

	return notePlayed;
}

void BBTrackContainer::updateAfterTrackAdd()
{
	if (numOfBBs() == 0 && !Engine::getSong()->isLoadingProject())
	{
		Engine::getSong()->addBBTrack();
	}
}

bar_t BBTrackContainer::lengthOfBB(int bb) const
{
	TimePos maxLength = TimePos::ticksPerBar();

	const TrackList & tl = tracks();
	for (Track * t : tl)
	{
		// Don't create TCOs here if they don't exist
		if (bb < t->numOfTCOs())
		{
			maxLength = qMax(maxLength, t->getTCO(bb)->length());
		}
	}

	return maxLength.nextFullBar();
}

int BBTrackContainer::numOfBBs() const
{
	return Engine::getSong()->countTracks(Track::BBTrack);
}

void BBTrackContainer::removeBB(int bb)
{
	TrackList tl = tracks();
	for (Track * t : tl)
	{
		delete t->getTCO(bb);
		t->removeBar(bb * DefaultTicksPerBar);
	}
	if (bb <= currentBB())
	{
		setCurrentBB(qMax(currentBB() - 1, 0));
	}
}

void BBTrackContainer::swapBB(int bb1, int bb2)
{
	TrackList tl = tracks();
	for (Track * t : tl)
	{
		t->swapPositionOfTCOs(bb1, bb2);
	}
	updateComboBox();
}

void BBTrackContainer::updateBBTrack(TrackContentObject * tco)
{
	BBTrack * t = BBTrack::findBBTrack(tco->startPosition() / DefaultTicksPerBar);
	if (t != NULL)
	{
		t->dataChanged();
	}
}

void BBTrackContainer::fixIncorrectPositions()
{
	TrackList tl = tracks();
	for (Track * t : tl)
	{
		for (int i = 0; i < numOfBBs(); ++i)
		{
			t->getTCO(i)->movePosition(TimePos(i, 0));
		}
	}
}

void BBTrackContainer::play()
{
	if (Engine::getSong()->playMode() != Song::Mode_PlayBB)
	{
		Engine::getSong()->playBB();
	}
	else
	{
		Engine::getSong()->togglePause();
	}
}

void BBTrackContainer::stop()
{
	Engine::getSong()->stop();
}

void BBTrackContainer::updateComboBox()
{
	const int curBB = currentBB();

	m_bbComboBoxModel.clear();

	for (int i = 0; i < numOfBBs(); ++i)
	{
		BBTrack * bbt = BBTrack::findBBTrack(i);
		m_bbComboBoxModel.addItem(bbt->name());
	}
	setCurrentBB(curBB);
}

void BBTrackContainer::currentBBChanged()
{
	// now update all track-labels (the current one has to become white, the others gray)
	TrackList tl = Engine::getSong()->tracks();
	for (Track * t : tl)
	{
		if (t->type() == Track::BBTrack)
		{
			t->dataChanged();
		}
	}
}

void BBTrackContainer::createTCOsForBB(int bb)
{
	TrackList tl = tracks();
	for (Track * t : tl)
	{
		t->createTCOsForBB(bb);
	}
}

AutomatedValueMap BBTrackContainer::automatedValuesAt(TimePos time, int tcoNum) const
{
	Q_ASSERT(tcoNum >= 0);
	Q_ASSERT(time.getTicks() >= 0);

	auto lengthBars = lengthOfBB(tcoNum);
	auto lengthTicks = lengthBars * TimePos::ticksPerBar();
	if (time > lengthTicks)
	{
		time = lengthTicks;
	}

	return TrackContainer::automatedValuesAt(time + (TimePos::ticksPerBar() * tcoNum), tcoNum);
}


code:
/*
 * BandLimitedWave.cpp - helper functions for band-limited
 *                    	waveform generation
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * 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 "BandLimitedWave.h"

#include <QDataStream>

WaveMipMap BandLimitedWave::s_waveforms[4] = {  };
bool BandLimitedWave::s_wavesGenerated = false;
QString BandLimitedWave::s_wavetableDir = "";

QDataStream& operator<< ( QDataStream &out, WaveMipMap &waveMipMap )
{
	for( int tbl = 0; tbl <= MAXTBL; tbl++ )
	{
		for( int i = 0; i < TLENS[tbl]; i++ )
		{
			out << waveMipMap.sampleAt( tbl, i );
		}
	}
    return out;
}

QDataStream& operator>> ( QDataStream &in, WaveMipMap &waveMipMap )
{
	sample_t sample;
	for( int tbl = 0; tbl <= MAXTBL; tbl++ )
	{
		for( int i = 0; i < TLENS[tbl]; i++ )
		{
			in >> sample;
			waveMipMap.setSampleAt( tbl, i, sample );
		}
	}
    return in;
}

void BandLimitedWave::generateWaves()
{
// don't generate if they already exist
	if( s_wavesGenerated ) return;

	int i;

// set wavetable directory
	s_wavetableDir = "data:wavetables/";

// set wavetable files
	QFile saw_file( s_wavetableDir + "saw.bin" );
	QFile sqr_file( s_wavetableDir + "sqr.bin" );
	QFile tri_file( s_wavetableDir + "tri.bin" );
	QFile moog_file( s_wavetableDir + "moog.bin" );

// saw wave - BLSaw
// check for file and use it if exists
	if( saw_file.exists() )
	{
		saw_file.open( QIODevice::ReadOnly );
		QDataStream in( &saw_file );
		in >> s_waveforms[ BandLimitedWave::BLSaw ];
		saw_file.close();
	}
	else
	{
		for( i = 0; i <= MAXTBL; i++ )
		{
			const int len = TLENS[i];
			//const double om = 1.0 / len;
			double max = 0.0;

			for( int ph = 0; ph < len; ph++ )
			{
				int harm = 1;
				double s = 0.0f;
				double hlen;
				do
				{
					hlen = static_cast<double>( len ) / static_cast<double>( harm );
					const double amp = -1.0 / static_cast<double>( harm );
					//const double a2 = cos( om * harm * F_2PI );
					s += amp * /*a2 **/sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
					harm++;
				} while( hlen > 2.0 );
				s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
				max = qMax( max, qAbs( s ) );
			}
			// normalize
			for( int ph = 0; ph < len; ph++ )
			{
				sample_t s = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, ph ) / max;
				s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
			}
		}
	}

// square wave - BLSquare
// check for file and use it if exists
	if( sqr_file.exists() )
	{
		sqr_file.open( QIODevice::ReadOnly );
		QDataStream in( &sqr_file );
		in >> s_waveforms[ BandLimitedWave::BLSquare ];
		sqr_file.close();
	}
	else
	{
		for( i = 0; i <= MAXTBL; i++ )
		{
			const int len = TLENS[i];
			//const double om = 1.0 / len;
			double max = 0.0;

			for( int ph = 0; ph < len; ph++ )
			{
				int harm = 1;
				double s = 0.0f;
				double hlen;
				do
				{
					hlen = static_cast<double>( len ) / static_cast<double>( harm );
					const double amp = 1.0 / static_cast<double>( harm );
					//const double a2 = cos( om * harm * F_2PI );
					s += amp * /*a2 **/ sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
					harm += 2;
				} while( hlen > 2.0 );
				s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
				max = qMax( max, qAbs( s ) );
			}
			// normalize
			for( int ph = 0; ph < len; ph++ )
			{
				sample_t s = s_waveforms[ BandLimitedWave::BLSquare ].sampleAt( i, ph ) / max;
				s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
			}
		}
	}

// triangle wave - BLTriangle
	if( tri_file.exists() )
	{
		tri_file.open( QIODevice::ReadOnly );
		QDataStream in( &tri_file );
		in >> s_waveforms[ BandLimitedWave::BLTriangle ];
		tri_file.close();
	}
	else
	{
		for( i = 0; i <= MAXTBL; i++ )
		{
			const int len = TLENS[i];
			//const double om = 1.0 / len;
			double max = 0.0;

			for( int ph = 0; ph < len; ph++ )
			{
				int harm = 1;
				double s = 0.0f;
				double hlen;
				do
				{
					hlen = static_cast<double>( len ) / static_cast<double>( harm );
					const double amp = 1.0 / static_cast<double>( harm * harm );
					//const double a2 = cos( om * harm * F_2PI );
					s += amp * /*a2 **/ sin( ( static_cast<double>( ph * harm ) / static_cast<double>( len ) +
							( ( harm + 1 ) % 4 == 0 ? 0.5 : 0.0 ) ) * F_2PI );
					harm += 2;
				} while( hlen > 2.0 );
				s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
				max = qMax( max, qAbs( s ) );
			}
			// normalize
			for( int ph = 0; ph < len; ph++ )
			{
				sample_t s = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph ) / max;
				s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
			}
		}
	}

// moog saw wave - BLMoog
// basically, just add in triangle + 270-phase saw
	if( moog_file.exists() )
	{
		moog_file.open( QIODevice::ReadOnly );
		QDataStream in( &moog_file );
		in >> s_waveforms[ BandLimitedWave::BLMoog ];
		moog_file.close();
	}
	else
	{
		for( i = 0; i <= MAXTBL; i++ )
		{
			const int len = TLENS[i];

			for( int ph = 0; ph < len; ph++ )
			{
				const int sawph = ( ph + static_cast<int>( len * 0.75 ) ) % len;
				const sample_t saw = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, sawph );
				const sample_t tri = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph );
				s_waveforms[ BandLimitedWave::BLMoog ].setSampleAt( i, ph, ( saw + tri ) * 0.5f );
			}
		}
	}

// set the generated flag so we don't load/generate them again needlessly
	s_wavesGenerated = true;

// generate files, serialize mipmaps as QDataStreams and save them on disk
//
// normally these are now provided with LMMS as pre-generated so we don't have to do this,
// but I'm leaving the code here in case it's needed in the future
// (maybe we add more waveforms or change the generation code or mipmap format, etc.)

/*

// if you want to generate the files, you need to set the filenames and paths here -
// can't use the usual wavetable directory here as it can require permissions on
// some systems...

QFile sawfile( "path-to-wavetables/saw.bin" );
QFile sqrfile( "path-to-wavetables/sqr.bin" );
QFile trifile( "path-to-wavetables/tri.bin" );
QFile moogfile( "path-to-wavetables/moog.bin" );

sawfile.open( QIODevice::WriteOnly );
QDataStream sawout( &sawfile );
sawout << s_waveforms[ BandLimitedWave::BLSaw ];
sawfile.close();

sqrfile.open( QIODevice::WriteOnly );
QDataStream sqrout( &sqrfile );
sqrout << s_waveforms[ BandLimitedWave::BLSquare ];
sqrfile.close();

trifile.open( QIODevice::WriteOnly );
QDataStream triout( &trifile );
triout << s_waveforms[ BandLimitedWave::BLTriangle ];
trifile.close();

moogfile.open( QIODevice::WriteOnly );
QDataStream moogout( &moogfile );
moogout << s_waveforms[ BandLimitedWave::BLMoog ];
moogfile.close();

*/

}


code:
/*
 * BufferManager.cpp - A buffer caching/memory management system
 *
 * Copyright (c) 2017 Lukas W <lukaswhl/at/gmail.com>
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2006-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * 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 "BufferManager.h"

#include "Engine.h"
#include "Mixer.h"
#include "MemoryManager.h"

static fpp_t framesPerPeriod;

void BufferManager::init( fpp_t framesPerPeriod )
{
	::framesPerPeriod = framesPerPeriod;
}

sampleFrame * BufferManager::acquire()
{
	return MM_ALLOC( sampleFrame, ::framesPerPeriod );
}

void BufferManager::clear( sampleFrame *ab, const f_cnt_t frames, const f_cnt_t offset )
{
	memset( ab + offset, 0, sizeof( *ab ) * frames );
}

#ifndef LMMS_DISABLE_SURROUND
void BufferManager::clear( surroundSampleFrame * ab, const f_cnt_t frames,
							const f_cnt_t offset )
{
	memset( ab + offset, 0, sizeof( *ab ) * frames );
}
#endif

void BufferManager::release( sampleFrame * buf )
{
	MM_FREE( buf );
}


code:
set(LMMS_SRCS
	${LMMS_SRCS}

	core/AutomatableModel.cpp
	core/AutomationPattern.cpp
	core/BandLimitedWave.cpp
	core/base64.cpp
	core/BBTrackContainer.cpp
	core/BufferManager.cpp
	core/Clipboard.cpp
	core/ComboBoxModel.cpp
	core/ConfigManager.cpp
	core/Controller.cpp
	core/ControllerConnection.cpp
	core/DataFile.cpp
	core/DrumSynth.cpp
	core/Effect.cpp
	core/EffectChain.cpp
	core/Engine.cpp
	core/EnvelopeAndLfoParameters.cpp
	core/fft_helpers.cpp
	core/FxMixer.cpp
	core/ImportFilter.cpp
	core/InlineAutomation.cpp
	core/Instrument.cpp
	core/InstrumentFunctions.cpp
	core/InstrumentPlayHandle.cpp
	core/InstrumentSoundShaping.cpp
	core/JournallingObject.cpp
	core/Ladspa2LMMS.cpp
	core/LadspaControl.cpp
	core/LadspaManager.cpp
	core/LfoController.cpp
	core/LinkedModelGroups.cpp
	core/LocklessAllocator.cpp
	core/MemoryHelper.cpp
	core/MemoryManager.cpp
	core/MeterModel.cpp
	core/MicroTimer.cpp
	core/Mixer.cpp
	core/MixerProfiler.cpp
	core/MixerWorkerThread.cpp
	core/MixHelpers.cpp
	core/Model.cpp
	core/ModelVisitor.cpp
	core/Note.cpp
	core/NotePlayHandle.cpp
	core/Oscillator.cpp
	core/PathUtil.cpp
	core/PeakController.cpp
	core/PerfLog.cpp
	core/Piano.cpp
	core/PlayHandle.cpp
	core/Plugin.cpp
	core/PluginIssue.cpp
	core/PluginFactory.cpp
	core/PresetPreviewPlayHandle.cpp
	core/ProjectJournal.cpp
	core/ProjectRenderer.cpp
	core/ProjectVersion.cpp
	core/RemotePlugin.cpp
	core/RenderManager.cpp
	core/RingBuffer.cpp
	core/SampleBuffer.cpp
	core/SamplePlayHandle.cpp
	core/SampleRecordHandle.cpp
	core/SerializingObject.cpp
	core/Song.cpp
	core/TempoSyncKnobModel.cpp
	core/TimePos.cpp
	core/ToolPlugin.cpp
	core/Track.cpp
	core/TrackContainer.cpp
	core/TrackContentObject.cpp
	core/ValueBuffer.cpp
	core/VstSyncController.cpp
	core/StepRecorder.cpp

	core/audio/AudioAlsa.cpp
	core/audio/AudioDevice.cpp
	core/audio/AudioFileDevice.cpp
	core/audio/AudioFileMP3.cpp
	core/audio/AudioFileOgg.cpp
	core/audio/AudioFileFlac.cpp
	core/audio/AudioFileWave.cpp
	core/audio/AudioJack.cpp
	core/audio/AudioOss.cpp
	core/audio/AudioSndio.cpp
	core/audio/AudioPort.cpp
	core/audio/AudioPortAudio.cpp
	core/audio/AudioSoundIo.cpp
	core/audio/AudioPulseAudio.cpp
	core/audio/AudioSampleRecorder.cpp
	core/audio/AudioSdl.cpp

	core/lv2/Lv2Basics.cpp
	core/lv2/Lv2ControlBase.cpp
	core/lv2/Lv2Evbuf.cpp
	core/lv2/Lv2Features.cpp
	core/lv2/Lv2Ports.cpp
	core/lv2/Lv2Proc.cpp
	core/lv2/Lv2Manager.cpp
	core/lv2/Lv2Options.cpp
	core/lv2/Lv2SubPluginFeatures.cpp
	core/lv2/Lv2UridCache.cpp
	core/lv2/Lv2UridMap.cpp

	core/midi/MidiAlsaRaw.cpp
	core/midi/MidiAlsaSeq.cpp
	core/midi/MidiClient.cpp
	core/midi/MidiController.cpp
	core/midi/MidiEventToByteSeq.cpp
	core/midi/MidiJack.cpp
	core/midi/MidiOss.cpp
	core/midi/MidiSndio.cpp
	core/midi/MidiApple.cpp
	core/midi/MidiPort.cpp
	core/midi/MidiWinMM.cpp

	PARENT_SCOPE
)


code:
/*
 * Clipboard.cpp - the clipboard for patterns, notes etc.
 *
 * Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * 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 <QApplication>
#include <QClipboard>
#include <QMimeData>

#include "Clipboard.h"
#include "JournallingObject.h"

namespace Clipboard
{
	const QMimeData * getMimeData()
	{
		return QApplication::clipboard()->mimeData( QClipboard::Clipboard );
	}

	bool hasFormat( MimeType mT )
	{
		return getMimeData()->hasFormat( mimeType( mT ) );
	}

	void copyString( const QString & str, MimeType mT )
	{
		QMimeData *content = new QMimeData;

		content->setData( mimeType( mT ), str.toUtf8() );
		QApplication::clipboard()->setMimeData( content, QClipboard::Clipboard );
	}

	QString getString( MimeType mT )
	{
		return QString( getMimeData()->data( mimeType( mT ) ) );
	}

	void copyStringPair( const QString & key, const QString & value )
	{
		QString finalString = key + ":" + value;

		QMimeData *content = new QMimeData;
		content->setData( mimeType( MimeType::StringPair ), finalString.toUtf8() );
		QApplication::clipboard()->setMimeData( content, QClipboard::Clipboard );
	}

	QString decodeKey( const QMimeData * mimeData )
	{
		return( QString::fromUtf8( mimeData->data( mimeType( MimeType::StringPair ) ) ).section( ':', 0, 0 ) );
	}

	QString decodeValue( const QMimeData * mimeData )
	{
		return( QString::fromUtf8( mimeData->data( mimeType( MimeType::StringPair ) ) ).section( ':', 1, -1 ) );
	}
}


code:
/*
 * ComboBoxModel.cpp - implementation of ComboBoxModel
 *
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * 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 "ComboBoxModel.h"
#include "embed.h"

using std::unique_ptr;
using std::move;

void ComboBoxModel::addItem( QString item, unique_ptr<PixmapLoader> loader )
{
	m_items.emplace_back( move(item), move(loader) );
	setRange( 0, m_items.size() - 1 );
}

void ComboBoxModel::clear()
{
	setRange( 0, 0 );

	m_items.clear();

	emit propertiesChanged();
}

int ComboBoxModel::findText( const QString& txt ) const
{
	for( auto it = m_items.begin(); it != m_items.end(); ++it )
	{
		if( ( *it ).first == txt )
		{
			return it - m_items.begin();
		}
	}
	return -1;
}