code:
/*
 * PatchesDialog.h - display GIG patches (based on Sf2 patches_dialog.h)
 *
 * Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
 *
 * 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 PATCHES_DIALOG_H
#define PATCHES_DIALOG_H

#include "ui_PatchesDialog.h"
#include "LcdSpinBox.h"
#include "GigPlayer.h"

#include <QWidget>
#include <QLabel>

//----------------------------------------------------------------------------
// qsynthPresetForm -- UI wrapper form.

class PatchesDialog : public QDialog, private Ui::PatchesDialog
{
	Q_OBJECT

public:

	// Constructor.
	PatchesDialog(QWidget * pParent = 0, Qt::WindowFlags wflags = QFlag(0));

	// Destructor.
	virtual ~PatchesDialog();

	void setup( GigInstance * pSynth, int iChan, const QString & chanName,
			LcdSpinBoxModel * bankModel, LcdSpinBoxModel * progModel, QLabel * patchLabel );

public slots:

	void stabilizeForm();
	void bankChanged();
	void progChanged( QTreeWidgetItem * curr, QTreeWidgetItem * prev );

protected slots:

	void accept();
	void reject();

protected:

	void setBankProg( int iBank, int iProg );

	QTreeWidgetItem * findBankItem( int iBank );
	QTreeWidgetItem * findProgItem( int iProg );

	bool validateForm();

private:

	// Instance variables.
	GigInstance * m_pSynth;

	int m_iChan;
	int m_iBank;
	int m_iProg;

	int m_dirty;

	LcdSpinBoxModel * m_bankModel;
	LcdSpinBoxModel * m_progModel;
	QLabel * m_patchLabel;
};

#endif


code:
<ui version="4.0" >
 <author>rncbc aka Rui Nuno Capela</author>
 <comment>qsynth - A fluidsynth Qt GUI Interface.

   Copyright (C) 2003-2007, rncbc aka Rui Nuno Capela. All rights reserved.

   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; if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

</comment>
 <class>PatchesDialog</class>
 <widget class="QDialog" name="PatchesDialog" >
  <property name="geometry" >
   <rect>
    <x>0</x>
    <y>0</y>
    <width>480</width>
    <height>350</height>
   </rect>
  </property>
  <property name="minimumSize" >
   <size>
    <width>300</width>
    <height>150</height>
   </size>
  </property>
  <property name="windowTitle" >
   <string>Qsynth: Channel Preset</string>
  </property>
  <layout class="QVBoxLayout" >
   <item>
    <widget class="QSplitter" name="m_splitter" >
     <property name="sizePolicy" >
      <sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
       <horstretch>0</horstretch>
       <verstretch>0</verstretch>
      </sizepolicy>
     </property>
     <property name="orientation" >
      <enum>Qt::Horizontal</enum>
     </property>
     <widget class="QTreeWidget" name="m_bankListView" >
      <property name="minimumSize" >
       <size>
        <width>20</width>
        <height>0</height>
       </size>
      </property>
      <property name="maximumSize" >
       <size>
        <width>80</width>
        <height>32767</height>
       </size>
      </property>
      <property name="toolTip" >
       <string>Bank selector</string>
      </property>
      <property name="alternatingRowColors" >
       <bool>true</bool>
      </property>
      <property name="indentation" >
       <number>4</number>
      </property>
      <property name="rootIsDecorated" >
       <bool>false</bool>
      </property>
      <property name="uniformRowHeights" >
       <bool>true</bool>
      </property>
      <property name="itemsExpandable" >
       <bool>false</bool>
      </property>
      <property name="sortingEnabled" >
       <bool>true</bool>
      </property>
      <property name="allColumnsShowFocus" >
       <bool>true</bool>
      </property>
      <column>
       <property name="text" >
        <string>Bank</string>
       </property>
      </column>
     </widget>
     <widget class="QTreeWidget" name="m_progListView" >
      <property name="toolTip" >
       <string>Program selector</string>
      </property>
      <property name="alternatingRowColors" >
       <bool>true</bool>
      </property>
      <property name="indentation" >
       <number>4</number>
      </property>
      <property name="rootIsDecorated" >
       <bool>false</bool>
      </property>
      <property name="uniformRowHeights" >
       <bool>true</bool>
      </property>
      <property name="itemsExpandable" >
       <bool>false</bool>
      </property>
      <property name="sortingEnabled" >
       <bool>true</bool>
      </property>
      <property name="allColumnsShowFocus" >
       <bool>true</bool>
      </property>
      <column>
       <property name="text" >
        <string>Patch</string>
       </property>
      </column>
      <column>
       <property name="text" >
        <string>Name</string>
       </property>
      </column>
     </widget>
    </widget>
   </item>
   <item>
    <layout class="QHBoxLayout" >
     <property name="spacing" >
      <number>4</number>
     </property>
     <property name="leftMargin" >
      <number>0</number>
     </property>
     <property name="topMargin" >
      <number>0</number>
     </property>
     <property name="rightMargin" >
      <number>0</number>
     </property>
     <property name="bottomMargin" >
      <number>0</number>
     </property>
     <item>
      <spacer>
       <property name="orientation" >
        <enum>Qt::Horizontal</enum>
       </property>
       <property name="sizeType" >
        <enum>QSizePolicy::Expanding</enum>
       </property>
       <property name="sizeHint" >
        <size>
         <width>120</width>
         <height>8</height>
        </size>
       </property>
      </spacer>
     </item>
     <item>
      <widget class="QPushButton" name="m_okButton" >
       <property name="toolTip" >
        <string/>
       </property>
       <property name="text" >
        <string>OK</string>
       </property>
       <property name="iconSize" >
        <size>
         <width>0</width>
         <height>0</height>
        </size>
       </property>
       <property name="default" >
        <bool>true</bool>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="m_cancelButton" >
       <property name="toolTip" >
        <string/>
       </property>
       <property name="text" >
        <string>Cancel</string>
       </property>
       <property name="iconSize" >
        <size>
         <width>0</width>
         <height>0</height>
        </size>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <layoutdefault spacing="4" margin="4" />
 <tabstops>
  <tabstop>m_okButton</tabstop>
  <tabstop>m_cancelButton</tabstop>
 </tabstops>
 <resources/>
 <connections/>
</ui>

repo_name: CYBERDEViL/LMMS

license: mit


code:
INCLUDE(BuildPlugin)

BUILD_PLUGIN(hydrogenimport HydrogenImport.cpp HydrogenImport.h local_file_mgr.cpp LocalFileMng.h)


code:
#include <QDomDocument>
#include <QDir>
#include <QApplication>
#include <QMessageBox>
#include <QProgressDialog>
#include <QTextStream>
#include <stdlib.h>

#include "LocalFileMng.h"
#include "HydrogenImport.h"
#include "Song.h"
#include "Engine.h"
#include "Instrument.h"
#include "InstrumentTrack.h"
#include "Note.h"
#include "Pattern.h"
#include "Track.h"
#include "BBTrack.h"
#include "BBTrackContainer.h"
#include "Instrument.h"

#include "plugin_export.h"

#define MAX_LAYERS 4
extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT hydrogenimport_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"Hydrogen Import",
	QT_TRANSLATE_NOOP( "PluginBrowser",
				"Filter for importing Hydrogen files into LMMS" ),
	"frank mather",
	0x0100,
	Plugin::ImportFilter,
	NULL,
	NULL,
	NULL
} ;

}

QString filename;
class NoteKey
{
public:
	enum Key {
		C = 0,
		Cs,
		D,
		Ef,
		E,
		F,
		Fs,
		G,
		Af,
		A,
		Bf,
		B,
	};

	static int stringToNoteKey( const QString& str )
	{
		int m_key = NoteKey::C;

		QString sKey = str.left( str.length() - 1 );
		QString sOct = str.mid( str.length() - 1, str.length() );

		if ( sKey.endsWith( "-" ) )
		{
			sKey.replace( "-", "" );
			sOct.insert( 0, "-" );
		}
		int nOctave = sOct.toInt();

		if ( sKey == "C" )
		{
			m_key = NoteKey::C;
		}
		else if ( sKey == "Cs" )
		{
			m_key = NoteKey::Cs;
		}
		else if ( sKey == "D" )
		{
			m_key = NoteKey::D;
		}
		else if ( sKey == "Ef" )
		{
			m_key = NoteKey::Ef;
		}
		else if ( sKey == "E" )
		{
			m_key = NoteKey::E;
		}
		else if ( sKey == "F" )
		{
			m_key = NoteKey::F;
		}
		else if ( sKey == "Fs" )
		{
			m_key = NoteKey::Fs;
		}
		else if ( sKey == "G" )
		{
			m_key = NoteKey::G;
		}
		else if ( sKey == "Af" )
		{
			m_key = NoteKey::Af;
		}
		else if ( sKey == "A" )
		{
			m_key = NoteKey::A;
		}
		else if ( sKey == "Bf" )
		{
			m_key = NoteKey::Bf;
		}
		else if ( sKey == "B" ) {
			m_key = NoteKey::B;
		}
        return m_key + (nOctave*12)+57;
	}

};
HydrogenImport::HydrogenImport( const QString & _file ) :
	ImportFilter( _file, &hydrogenimport_plugin_descriptor )
{
	filename = _file;
}

HydrogenImport::~HydrogenImport()
{
}
Instrument * ins;
bool HydrogenImport::readSong()
{
	QHash<QString, InstrumentTrack *> drum_track;
	QHash<QString, int> pattern_length;
	QHash<QString, int> pattern_id;

	Song *s = Engine::getSong();
	int song_num_tracks = s->tracks().size();
	if ( QFile( filename ).exists() == false )
	{
		printf( "Song file not found \n" );
		return false;
	}
	QDomDocument doc = LocalFileMng::openXmlDocument( filename );
	QDomNodeList nodeList = doc.elementsByTagName( "song" );

	if( nodeList.isEmpty() )
	{
		printf( "Error reading song: song node not found\n" );
		return false;
	}
	QDomNode songNode = nodeList.at( 0 );

	QString m_sSongVersion = LocalFileMng::readXmlString( songNode , "version", "Unknown version" );

	QString sName( LocalFileMng::readXmlString( songNode, "name", "Untitled Song" ) );
	QString sAuthor( LocalFileMng::readXmlString( songNode, "author", "Unknown Author" ) );
	QString sNotes( LocalFileMng::readXmlString( songNode, "notes", "..." ) );
	QString sLicense( LocalFileMng::readXmlString( songNode, "license", "Unknown license" ) );
	QString sMode = LocalFileMng::readXmlString( songNode, "mode", "pattern" );

	QDomNode instrumentListNode = songNode.firstChildElement( "instrumentList" );
	if ( ( ! instrumentListNode.isNull()  ) )
	{

		int instrumentList_count = 0;
		QDomNode instrumentNode;
		instrumentNode = instrumentListNode.firstChildElement( "instrument" );
		while ( ! instrumentNode.isNull()  )
		{
			instrumentList_count++;
			QString sId = LocalFileMng::readXmlString( instrumentNode, "id", "" );			// instrument id
			QString sDrumkit = LocalFileMng::readXmlString( instrumentNode, "drumkit", "" );	// drumkit
			QString sName = LocalFileMng::readXmlString( instrumentNode, "name", "" );		// name
			float fVolume = LocalFileMng::readXmlFloat( instrumentNode, "volume", 1.0 );	// volume
			float fPan_L = LocalFileMng::readXmlFloat( instrumentNode, "pan_L", 0.5 );	// pan L
			float fPan_R = LocalFileMng::readXmlFloat( instrumentNode, "pan_R", 0.5 );	// pan R

			if ( sId.isEmpty() ) {
				printf( "Empty ID for instrument. skipping \n" );
				instrumentNode = (QDomNode) instrumentNode.nextSiblingElement( "instrument" );
				continue;
			}
			QDomNode filenameNode = instrumentNode.firstChildElement( "filename" );

			if ( ! filenameNode.isNull() )
			{
				return false;
			}
			else
			{
				unsigned nLayer = 0;
				QDomNode instrumentComponentNode = instrumentNode.firstChildElement("instrumentComponent");
				if (instrumentComponentNode.isNull())
				{
					instrumentComponentNode = instrumentNode;
				}

				QDomNode layerNode = instrumentComponentNode.firstChildElement( "layer" );
				while (  ! layerNode.isNull()  )
				{
					if ( nLayer >= MAX_LAYERS )
					{
						printf("nLayer >= MAX_LAYERS\n");
						break;
					}
					QString sFilename = LocalFileMng::readXmlString( layerNode, "filename", "" );
					QString sMode = LocalFileMng::readXmlString( layerNode, "smode", "forward" );

					if ( nLayer == 0 )
					{
						drum_track[sId] = ( InstrumentTrack * ) Track::create( Track::InstrumentTrack,Engine::getBBTrackContainer() );
						drum_track[sId]->volumeModel()->setValue( fVolume * 100 );
						drum_track[sId]->panningModel()->setValue( ( fPan_R - fPan_L ) * 100 );
						ins = drum_track[sId]->loadInstrument( "audiofileprocessor" );
						ins->loadFile( sFilename );
					}
					nLayer++;
					layerNode = ( QDomNode ) layerNode.nextSiblingElement( "layer" );
				}
			}

			instrumentNode = (QDomNode) instrumentNode.nextSiblingElement( "instrument" );
		}
		if ( instrumentList_count == 0 )
		{
			return false;
		}
	}
	else
	{
		return false;
	}
	QDomNode patterns = songNode.firstChildElement( "patternList" );
	int pattern_count = 0;
	int nbb = Engine::getBBTrackContainer()->numOfBBs();
	QDomNode patternNode =  patterns.firstChildElement( "pattern" );
	int pn = 1;
	while (  !patternNode.isNull()  )
	{
		if ( pn > 0 )
		{
			pattern_count++;
			s->addBBTrack();
			pn = 0;
		}
		QString sName;	// name
		sName = LocalFileMng::readXmlString( patternNode, "name", sName );

		QString sCategory = ""; // category
		sCategory = LocalFileMng::readXmlString( patternNode, "category", sCategory ,false ,false );
		int nSize = -1;
		nSize = LocalFileMng::readXmlInt( patternNode, "size", nSize, false, false );
		pattern_length[sName] = nSize;
		QDomNode pNoteListNode = patternNode.firstChildElement( "noteList" );
		if ( ! pNoteListNode.isNull() ) {
			QDomNode noteNode = pNoteListNode.firstChildElement( "note" );
			while ( ! noteNode.isNull()  ) {
				int nPosition = LocalFileMng::readXmlInt( noteNode, "position", 0 );
				float fVelocity = LocalFileMng::readXmlFloat( noteNode, "velocity", 0.8f );
				float fPan_L = LocalFileMng::readXmlFloat( noteNode, "pan_L", 0.5 );
				float fPan_R = LocalFileMng::readXmlFloat( noteNode, "pan_R", 0.5 );
				QString sKey = LocalFileMng::readXmlString( noteNode, "key", "C0", false, false );
				QString nNoteOff = LocalFileMng::readXmlString( noteNode, "note_off", "false", false, false );

				QString instrId = LocalFileMng::readXmlString( noteNode, "instrument", 0,false, false );
				int i = pattern_count - 1 + nbb;
				pattern_id[sName] = pattern_count - 1;
				Pattern*p = dynamic_cast<Pattern*>( drum_track[instrId]->getTCO( i ) );
				Note n;
				n.setPos( nPosition );
				if ( (nPosition + 48) <= nSize )
				{
  					n.setLength( 48 );
				}
				else
				{
					n.setLength( nSize - nPosition );
				}
				n.setVolume( fVelocity * 100 );
				n.setPanning( ( fPan_R - fPan_L ) * 100 );
				n.setKey( NoteKey::stringToNoteKey( sKey ) );
				p->addNote( n,false );
				pn = pn + 1;
				noteNode = ( QDomNode ) noteNode.nextSiblingElement( "note" );
			}
		}
		patternNode = ( QDomNode ) patternNode.nextSiblingElement( "pattern" );
	}
	// Pattern sequence
	QDomNode patternSequenceNode = songNode.firstChildElement( "patternSequence" );
	QDomNode groupNode = patternSequenceNode.firstChildElement( "group" );
	int pos = 0;
	while (  !groupNode.isNull()  )
	{
	    int best_length = 0;
		QDomNode patternId = groupNode.firstChildElement( "patternID" );
		while (  !patternId.isNull()  )
		{
			QString patId = patternId.firstChild().nodeValue();
			patternId = ( QDomNode ) patternId.nextSiblingElement( "patternID" );

			int i = pattern_id[patId]+song_num_tracks;
			Track *t = ( BBTrack * ) s->tracks().at( i );
			t->createTCO(pos);

			if ( pattern_length[patId] > best_length )
			{
				best_length = pattern_length[patId];
			}
		}
		pos = pos + best_length;
		groupNode = groupNode.nextSiblingElement( "group" );
	}

	if ( pattern_count == 0 )
	{
		return false;
	}
	return true;
}
bool HydrogenImport::tryImport( TrackContainer* tc )
{
	if( openFile() == false )
	{
		return false;
	}
	return readSong();
}

extern "C"
{

// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *, void * _data )
{
	return new HydrogenImport( QString::fromUtf8(
									static_cast<const char *>( _data ) ) );
}

}


code:
#ifndef _HYDROGEN_IMPORT_H
#define _HYDROGEN_IMPORT_H

#include <QString>
#include <QPair>
#include <QVector>

#include "ImportFilter.h"

class HydrogenImport : public ImportFilter
{
public:
	HydrogenImport( const QString & _file );
        bool readSong();

	virtual ~HydrogenImport();

	virtual PluginView * instantiateView( QWidget * )
	{
		return( NULL );
	}
private:
	virtual bool tryImport( TrackContainer* tc );
};
#endif


code:
#ifndef LFILEMNG_H
#define LFILEMNG_H

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <QDomDocument>

class LocalFileMng
{
public:
	LocalFileMng();
	~LocalFileMng();
	std::vector<QString> getallPatternList(){
		return m_allPatternList;
	}

	static QString readXmlString( QDomNode , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false);
	static float readXmlFloat( QDomNode , const QString& nodeName, float defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false);
	static int readXmlInt( QDomNode , const QString& nodeName, int defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false);
	static bool readXmlBool( QDomNode , const QString& nodeName, bool defaultValue, bool bShouldExists = true , bool tinyXmlCompatMode = false );
	static void convertFromTinyXMLString( QByteArray* str );
	static bool checkTinyXMLCompatMode( const QString& filename );
	static QDomDocument openXmlDocument( const QString& filename );
	std::vector<QString> m_allPatternList;
};
#endif //LFILEMNG_H


code:
#include <cstdlib>
#include <cassert>
#include <sys/stat.h>
#include <ctype.h>

#include <QDir>
#include <QApplication>
#include <QVector>
#include <QDomDocument>
#include <QLocale>
#include <QTextCodec>

#include <algorithm>
#include "LocalFileMng.h"

/* New QtXml based methods */

QString LocalFileMng::readXmlString( QDomNode node , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode)
{
 	QDomElement element = node.firstChildElement( nodeName );

	if( !node.isNull() && !element.isNull() ){
		if(  !element.text().isEmpty() ){
			return element.text();
		} else {
			if ( !bCanBeEmpty ) {
				//_WARNINGLOG( "Using default value in " + nodeName );
			}
			return defaultValue;
		}
	} else {
		if(  bShouldExists ){
			//_WARNINGLOG( "'" + nodeName + "' node not found" );

		}
		return defaultValue;
	}
}

float LocalFileMng::readXmlFloat( QDomNode node , const QString& nodeName, float defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode)
{
	QLocale c_locale = QLocale::c();
 	QDomElement element = node.firstChildElement( nodeName );

	if( !node.isNull() && !element.isNull() ){
		if(  !element.text().isEmpty() ){
			return c_locale.toFloat(element.text());
		} else {
			if ( !bCanBeEmpty ) {
				//_WARNINGLOG( "Using default value in " + nodeName );
			}
			return defaultValue;
		}
	} else {
		if(  bShouldExists ){
			//_WARNINGLOG( "'" + nodeName + "' node not found" );
		}
		return defaultValue;
	}
}

int LocalFileMng::readXmlInt( QDomNode node , const QString& nodeName, int defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode)
{
	QLocale c_locale = QLocale::c();
 	QDomElement element = node.firstChildElement( nodeName );

	if( !node.isNull() && !element.isNull() ){
		if(  !element.text().isEmpty() ){
			return c_locale.toInt( element.text() );
		} else {
			if ( !bCanBeEmpty ) {
				//_WARNINGLOG( "Using default value in " + nodeName );
			}
			return defaultValue;
		}
	} else {
		if(  bShouldExists ){
			//_WARNINGLOG( "'" + nodeName + "' node not found" );
		}
		return defaultValue;
	}
}

bool LocalFileMng::readXmlBool( QDomNode node , const QString& nodeName, bool defaultValue, bool bShouldExists, bool tinyXmlCompatMode)
{
 	QDomElement element = node.firstChildElement( nodeName );

	if( !node.isNull() && !element.isNull() ){
		if(  !element.text().isEmpty() ){
			if( element.text() == "true"){
				return true;
			} else {
				return false;
			}
		} else {
			//_WARNINGLOG( "Using default value in " + nodeName );
			return defaultValue;
		}
	} else {
		if(  bShouldExists ){
			//_WARNINGLOG( "'" + nodeName + "' node not found" );
		}
		return defaultValue;
	}
}

/* Convert (in-place) an XML escape sequence into a literal byte,
 * rather than the character it actually refers to.
 */
void LocalFileMng::convertFromTinyXMLString( QByteArray* str )
{
	/* When TinyXML encountered a non-ASCII character, it would
	 * simply write the character as "&#xx;" -- where "xx" is
	 * the hex character code.  However, this doesn't respect
	 * any encodings (e.g. UTF-8, UTF-16).  In XML, &#xx; literally
	 * means "the Unicode character # xx."  However, in a UTF-8
	 * sequence, this could be an escape character that tells
	 * whether we have a 2, 3, or 4-byte UTF-8 sequence.
	 *
	 * For example, the UTF-8 sequence 0xD184 was being written
	 * by TinyXML as "Ñ„".  However, this is the UTF-8
	 * sequence for the cyrillic small letter EF (which looks
	 * kind of like a thorn or a greek phi).  This letter, in
	 * XML, should be saved as &#x00000444;, or even literally
	 * (no escaping).  As a consequence, when Ñ is read
	 * by an XML parser, it will be interpreted as capital N
	 * with a tilde (~).  Then „ will be interpreted as
	 * an unknown or control character.
	 *
	 * So, when we know that TinyXML wrote the file, we can
	 * simply exchange these hex sequences to literal bytes.
	 */
	int pos = 0;

	pos = str->indexOf("&#x");
	while( pos != -1 ) {
		if( isxdigit(str->at(pos+3))
		    && isxdigit(str->at(pos+4))
		    && (str->at(pos+5) == ';') ) {
			char w1 = str->at(pos+3);
			char w2 = str->at(pos+4);

			w1 = tolower(w1) - 0x30;  // '0' = 0x30
			if( w1 > 9 ) w1 -= 0x27;  // '9' = 0x39, 'a' = 0x61
			w1 = (w1 & 0xF);

			w2 = tolower(w2) - 0x30;  // '0' = 0x30
			if( w2 > 9 ) w2 -= 0x27;  // '9' = 0x39, 'a' = 0x61
			w2 = (w2 & 0xF);

			char ch = (w1 << 4) | w2;
			(*str)[pos] = ch;
			++pos;
			str->remove(pos, 5);
		}
		pos = str->indexOf("&#x");
	}
}

bool LocalFileMng::checkTinyXMLCompatMode( const QString& filename )
{
	/*
		Check if filename was created with TinyXml or QtXml
		TinyXML: return true
		QtXml: return false
	*/

	QFile file( filename );

	if ( !file.open(QIODevice::ReadOnly) )
		return false;

	QString line = file.readLine();
	file.close();
	if ( line.startsWith( "<?xml" )){
		return false;
	} else  {
		//_WARNINGLOG( QString("File '%1' is being read in "
		//		    "TinyXML compatability mode")
		//	    .arg(filename) );
		return true;
	}

}

QDomDocument LocalFileMng::openXmlDocument( const QString& filename )
{
	bool TinyXMLCompat = LocalFileMng::checkTinyXMLCompatMode( filename );

	QDomDocument doc;
	QFile file( filename );

	if ( !file.open(QIODevice::ReadOnly) )
		return QDomDocument();

	if( TinyXMLCompat ) {
	    QString enc = QTextCodec::codecForLocale()->name();
	    if( enc == QString("System") ) {
		    enc = "UTF-8";
	    }
	    QByteArray line;
	    QByteArray buf = QString("<?xml version='1.0' encoding='%1' ?>\n")
		.arg( enc )
		.toLocal8Bit();

	    //_INFOLOG( QString("Using '%1' encoding for TinyXML file").arg(enc) );

	    while( !file.atEnd() ) {
			line = file.readLine();
			LocalFileMng::convertFromTinyXMLString( &line );
			buf += line;
	    }

	    if( ! doc.setContent( buf ) ) {
			file.close();
			return QDomDocument();
	    }

	} else {
	    if( ! doc.setContent( &file ) ) {
			file.close();
			return QDomDocument();
	    }
	}
	file.close();

	return doc;
}


code:
INCLUDE(BuildPlugin)

BUILD_PLUGIN(ladspaeffect LadspaEffect.cpp LadspaControls.cpp LadspaControlDialog.cpp LadspaSubPluginFeatures.cpp LadspaEffect.h LadspaControls.h LadspaControlDialog.h LadspaSubPluginFeatures.h MOCFILES LadspaEffect.h LadspaControls.h LadspaControlDialog.h EMBEDDED_RESOURCES logo.png)

SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ladspa")

IF(WANT_CAPS)
ADD_SUBDIRECTORY(caps)
ENDIF(WANT_CAPS)

IF(WANT_TAP)
ADD_SUBDIRECTORY(tap)
ENDIF(WANT_TAP)

IF(WANT_SWH)
ADD_SUBDIRECTORY(swh)
ENDIF(WANT_SWH)

IF(WANT_CMT)
ADD_SUBDIRECTORY(cmt)
ENDIF(WANT_CMT)

IF(WANT_CALF)
ADD_SUBDIRECTORY(calf)
ENDIF(WANT_CALF)


code:
/*
 * LadspaControlDialog.cpp - dialog for displaying and editing control port
 *                             values for LADSPA plugins
 *
 * Copyright (c) 2006-2008 Danny McRae <khjklujn/at/users.sourceforge.net>
 * Copyright (c) 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 <cmath>

#include <QGroupBox>
#include <QLayout>

#include "LadspaEffect.h"
#include "LadspaControlDialog.h"
#include "LadspaControlView.h"
#include "LedCheckbox.h"

LadspaControlDialog::LadspaControlDialog( LadspaControls * _ctl ) :
	EffectControlDialog( _ctl ),
	m_effectLayout( NULL ),
	m_stereoLink( NULL )
{
	QVBoxLayout * mainLay = new QVBoxLayout( this );

	m_effectLayout = new QHBoxLayout();
	mainLay->addLayout( m_effectLayout );

	updateEffectView( _ctl );

	if( _ctl->m_processors > 1 )
	{
		mainLay->addSpacing( 3 );
		QHBoxLayout * center = new QHBoxLayout();
		mainLay->addLayout( center );
		m_stereoLink = new LedCheckBox( tr( "Link Channels" ), this );
		m_stereoLink->setModel( &_ctl->m_stereoLinkModel );
		center->addWidget( m_stereoLink );
	}
}

LadspaControlDialog::~LadspaControlDialog()
{
}

void LadspaControlDialog::updateEffectView( LadspaControls * _ctl )
{
	QList<QGroupBox *> list = findChildren<QGroupBox *>();
	for( QList<QGroupBox *>::iterator it = list.begin(); it != list.end();
									++it )
	{
		delete *it;
	}

	m_effectControls = _ctl;

	const int cols = static_cast<int>( sqrt(
		static_cast<double>( _ctl->m_controlCount /
						_ctl->m_processors ) ) );
	for( ch_cnt_t proc = 0; proc < _ctl->m_processors; proc++ )
	{
		control_list_t & controls = _ctl->m_controls[proc];
		int row = 0;
		int col = 0;
		buffer_data_t last_port = NONE;

		QGroupBox * grouper;
		if( _ctl->m_processors > 1 )
		{
			grouper = new QGroupBox( tr( "Channel " ) +
						QString::number( proc + 1 ),
								this );
		}
		else
		{
			grouper = new QGroupBox( this );
		}

		QGridLayout * gl = new QGridLayout( grouper );
		grouper->setLayout( gl );
		grouper->setAlignment( Qt::Vertical );

		for( control_list_t::iterator it = controls.begin();
						it != controls.end(); ++it )
		{
			if( (*it)->port()->proc == proc )
			{
				buffer_data_t this_port = (*it)->port()->data_type;
				if( last_port != NONE &&
					( this_port == TOGGLED || this_port == ENUM ) &&
					( last_port != TOGGLED && last_port != ENUM ) )
				{
					++row;
					col = 0;
				}
				gl->addWidget( new LadspaControlView( grouper, *it ), row, col );
				if( ++col == cols )
				{
					++row;
					col = 0;
				}
				last_port = (*it)->port()->data_type;
			}
		}

		m_effectLayout->addWidget( grouper );
	}

	if( _ctl->m_processors > 1 && m_stereoLink != NULL )
	{
		m_stereoLink->setModel( &_ctl->m_stereoLinkModel );
	}

	connect( _ctl, SIGNAL( effectModelChanged( LadspaControls * ) ),
				this, SLOT( updateEffectView( LadspaControls * ) ),
							Qt::DirectConnection );
}


code:
/*
 * LadspaControlDialog.h - dialog for displaying and editing control port
 *                         values for LADSPA plugins
 *
 * Copyright (c) 2006-2008 Danny McRae <khjklujn/at/users.sourceforge.net>
 * Copyright (c) 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.
 *
 */

#ifndef LADSPA_CONTROL_DIALOG_H
#define LADSPA_CONTROL_DIALOG_H

#include "EffectControlDialog.h"

class QHBoxLayout;
class LadspaControls;
class LedCheckBox;

class LadspaControlDialog : public EffectControlDialog
{
	Q_OBJECT
public:
	LadspaControlDialog( LadspaControls * _ctl );
	~LadspaControlDialog();

private slots:
	void updateEffectView( LadspaControls * _ctl );

private:
	QHBoxLayout * m_effectLayout;
	LedCheckBox * m_stereoLink;

} ;

#endif


code:
/*
 * LadspaControls.cpp - model for LADSPA plugin controls
 *
 * 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 <QDomElement>

#include "LadspaEffect.h"

LadspaControls::LadspaControls( LadspaEffect * _eff ) :
	EffectControls( _eff ),
	m_effect( _eff ),
	m_processors( _eff->processorCount() ),
	m_noLink( false ),
	m_stereoLinkModel( true, this )
{

	connect( &m_stereoLinkModel, SIGNAL( dataChanged() ),
				this, SLOT( updateLinkStatesFromGlobal() ),
				Qt::DirectConnection );

	multi_proc_t controls = m_effect->getPortControls();
	m_controlCount = controls.count();

	for( ch_cnt_t proc = 0; proc < m_processors; proc++ )
	{
		control_list_t p;

		const bool linked_control = ( m_processors > 1 && proc == 0 );

		for( multi_proc_t::Iterator it = controls.begin(); it != controls.end(); it++ )
		{
			if( (*it)->proc == proc )
			{
				(*it)->control = new LadspaControl( this, *it,
							linked_control );

				p.append( (*it)->control );

				if( linked_control )
				{
					connect( (*it)->control, SIGNAL( linkChanged( int, bool ) ),
								this, SLOT( linkPort( int, bool ) ),
								Qt::DirectConnection );
				}
			}
		}

		m_controls.append( p );
	}

	// now link all controls
	if( m_processors > 1 )
	{
		for( multi_proc_t::Iterator it = controls.begin();
						it != controls.end(); it++ )
		{
			if( (*it)->proc == 0 )
			{
				linkPort( ( *it )->control_id, true );
			}
		}
	}
}

LadspaControls::~LadspaControls()
{
	for( ch_cnt_t proc = 0; proc < m_processors; proc++ )
	{
		m_controls[proc].clear();
	}
	m_controls.clear();
}

void LadspaControls::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
	if( m_processors > 1 )
	{
		_this.setAttribute( "link", m_stereoLinkModel.value() );
	}

	multi_proc_t controls = m_effect->getPortControls();
	_this.setAttribute( "ports", controls.count() );
	for( multi_proc_t::Iterator it = controls.begin();
						it != controls.end(); it++ )
	{
		QString n = "port" + QString::number( (*it)->proc ) +
					QString::number( (*it)->port_id );
		(*it)->control->saveSettings( _doc, _this, n );
	}
}

void LadspaControls::loadSettings( const QDomElement & _this )
{
	if( m_processors > 1 )
	{
		m_stereoLinkModel.setValue( _this.attribute( "link" ).toInt() );
	}

	multi_proc_t controls = m_effect->getPortControls();
	for( multi_proc_t::Iterator it = controls.begin();
						it != controls.end(); it++ )
	{
		QString n = "port" + QString::number( (*it)->proc ) +
					QString::number( (*it)->port_id );
		(*it)->control->loadSettings( _this, n );
	}
}

void LadspaControls::linkPort( int _port, bool _state )
{
	LadspaControl * first = m_controls[0][_port];
	if( _state )
	{
		for( ch_cnt_t proc = 1; proc < m_processors; proc++ )
		{
			first->linkControls( m_controls[proc][_port] );
		}
	}
	else
	{
		for( ch_cnt_t proc = 1; proc < m_processors; proc++ )
		{
			first->unlinkControls( m_controls[proc][_port] );
		}

		// m_stereoLinkModel.setValue() will call updateLinkStatesFromGlobal()
		// m_noLink will make sure that this will not unlink any other ports
		m_noLink = true;
		m_stereoLinkModel.setValue( false );
	}
}

void LadspaControls::updateLinkStatesFromGlobal()
{
	if( m_stereoLinkModel.value() )
	{
		for( int port = 0; port < m_controlCount / m_processors; port++ )
		{
			m_controls[0][port]->setLink( true );
		}
	}
	else if( !m_noLink )
	{
		for( int port = 0; port < m_controlCount / m_processors; port++ )
		{
			m_controls[0][port]->setLink( false );
		}
	}

	// if global channel link state has changed, always ignore link
	// status of individual ports in the future
	m_noLink = false;
}


code:
/*
 * LadspaControls.h - model for LADSPA plugin controls
 *
 * 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.
 *
 */

#ifndef LADSPA_CONTROLS_H
#define LADSPA_CONTROLS_H

#include "EffectControls.h"
#include "LadspaControl.h"
#include "LadspaControlDialog.h"

typedef QVector<LadspaControl *> control_list_t;

class LadspaEffect;

class LadspaControls : public EffectControls
{
	Q_OBJECT
public:
	LadspaControls( LadspaEffect * _eff );
	virtual ~LadspaControls();

	inline int controlCount()
	{
		return m_controlCount;
	}

	virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent );
	virtual void loadSettings( const QDomElement & _this );
	inline virtual QString nodeName() const
	{
		return "ladspacontrols";
	}

	virtual EffectControlDialog * createView()
	{
		return new LadspaControlDialog( this );
	}

protected slots:
	void updateLinkStatesFromGlobal();
	void linkPort( int _port, bool _state );

private:
	LadspaEffect * m_effect;
	ch_cnt_t m_processors;
	ch_cnt_t m_controlCount;
	bool m_noLink;
	BoolModel m_stereoLinkModel;
	//! control vector for each processor
	QVector<control_list_t> m_controls;

	friend class LadspaControlDialog;
	friend class LadspaEffect;

signals:
	void effectModelChanged( LadspaControls * );

} ;

#endif


code:
/*
 * LadspaEffect.cpp - class for processing LADSPA effects
 *
 * Copyright (c) 2006-2008 Danny McRae <khjklujn/at/users.sourceforge.net>
 * Copyright (c) 2009-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 <QtCore/QVarLengthArray>
#include <QMessageBox>

#include "LadspaEffect.h"
#include "DataFile.h"
#include "AudioDevice.h"
#include "ConfigManager.h"
#include "Ladspa2LMMS.h"
#include "LadspaControl.h"
#include "LadspaSubPluginFeatures.h"
#include "Mixer.h"
#include "EffectChain.h"
#include "AutomationPattern.h"
#include "ControllerConnection.h"
#include "MemoryManager.h"
#include "ValueBuffer.h"
#include "Song.h"

#include "embed.h"

#include "plugin_export.h"

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT ladspaeffect_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"LADSPA",
	QT_TRANSLATE_NOOP( "PluginBrowser",
				"plugin for using arbitrary LADSPA-effects "
				"inside LMMS." ),
	"Danny McRae <khjklujn/at/users.sourceforge.net>",
	0x0100,
	Plugin::Effect,
	new PluginPixmapLoader("logo"),
	NULL,
	new LadspaSubPluginFeatures( Plugin::Effect )
} ;

}

LadspaEffect::LadspaEffect( Model * _parent,
			const Descriptor::SubPluginFeatures::Key * _key ) :
	Effect( &ladspaeffect_plugin_descriptor, _parent, _key ),
	m_controls( NULL ),
	m_maxSampleRate( 0 ),
	m_key( LadspaSubPluginFeatures::subPluginKeyToLadspaKey( _key ) )
{
	Ladspa2LMMS * manager = Engine::getLADSPAManager();
	if( manager->getDescription( m_key ) == NULL )
	{
		Engine::getSong()->collectError(tr( "Unknown LADSPA plugin %1 requested." ).arg(
											m_key.second ) );
		setOkay( false );
		return;
	}

	setDisplayName( manager->getShortName( m_key ) );

	pluginInstantiation();

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

LadspaEffect::~LadspaEffect()
{
	pluginDestruction();
}

void LadspaEffect::changeSampleRate()
{
	DataFile dataFile( DataFile::EffectSettings );
	m_controls->saveState( dataFile, dataFile.content() );

	LadspaControls * controls = m_controls;
	m_controls = NULL;

	m_pluginMutex.lock();
	pluginDestruction();
	pluginInstantiation();
	m_pluginMutex.unlock();

	controls->effectModelChanged( m_controls );
	delete controls;

	m_controls->restoreState( dataFile.content().firstChild().toElement() );

	// the IDs of re-created controls have been saved and now need to be
	// resolved again
	AutomationPattern::resolveAllIDs();
}

bool LadspaEffect::processAudioBuffer( sampleFrame * _buf,
							const fpp_t _frames )
{
	m_pluginMutex.lock();
	if( !isOkay() || dontRun() || !isRunning() || !isEnabled() )
	{
		m_pluginMutex.unlock();
		return( false );
	}

	int frames = _frames;
	sampleFrame * o_buf = NULL;
	QVarLengthArray<sample_t> sBuf(_frames * DEFAULT_CHANNELS);

	if( m_maxSampleRate < Engine::mixer()->processingSampleRate() )
	{
		o_buf = _buf;
		_buf = reinterpret_cast<sampleFrame*>(sBuf.data());
		sampleDown( o_buf, _buf, m_maxSampleRate );
		frames = _frames * m_maxSampleRate /
				Engine::mixer()->processingSampleRate();
	}

	// Copy the LMMS audio buffer to the LADSPA input buffer and initialize
	// the control ports.
	ch_cnt_t channel = 0;
	for( ch_cnt_t proc = 0; proc < processorCount(); ++proc )
	{
		for( int port = 0; port < m_portCount; ++port )
		{
			port_desc_t * pp = m_ports.at( proc ).at( port );
			switch( pp->rate )
			{
				case CHANNEL_IN:
					for( fpp_t frame = 0;
						frame < frames; ++frame )
					{
						pp->buffer[frame] =
							_buf[frame][channel];
					}
					++channel;
					break;
				case AUDIO_RATE_INPUT:
				{
					ValueBuffer * vb = pp->control->valueBuffer();
					if( vb )
					{
						memcpy( pp->buffer, vb->values(), frames * sizeof(float) );
					}
					else
					{
						pp->value = static_cast<LADSPA_Data>(
											pp->control->value() / pp->scale );
						// This only supports control rate ports, so the audio rates are
						// treated as though they were control rate by setting the
						// port buffer to all the same value.
						for( fpp_t frame = 0;
							frame < frames; ++frame )
						{
							pp->buffer[frame] =
								pp->value;
						}
					}
					break;
				}
				case CONTROL_RATE_INPUT:
					if( pp->control == NULL )
					{
						break;
					}
					pp->value = static_cast<LADSPA_Data>(
										pp->control->value() / pp->scale );
					pp->buffer[0] =
						pp->value;
					break;
				case CHANNEL_OUT:
				case AUDIO_RATE_OUTPUT:
				case CONTROL_RATE_OUTPUT:
					break;
				default:
					break;
			}
		}
	}

	// Process the buffers.
	for( ch_cnt_t proc = 0; proc < processorCount(); ++proc )
	{
		(m_descriptor->run)( m_handles[proc], frames );
	}

	// Copy the LADSPA output buffers to the LMMS buffer.
	double out_sum = 0.0;
	channel = 0;
	const float d = dryLevel();
	const float w = wetLevel();
	for( ch_cnt_t proc = 0; proc < processorCount(); ++proc )
	{
		for( int port = 0; port < m_portCount; ++port )
		{
			port_desc_t * pp = m_ports.at( proc ).at( port );
			switch( pp->rate )
			{
				case CHANNEL_IN:
				case AUDIO_RATE_INPUT:
				case CONTROL_RATE_INPUT:
					break;
				case CHANNEL_OUT:
					for( fpp_t frame = 0;
						frame < frames; ++frame )
					{
						_buf[frame][channel] = d * _buf[frame][channel] + w * pp->buffer[frame];
						out_sum += _buf[frame][channel] * _buf[frame][channel];
					}
					++channel;
					break;
				case AUDIO_RATE_OUTPUT:
				case CONTROL_RATE_OUTPUT:
					break;
				default:
					break;
			}
		}
	}

	if( o_buf != NULL )
	{
		sampleBack( _buf, o_buf, m_maxSampleRate );
	}

	checkGate( out_sum / frames );

	bool is_running = isRunning();
	m_pluginMutex.unlock();
	return( is_running );
}

void LadspaEffect::setControl( int _control, LADSPA_Data _value )
{
	if( !isOkay() )
	{
		return;
	}
	m_portControls[_control]->value = _value;
}

void LadspaEffect::pluginInstantiation()
{
	m_maxSampleRate = maxSamplerate( displayName() );

	Ladspa2LMMS * manager = Engine::getLADSPAManager();

	// Calculate how many processing units are needed.
	const ch_cnt_t lmms_chnls = Engine::mixer()->audioDev()->channels();
	int effect_channels = manager->getDescription( m_key )->inputChannels;
	setProcessorCount( lmms_chnls / effect_channels );

	// get inPlaceBroken property
	m_inPlaceBroken = manager->isInplaceBroken( m_key );

	// Categorize the ports, and create the buffers.
	m_portCount = manager->getPortCount( m_key );

	int inputch = 0;
	int outputch = 0;
	LADSPA_Data * inbuf [2];
	inbuf[0] = NULL;
	inbuf[1] = NULL;
	for( ch_cnt_t proc = 0; proc < processorCount(); proc++ )
	{
		multi_proc_t ports;
		for( int port = 0; port < m_portCount; port++ )
		{
			port_desc_t * p = new PortDescription;

			p->name = manager->getPortName( m_key, port );
			p->proc = proc;
			p->port_id = port;
			p->control = NULL;
			p->buffer = NULL;

			// Determine the port's category.
			if( manager->isPortAudio( m_key, port ) )
			{
				if( p->name.toUpper().contains( "IN" ) &&
					manager->isPortInput( m_key, port ) )
				{
					p->rate = CHANNEL_IN;
					p->buffer = MM_ALLOC( LADSPA_Data, Engine::mixer()->framesPerPeriod() );
					inbuf[ inputch ] = p->buffer;
					inputch++;
				}
				else if( p->name.toUpper().contains( "OUT" ) &&
					manager->isPortOutput( m_key, port ) )
				{
					p->rate = CHANNEL_OUT;
					if( ! m_inPlaceBroken && inbuf[ outputch ] )
					{
						p->buffer = inbuf[ outputch ];
						outputch++;
					}
					else
					{
						p->buffer = MM_ALLOC( LADSPA_Data, Engine::mixer()->framesPerPeriod() );
						m_inPlaceBroken = true;
					}
				}
				else if( manager->isPortInput( m_key, port ) )
				{
					p->rate = AUDIO_RATE_INPUT;
					p->buffer = MM_ALLOC( LADSPA_Data, Engine::mixer()->framesPerPeriod() );
				}
				else
				{
					p->rate = AUDIO_RATE_OUTPUT;
					p->buffer = MM_ALLOC( LADSPA_Data, Engine::mixer()->framesPerPeriod() );
				}
			}
			else
			{
				p->buffer = MM_ALLOC( LADSPA_Data, 1 );

				if( manager->isPortInput( m_key, port ) )
				{
					p->rate = CONTROL_RATE_INPUT;
				}
				else
				{
					p->rate = CONTROL_RATE_OUTPUT;
				}
			}

			p->scale = 1.0f;
			if( manager->isEnum( m_key, port ) )
			{
				p->data_type = ENUM;
			}
			else if( manager->isPortToggled( m_key, port ) )
			{
				p->data_type = TOGGLED;
			}
			else if( manager->isInteger( m_key, port ) )
			{
				p->data_type = INTEGER;
			}
			else if( p->name.toUpper().contains( "(SECONDS)" ) )
			{
				p->data_type = TIME;
				p->scale = 1000.0f;
				int loc = p->name.toUpper().indexOf(
								"(SECONDS)" );
				p->name.replace( loc, 9, "(ms)" );
			}
			else if( p->name.toUpper().contains( "(S)" ) )
			{
				p->data_type = TIME;
				p->scale = 1000.0f;
				int loc = p->name.toUpper().indexOf( "(S)" );
				p->name.replace( loc, 3, "(ms)" );
			}
			else if( p->name.toUpper().contains( "(MS)" ) )
			{
				p->data_type = TIME;
				int loc = p->name.toUpper().indexOf( "(MS)" );
				p->name.replace( loc, 4, "(ms)" );
			}
			else
			{
				p->data_type = FLOATING;
			}

			// Get the range and default values.
			p->max = manager->getUpperBound( m_key, port );
			if( p->max == NOHINT )
			{
				p->max = p->name.toUpper() == "GAIN" ? 10.0f :
					1.0f;
			}

			if( manager->areHintsSampleRateDependent(
								m_key, port ) )
			{
				p->max *= m_maxSampleRate;
			}

			p->min = manager->getLowerBound( m_key, port );
			if( p->min == NOHINT )
			{
				p->min = 0.0f;
			}

			if( manager->areHintsSampleRateDependent(
								m_key, port ) )
			{
				p->min *= m_maxSampleRate;
			}

			p->def = manager->getDefaultSetting( m_key, port );
			if( p->def == NOHINT )
			{
				if( p->data_type != TOGGLED )
				{
					p->def = ( p->min + p->max ) / 2.0f;
				}
				else
				{
					p->def = 1.0f;
				}
			}
			else if( manager->areHintsSampleRateDependent( m_key, port ) )
			{
				p->def *= m_maxSampleRate;
			}

			p->max *= p->scale;
			p->min *= p->scale;
			p->def *= p->scale;

			p->value = p->def;

			p->suggests_logscale = manager->isLogarithmic( m_key, port );

			ports.append( p );

	// For convenience, keep a separate list of the ports that are used
	// to control the processors.
			if( p->rate == AUDIO_RATE_INPUT ||
					p->rate == CONTROL_RATE_INPUT )
			{
				p->control_id = m_portControls.count();
				m_portControls.append( p );
			}
		}
		m_ports.append( ports );
	}

	// Instantiate the processing units.
	m_descriptor = manager->getDescriptor( m_key );
	if( m_descriptor == NULL )
	{
		QMessageBox::warning( 0, "Effect",
			"Can't get LADSPA descriptor function: " + m_key.second,
			QMessageBox::Ok, QMessageBox::NoButton );
		setOkay( false );
		return;
	}
	if( m_descriptor->run == NULL )
	{
		QMessageBox::warning( 0, "Effect",
			"Plugin has no processor: " + m_key.second,
			QMessageBox::Ok, QMessageBox::NoButton );
		setDontRun( true );
	}
	for( ch_cnt_t proc = 0; proc < processorCount(); proc++ )
	{
		LADSPA_Handle effect = manager->instantiate( m_key,
							m_maxSampleRate );
		if( effect == NULL )
		{
			QMessageBox::warning( 0, "Effect",
				"Can't get LADSPA instance: " + m_key.second,
				QMessageBox::Ok, QMessageBox::NoButton );
			setOkay( false );
			return;
		}
		m_handles.append( effect );
	}

	// Connect the ports.
	for( ch_cnt_t proc = 0; proc < processorCount(); proc++ )
	{
		for( int port = 0; port < m_portCount; port++ )
		{
			port_desc_t * pp = m_ports.at( proc ).at( port );
			if( !manager->connectPort( m_key,
						m_handles[proc],
						port,
						pp->buffer ) )
			{
				QMessageBox::warning( 0, "Effect",
				"Failed to connect port: " + m_key.second,
				QMessageBox::Ok, QMessageBox::NoButton );
				setDontRun( true );
				return;
			}
		}
	}

	// Activate the processing units.
	for( ch_cnt_t proc = 0; proc < processorCount(); proc++ )
	{
		manager->activate( m_key, m_handles[proc] );
	}
	m_controls = new LadspaControls( this );
}

void LadspaEffect::pluginDestruction()
{
	if( !isOkay() )
	{
		return;
	}

	delete m_controls;

	for( ch_cnt_t proc = 0; proc < processorCount(); proc++ )
	{
		Ladspa2LMMS * manager = Engine::getLADSPAManager();
		manager->deactivate( m_key, m_handles[proc] );
		manager->cleanup( m_key, m_handles[proc] );
		for( int port = 0; port < m_portCount; port++ )
		{
			port_desc_t * pp = m_ports.at( proc ).at( port );
			if( m_inPlaceBroken || pp->rate != CHANNEL_OUT )
			{
				if( pp->buffer) MM_FREE( pp->buffer );
			}
			delete pp;
		}
		m_ports[proc].clear();
	}
	m_ports.clear();
	m_handles.clear();
	m_portControls.clear();
}

static QMap<QString, sample_rate_t> __buggy_plugins;

sample_rate_t LadspaEffect::maxSamplerate( const QString & _name )
{
	if( __buggy_plugins.isEmpty() )
	{
		__buggy_plugins["C* AmpVTS"] = 88200;
		__buggy_plugins["Chorus2"] = 44100;
		__buggy_plugins["Notch Filter"] = 96000;
		__buggy_plugins["TAP Reflector"] = 192000;
	}
	if( __buggy_plugins.contains( _name ) )
	{
		return( __buggy_plugins[_name] );
	}
	return( Engine::mixer()->processingSampleRate() );
}

extern "C"
{

// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main( Model * _parent, void * _data )
{
	return new LadspaEffect( _parent,
		static_cast<const Plugin::Descriptor::SubPluginFeatures::Key *>(
								_data ) );
}

}


code:
/*
 * LadspaEffect.h - class for handling LADSPA effect plugins
 *
 * Copyright (c) 2006-2008 Danny McRae <khjklujn/at/users.sourceforge.net>
 * Copyright (c) 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.
 *
 */

#ifndef _LADSPA_EFFECT_H
#define _LADSPA_EFFECT_H

#include <QMutex>

#include "Effect.h"
#include "LadspaBase.h"
#include "LadspaControls.h"

typedef QVector<port_desc_t *> multi_proc_t;

class LadspaEffect : public Effect
{
	Q_OBJECT
public:
	LadspaEffect( Model * _parent,
			const Descriptor::SubPluginFeatures::Key * _key );
	virtual ~LadspaEffect();

	virtual bool processAudioBuffer( sampleFrame * _buf,
							const fpp_t _frames );

	void setControl( int _control, LADSPA_Data _data );

	virtual EffectControls * controls()
	{
		return m_controls;
	}

	inline const multi_proc_t & getPortControls()
	{
		return m_portControls;
	}

private slots:
	void changeSampleRate();

private:
	void pluginInstantiation();
	void pluginDestruction();

	static sample_rate_t maxSamplerate( const QString & _name );

	QMutex m_pluginMutex;
	LadspaControls * m_controls;

	sample_rate_t m_maxSampleRate;
	ladspa_key_t m_key;
	int m_portCount;
	bool m_inPlaceBroken;

	const LADSPA_Descriptor * m_descriptor;
	QVector<LADSPA_Handle> m_handles;

	QVector<multi_proc_t> m_ports;
	multi_proc_t m_portControls;

} ;

#endif


code:
/*
 * LadspaSubPluginFeatures.cpp - derivation from
 *                               Plugin::Descriptor::SubPluginFeatures for
 *                               hosting LADSPA-plugins
 *
 * Copyright (c) 2006-2007 Danny McRae <khjklujn/at/users.sourceforge.net>
 * 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 <QHBoxLayout>
#include <QLabel>

#include "LadspaSubPluginFeatures.h"
#include "AudioDevice.h"
#include "Engine.h"
#include "Ladspa2LMMS.h"
#include "LadspaBase.h"
#include "Mixer.h"

LadspaSubPluginFeatures::LadspaSubPluginFeatures( Plugin::PluginTypes _type ) :
	SubPluginFeatures( _type )
{
}

QString LadspaSubPluginFeatures::displayName(const Plugin::Descriptor::SubPluginFeatures::Key &k) const
{
	const ladspa_key_t & lkey = subPluginKeyToLadspaKey(&k);
	Ladspa2LMMS * lm = Engine::getLADSPAManager();
	return lm->getName(lkey);
}

void LadspaSubPluginFeatures::fillDescriptionWidget( QWidget * _parent,
													const Key * _key  ) const
{
	const ladspa_key_t & lkey = subPluginKeyToLadspaKey( _key );
	Ladspa2LMMS * lm = Engine::getLADSPAManager();

	QLabel * label = new QLabel( _parent );
	label->setText( QWidget::tr( "Name: " ) + lm->getName( lkey ) );

	QLabel* fileInfo = new QLabel( _parent );
	fileInfo->setText( QWidget::tr( "File: %1" ).arg( lkey.first ) );

	QWidget * maker = new QWidget( _parent );
	QHBoxLayout * l = new QHBoxLayout( maker );
	l->setMargin( 0 );
	l->setSpacing( 0 );

	QLabel * maker_label = new QLabel( maker );
	maker_label->setText( QWidget::tr( "Maker: " ) );
	maker_label->setAlignment( Qt::AlignTop );
	QLabel * maker_content = new QLabel( maker );
	maker_content->setText( lm->getMaker( lkey ) );
	maker_content->setWordWrap( true );
	l->addWidget( maker_label );
	l->addWidget( maker_content, 1 );

	QWidget * copyright = new QWidget( _parent );
	l = new QHBoxLayout( copyright );
	l->setMargin( 0 );
	l->setSpacing( 0 );

	copyright->setMinimumWidth( _parent->minimumWidth() );
	QLabel * copyright_label = new QLabel( copyright );
	copyright_label->setText( QWidget::tr( "Copyright: " ) );
	copyright_label->setAlignment( Qt::AlignTop );

	QLabel * copyright_content = new QLabel( copyright );
	copyright_content->setText( lm->getCopyright( lkey ) );
	copyright_content->setWordWrap( true );
	l->addWidget( copyright_label );
	l->addWidget( copyright_content, 1 );

	QLabel * requiresRealTime = new QLabel( _parent );
	requiresRealTime->setText( QWidget::tr( "Requires Real Time: " ) +
					( lm->hasRealTimeDependency( lkey ) ?
							QWidget::tr( "Yes" ) :
							QWidget::tr( "No" ) ) );

	QLabel * realTimeCapable = new QLabel( _parent );
	realTimeCapable->setText( QWidget::tr( "Real Time Capable: " ) +
					( lm->isRealTimeCapable( lkey ) ?
							QWidget::tr( "Yes" ) :
							QWidget::tr( "No" ) ) );

	QLabel * inplaceBroken = new QLabel( _parent );
	inplaceBroken->setText( QWidget::tr( "In Place Broken: " ) +
					( lm->isInplaceBroken( lkey ) ?
							QWidget::tr( "Yes" ) :
							QWidget::tr( "No" ) ) );

	QLabel * channelsIn = new QLabel( _parent );
	channelsIn->setText( QWidget::tr( "Channels In: " ) +
		QString::number( lm->getDescription( lkey )->inputChannels ) );

	QLabel * channelsOut = new QLabel( _parent );
	channelsOut->setText( QWidget::tr( "Channels Out: " ) +
		QString::number( lm->getDescription( lkey )->outputChannels ) );
}

void LadspaSubPluginFeatures::listSubPluginKeys(
						const Plugin::Descriptor * _desc, KeyList & _kl ) const
{
	Ladspa2LMMS * lm = Engine::getLADSPAManager();

	l_sortable_plugin_t plugins;
	switch( m_type )
	{
		case Plugin::Instrument:
			plugins = lm->getInstruments();
			break;
		case Plugin::Effect:
			plugins = lm->getValidEffects();
			//plugins += lm->getInvalidEffects();
			break;
		case Plugin::Tool:
			plugins = lm->getAnalysisTools();
			break;
		case Plugin::Other:
			plugins = lm->getOthers();
			break;
		default:
			break;
	}

	for( l_sortable_plugin_t::const_iterator it = plugins.begin();
						it != plugins.end(); ++it )
	{
		if( lm->getDescription( ( *it ).second )->inputChannels <=
				  Engine::mixer()->audioDev()->channels() )
		{
			_kl.push_back( ladspaKeyToSubPluginKey( _desc, ( *it ).first, ( *it ).second ) );
		}
	}
}

ladspa_key_t LadspaSubPluginFeatures::subPluginKeyToLadspaKey(
							const Key * _key )
{
	QString file = _key->attributes["file"];
	return( ladspa_key_t( file.remove( QRegExp( "\\.so$" ) ).
				remove( QRegExp( "\\.dll$" ) ) +
#ifdef LMMS_BUILD_WIN32
						".dll"
#else
						".so"
#endif
					, _key->attributes["plugin"] ) );
}


code:
/*
 * LadspaSubPluginFeatures.h - derivation from
 *                             Plugin::Descriptor::SubPluginFeatures for
 *                             hosting LADSPA-plugins
 *
 * Copyright (c) 2006-2007 Danny McRae <khjklujn/at/users.sourceforge.net>
 * Copyright (c) 2006-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.
 *
 */

#ifndef LADSPA_SUBPLUGIN_FEATURES_H
#define LADSPA_SUBPLUGIN_FEATURES_H

#include "LadspaManager.h"
#include "Plugin.h"

class LadspaSubPluginFeatures : public Plugin::Descriptor::SubPluginFeatures
{
public:
	LadspaSubPluginFeatures( Plugin::PluginTypes _type );

	QString displayName(const Key& k) const override;
	void fillDescriptionWidget( QWidget * _parent,
												const Key * _key ) const override;

	virtual void listSubPluginKeys( const Plugin::Descriptor * _desc,
												KeyList & _kl ) const override;

	static ladspa_key_t subPluginKeyToLadspaKey( const Key * _key );

} ;

#endif


code:
# Note:
# 	The last version of Calf that was LADSPA-capable is version 0.0.18.2

# Parse version info from autoconf
FILE(READ veal/configure.ac VERSION_FILE)
STRING(REPLACE "[" ";" VERSION_FILE ${VERSION_FILE} )
STRING(REPLACE "]" ";" VERSION_FILE ${VERSION_FILE} )
LIST(GET VERSION_FILE 2 VERSION)
CONFIGURE_FILE(config.h.in config.h)

FILE(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/veal/src/*.cpp")
LIST(SORT SOURCES)

# Skip files matching pattern
SET(FILE_PATTERNS "ctl;gui;gtk;session;connector;jack;rdf;draw;fluid;preset;lv2;benchmark;win;plugin.cpp")
FOREACH(_item ${SOURCES})
	GET_FILENAME_COMPONENT(m_basename ${_item} NAME)
	FOREACH(_pattern ${FILE_PATTERNS})
		IF(${m_basename} MATCHES ${_pattern})
			LIST(REMOVE_ITEM SOURCES ${_item})
		ENDIF()
	ENDFOREACH()
ENDFOREACH()

ADD_LIBRARY(veal MODULE ${SOURCES})
INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include"
                    "${CMAKE_BINARY_DIR}"
                    "${CMAKE_CURRENT_BINARY_DIR}"
                    "${CMAKE_CURRENT_SOURCE_DIR}/veal/src")

INSTALL(TARGETS veal LIBRARY DESTINATION "${PLUGIN_DIR}/ladspa")
SET_TARGET_PROPERTIES(veal PROPERTIES PREFIX "")

# Disable OSC messaging, it's not mingw compatible
TARGET_COMPILE_DEFINITIONS(veal PRIVATE DISABLE_OSC=1)

SET(INLINE_FLAGS "")
IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
	SET(INLINE_FLAGS "-finline-functions-called-once -finline-limit=80")
ENDIF()
SET_TARGET_PROPERTIES(veal PROPERTIES COMPILE_FLAGS "-fexceptions -O2 -finline-functions ${INLINE_FLAGS}")

# Don't strip if "Debug" or "RelWithDebInfo"
IF(LMMS_BUILD_WIN32 AND NOT CMAKE_BUILD_TYPE MATCHES "Deb")
	ADD_CUSTOM_COMMAND(TARGET veal POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:veal>")
ENDIF()
IF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD)
	SET_TARGET_PROPERTIES(veal PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined")
ENDIF()


code:
#define VERSION "${VERSION}"
#define PACKAGE_NAME "veal"
#define USE_LADSPA 1
#define PKGLIBDIR ""

// Namespace change to avoid conflict with LV2
#define calf_plugins veal_plugins


code:
/*
	Amp.cc

	Copyright 2003-7
		Tim Goetze <tim@quitte.de>
		David Yeh <dtyeh@ccrma.stanford.edu> (Tone Stack in TS models)

	http://quitte.de/dsp/

	Tube amplifier models

*/
/*
	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; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA or point your web browser to http://www.gnu.org.
*/

#include "basics.h"
#include <stdio.h>

#include "Amp.h"
#include "Descriptor.h"

void
AmpStub::init (bool adjust_downsampler)
{
	dc_blocker.set_f (10. / fs);

	/* going a bit lower than nominal with fc */
	double f = .7 * M_PI / OVERSAMPLE;

	/* construct the upsampler filter kernel */
	DSP::sinc (f, up.c, FIR_SIZE);
	DSP::kaiser<DSP::apply_window> (up.c, FIR_SIZE, 6.4);

	/* copy upsampler filter kernel for downsampler, make sum */
	double s = 0;
	for (int i = 0; i < up.n; ++i)
		down.c[i] = up.c[i],
		s += up.c[i];

	s = 1 / s;

	/* scale downsampler kernel for unity gain + correction for transfer */
	double t = adjust_downsampler ?
		s / max (fabs (tube.clip[0].value), fabs (tube.clip[1].value)) : s;

	for (int i = 0; i < down.n; ++i)
		down.c[i] *= t;

	/* scale upsampler kernel for unity gain */
	s *= OVERSAMPLE;
	for (int i = 0; i < up.n; ++i)
		up.c[i] *= s;
}

/* //////////////////////////////////////////////////////////////////////// */

void
AmpIII::init()
{
	this->AmpStub::init (false);

	/* need to filter out dc before the power amp stage, which is running at
	 * the oversampled rate */
	dc_blocker.set_f (10. / (fs * OVERSAMPLE));

	DSP::RBJ::LoShelve (200 / fs, .2, -3, filter.a, filter.b);
}

template <sample_func_t F, int OVERSAMPLE>
void
AmpIII::one_cycle (int frames)
{
	sample_t * s = ports[0];

	sample_t gain = getport(1);
	sample_t temp = getport(2) * tube.scale;

	drive = getport(3) * .5;
	i_drive = 1 / (1 - drive);

	sample_t * d = ports[4];

	*ports[5] = OVERSAMPLE;

	double g = current.g;

	current.g = max (gain < 1 ? gain : exp2 (gain - 1), .000001);
	current.g *= tube.scale / fabs (tube.transfer (temp));

	/* recursive fade to prevent zipper noise from the 'gain' knob */
	if (g == 0) g = current.g;

	double one_over_n = frames > 0 ? 1. / frames : 1;
	double gf = pow (current.g / g, one_over_n);

	for (int i = 0; i < frames; ++i)
	{
		register sample_t a = s[i];

		a = g * tube.transfer (a * temp);
		a = filter.process (a + normal);

		a = tube.transfer_clip (up.upsample (a));
		a = power_transfer (dc_blocker.process (a));

		a = down.process (a);

		for (int o = 1; o < OVERSAMPLE; ++o)
			down.store (
					power_transfer (
						dc_blocker.process (
							normal + tube.transfer_clip (up.pad (o)))));

		F (d, i, a, adding_gain);

		g *= gf;
	}

	current.g = g;
}

/* //////////////////////////////////////////////////////////////////////// */

PortInfo
AmpIII::port_info [] =
{
	{
		"in",
		INPUT | AUDIO,
		{BOUNDED, -1, 1}
	}, {
		"gain",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_1, 0, 10}
	}, {
		"temperature",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_MID, 0.005, 1}
	}, {
		"drive",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_MAX, 0.0001, 1} /* ^2 gives the nice drive */
	}, {
		"out",
		OUTPUT | AUDIO,
		{0}
	}, {
		"latency",
		OUTPUT | CONTROL,
		{0}
	}
};

template <> void
Descriptor<AmpIII>::setup()
{
	UniqueID = 1786;
	Label = "AmpIII";
	Properties = HARD_RT;

	Name = CAPS "AmpIII - Tube amp";
	Maker = "Tim Goetze <tim@quitte.de>";
	Copyright = "GPL, 2002-7";

	/* fill port info and vtable */
	autogen();
}

/* //////////////////////////////////////////////////////////////////////// */

void
AmpIV::init()
{
	this->AmpStub::init (false);

	/* need to filter out dc before the power amp stage, which is running at
	 * the oversampled rate */
	dc_blocker.set_f (10. / (fs * OVERSAMPLE));

	tone.init (fs);
}

template <sample_func_t F, int OVERSAMPLE>
void
AmpIV::one_cycle (int frames)
{
	double one_over_n = frames > 0 ? 1. / frames : 1;

	sample_t * s = ports[0];

	sample_t gain = getport(1);
	sample_t temp = getport(2) * tube.scale;

	tone.start_cycle (ports + 3, one_over_n);

	drive = getport(7) * .5;
	i_drive = 1 / (1 - drive);

	sample_t * d = ports[8];

	*ports[9] = OVERSAMPLE;

	double g = current.g;

	current.g = max (gain < 1 ? gain : exp2 (gain - 1), .000001);
	current.g *= tube.scale / fabs (tube.transfer (temp));

	/* recursive fade to prevent zipper noise from the 'gain' knob */
	if (g == 0) g = current.g;

	double gf = pow (current.g / g, one_over_n);

	for (int i = 0; i < frames; ++i)
	{
		register sample_t a = s[i] + normal;

		a = g * tube.transfer (a * temp);
		a = tone.process (a);

		a = tube.transfer_clip (up.upsample (a));
		a = power_transfer (dc_blocker.process (a));

		a = down.process (a);

		for (int o = 1; o < OVERSAMPLE; ++o)
			down.store (
					power_transfer (
						dc_blocker.process (
							normal + tube.transfer_clip (up.pad (o)))));

		F (d, i, a, adding_gain);

		g *= gf;
	}

	current.g = g;
}

/* //////////////////////////////////////////////////////////////////////// */

PortInfo
AmpIV::port_info [] =
{
	{
		"in",
		INPUT | AUDIO,
		{BOUNDED, -1, 1}
	}, {
		"gain",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_1, 0, 10}
	}, {
		"temperature",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_MID, 0.005, 1}
	}, {
		"bass",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_0, -20, 20}
	}, {
		"mid",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_0, -20, 20}
	}, {
		"treble",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_0, -20, 20}
	}, {
		"hi",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_0, -20, 20}
	}, {
		"drive",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_MAX, 0.0001, 1} /* ^2 gives the nice drive */
	}, {
		"out",
		OUTPUT | AUDIO,
		{0}
	}, {
		"latency",
		OUTPUT | CONTROL,
		{0}
	}
};

template <> void
Descriptor<AmpIV>::setup()
{
	UniqueID = 1794;
	Label = "AmpIV";
	Properties = HARD_RT;

	Name = CAPS "AmpIV - Tube amp + tone controls";
	Maker = "Tim Goetze <tim@quitte.de>";
	Copyright = "GPL, 2002-7";

	/* fill port info and vtable */
	autogen();
}

/* //////////////////////////////////////////////////////////////////////// */

void
AmpV::init()
{
	this->AmpStub::init (false);

	/* need to filter out dc before the power amp stage, which is running at
	 * the oversampled rate */
	dc_blocker.set_f (10. / (fs * OVERSAMPLE));

	DSP::RBJ::LoShelve (210. / fs, .2, -1, filter[0].a, filter[0].b);
	DSP::RBJ::LoShelve (4200. / fs, 1.2, +6, filter[1].a, filter[1].b);
	DSP::RBJ::LoShelve (420. / fs, .2, +2, filter[2].a, filter[2].b);

	/* power supply cap */
	for (int i = 0; i < 2; ++i)
		DSP::RBJ::LP (10. / fs, .3, power_cap[i].a, power_cap[i].b);
}

template <sample_func_t F, int OVERSAMPLE>
void
AmpV::one_cycle (int frames)
{
	sample_t * s = ports[0];

	sample_t gain = getport(1);

	if (*ports[2] != cut)
	{
		cut = getport(2);
		DSP::RBJ::LoShelve (210. / fs, .2, cut, filter[0].a, filter[0].b);
	}
	if (*ports[3] != tone)
	{
		tone = getport(3);
		double f = tone * tone * 8400 + 420;
		double q = tone * .4 + .2;
		double db = tone * 2 + 2;
		DSP::RBJ::LoShelve (f / fs, q, db, filter[2].a, filter[2].b);
	}

	drive = getport(4) * .5;
	i_drive = 1 / (1 - drive);

	#define MAX_WATTS port_info[5].range.UpperBound
	sample_t sag = (MAX_WATTS - getport(5)) / MAX_WATTS;
	sag = .6 * sag * sag;

	sample_t * d = ports[6];

	*ports[7] = OVERSAMPLE;

	double g = current.g;

	current.g = max (gain < 1 ? gain : pow (20, gain - 1), .000001);
	#if 0
	if ( (++_turn & 127) == 0)
		fprintf (stderr, "supply = %.3f sag = %.3f\n", supply, sag);
	#endif

	if (g == 0) g = current.g;

	/* recursive fade to prevent zipper noise from the 'gain' knob */
	double one_over_n = frames > 0 ? 1. / frames : 1;
	double gf = pow (current.g / g, one_over_n);

	for (int i = 0; i < frames; ++i)
	{
		register sample_t a = s[i];
		register sample_t v = 3 - supply;
		/* alternative curve: v = v * v * .1 + .1; */
		v = v * v * .06 + .46;

		a = filter[0].process (a + normal);
		if (0)
			a = filter[2].process (a);

		a = g * (a + supply * .001);

		a = v * tube.transfer_clip (up.upsample (a));
		a = power_transfer (dc_blocker.process (a));

		a = down.process (a);

		a = filter[1].process (a - normal);
		if (1)
			a = filter[2].process (a + normal);

		{
			for (int o = 1; o < OVERSAMPLE; ++o)
				down.store (
						power_transfer (
							dc_blocker.process (
								normal + tube.transfer_clip (
									up.pad (o)))));
		}

		F (d, i, a, adding_gain);

		/* integrate for an approximation of cumulative output power */
		supply += sag * fabs (a) + normal;
		/* filter integrated power consumption */
		for (int j = 0; j < 2; ++j)
			supply = 0.9 * (power_cap[j].process (supply));

		g *= gf;
		normal = -normal;
	}

	current.g = g;
}

/* //////////////////////////////////////////////////////////////////////// */

PortInfo
AmpV::port_info [] =
{
	{
		"in",
		INPUT | AUDIO,
		{BOUNDED, -1, 1}
	}, {
		"gain",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_1, 0, 3}
	}, {
		"bass",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_0, -9, 9}
	}, {
		"tone",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_MIN, 0, 1}
	}, {
		"drive",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_HIGH, 0.0001, 1} /* ^2 gives the nice drive */
	}, {
		"watts",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_MID, 5, 150}
	}, {
		"out",
		OUTPUT | AUDIO,
		{0}
	}, {
		"latency",
		OUTPUT | CONTROL,
		{0}
	}
};

template <> void
Descriptor<AmpV>::setup()
{
	UniqueID = 2587;
	Label = "AmpV";
	Properties = HARD_RT;

	Name = CAPS "AmpV - Tube amp";
	Maker = "Tim Goetze <tim@quitte.de>";
	Copyright = "GPL, 2002-7";

	/* fill port info and vtable */
	autogen();
}

/* //////////////////////////////////////////////////////////////////////// */

void
AmpVTS::init()
{
	this->AmpStub::init (false);

	/* need to filter out dc before the power amp stage, which is running at
	 * the oversampled rate */
	dc_blocker.set_f (10. / (fs * OVERSAMPLE));

	/* power supply capacitance */
	for (int i = 0; i < 2; ++i)
		DSP::RBJ::LP (10. / fs, .3, power_cap[i].a, power_cap[i].b);

	tonestack.init (fs);
}

template <sample_func_t F, int OVERSAMPLE>
void
AmpVTS::one_cycle (int frames)
{
	sample_t * s = ports[0];

	tonestack.start_cycle (ports + 1, 2);
	sample_t gain = getport(2);

	drive = getport(6) * .5;
	i_drive = 1 / (1 - drive);

	sample_t sag = 1 - max (0.0001, min (1, getport(7)));
	sag = .6 * sag * sag; /* map to log space makes slider better */

	sample_t * d = ports[8];

	*ports[9] = OVERSAMPLE;

	double g = current.g;

	if (gain < 1)
		current.g = max (gain, .001);
	else
	{
		gain -= 1;
		gain *= gain;
		current.g = pow (10, gain);
	}

	/* recursive fade to prevent zipper noise from the 'gain' knob */
	double one_over_n = frames > 0 ? 1. / frames : 1;
	double gf = pow (current.g / g, one_over_n);

	for (int i = 0; i < frames; ++i)
	{
		register double a = s[i];
		register double v = 3 - supply;
		v = v * v * .06 + .46;

		a = tube.transfer (a);
		a = tonestack.process (a + normal);

		a = g * (a + supply * .001);

		a = v * tube.transfer_clip (up.upsample (a));
		a = power_transfer (dc_blocker.process (a));

		a = down.process (a);

		{
			for (int o = 1; o < OVERSAMPLE; ++o)
				down.store (
						power_transfer (
							dc_blocker.process (
								normal + tube.transfer_clip (
									up.pad (o)))));
		}

		F (d, i, a, adding_gain);

		/* integrate for an approximation of cumulative output power */
		supply += sag * fabs (a) + normal;
		/* filter integrated power consumption */
		for (int j = 0; j < 2; ++j)
			supply = 0.9 * (power_cap[j].process (supply + normal));

		g *= gf;
		normal = -normal;
	}

	current.g = g;
}

/* //////////////////////////////////////////////////////////////////////// */

PortInfo
AmpVTS::port_info [] =
{
	{
		"in",
		INPUT | AUDIO,
		{BOUNDED, -1, 1}
	}, {
		"model",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_0 | INTEGER, 0, 5} /* no way to set dyn at compile t */
	}, {
		"gain",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_HIGH, 0, 3}
	}, {
		"bass",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_MID, 0, 1}
	}, {
		"mid",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_1, 0, 1}
	}, {
		"treble",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_HIGH, 0, 1}
	}, {
		"drive",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_LOW, 0.0001, 1}
	}, {
		"watts",
		INPUT | CONTROL,
		{BOUNDED | DEFAULT_HIGH, 0.0001, 1}
	}, {
		"out",
		OUTPUT | AUDIO,
		{0}
	}, {
		"latency",
		OUTPUT | CONTROL,
		{0}
	}
};

template <> void
Descriptor<AmpVTS>::setup()
{
	UniqueID = 2592;
	Label = "AmpVTS";
	Properties = HARD_RT;

	Name = CAPS "AmpVTS - Tube amp + Tone stack";
	Maker = "David Yeh <dtyeh@ccrma.stanford.edu> & Tim Goetze <tim@quitte.de>";
	Copyright = "GPL, 2002-7";

	/* fill port info and vtable */
	autogen();
}


code:
/*
	Amp.h

	Copyright 2002-9 Tim Goetze <tim@quitte.de>

	http://quitte.de/dsp/

	Oversampled tube amplifier emulation.

*/
/*
	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; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA or point your web browser to http://www.gnu.org.
*/

#ifndef _AMP_H_
#define _AMP_H_

#include "dsp/util.h"
#include "dsp/OnePole.h"
#include "dsp/BiQuad.h"
#include "dsp/TwelveAX7.h"
#include "dsp/Roessler.h"

#include "dsp/FIR.h"
#include "dsp/sinc.h"
#include "dsp/windows.h"

#include "dsp/RBJ.h"
#include "dsp/Eq.h"

#include "dsp/ToneStack.h"

class AmpStub
: public Plugin
{
	public:
		DSP::TwelveAX7_3 tube;

		sample_t drive, i_drive;

		struct {
			/* gain (remember current setting and fade to port setting in run) */
			double g;
			/* should also do this for temperature to remove another potential
			 * source of zippering, but that would be overkill, at the cost of
			 * at least one pow() per block. */
		} current;

		/* input is hipass-filtered first */
		DSP::OnePoleHP dc_blocker;

		enum {
			OVERSAMPLE = 8,
			FIR_SIZE = 64,
		};

		/* antialias filters */
		DSP::FIRUpsampler up;
		DSP::FIR down;

		AmpStub()
			: up (FIR_SIZE, OVERSAMPLE),
				down (FIR_SIZE, up.c)
			{ }

		void init (bool adjust_downsampler = false);

		inline sample_t power_transfer (sample_t a)
			{
				return i_drive * (a - drive * fabs (a) * a);
			}
};

/* /////////////////////////////////////////////////////////////////////// */

class PreampIII
: public AmpStub
{
	public:
		template <sample_func_t F, int OVERSAMPLE>
			void one_cycle (int frames);

		DSP::BiQuad filter;

	public:
		static PortInfo port_info[];

		sample_t adding_gain;

		void init();
		void activate()
			{
				current.g = 1;

				filter.reset();
				up.reset();
				down.reset();
				dc_blocker.reset();
			}

		void run (int n)
			{
				one_cycle<store_func, OVERSAMPLE> (n);
			}

		void run_adding (int n)
			{
				one_cycle<adding_func, OVERSAMPLE> (n);
			}
};

/* /////////////////////////////////////////////////////////////////////// */

class AmpIII
: public AmpStub
{
	public:
		template <sample_func_t F, int OVERSAMPLE>
			void one_cycle (int frames);

		DSP::BiQuad filter;

	public:
		static PortInfo port_info[];

		sample_t adding_gain;

		void init();
		void activate()
			{
				current.g = 1;

				up.reset();
				down.reset();
				dc_blocker.reset();
				filter.reset();
			}

		void run (int n)
			{
				one_cycle<store_func, OVERSAMPLE> (n);
			}

		void run_adding (int n)
			{
				one_cycle<adding_func, OVERSAMPLE> (n);
			}
};

/* /////////////////////////////////////////////////////////////////////// */

typedef struct
	{float center, Q, adjust;}
PreampBand;

class ToneControls
{
	public:
		sample_t eq_gain[4];
		DSP::Eq<4> eq;
		static PreampBand bands[4];

	public:
		void init (double _fs);
		void activate (sample_t **);

		inline void
		start_cycle (sample_t ** ports, double one_over_n)
			{
				for (int i = 0; i < 4; ++i)
				{
					if (*ports[i] == eq_gain[i])
					{
						eq.gf[i] = 1;
						continue;
					}

					eq_gain[i] = *ports [i];

					double want = get_band_gain (i, eq_gain[i]);
					eq.gf[i] = pow (want / eq.gain[i], one_over_n);
				}
			}

		double get_band_gain (int i, double g);
		void set_band_gain (int i, float g);

		inline sample_t process (sample_t x)
			{
				return eq.process (x);
			}
};

/* /////////////////////////////////////////////////////////////////////// */

class PreampIV
: public PreampIII
{
	public:
		ToneControls tone;

		template <sample_func_t F, int OVERSAMPLE>
			void one_cycle (int frames);

	public:
		static PortInfo port_info[];

		sample_t adding_gain;

		void init();
		void activate();

		void run (int n)
			{
				one_cycle<store_func, OVERSAMPLE> (n);
			}

		void run_adding (int n)
			{
				one_cycle<adding_func, OVERSAMPLE> (n);
			}
};

/* /////////////////////////////////////////////////////////////////////// */

class AmpIV
: public AmpStub
{
	public:
		ToneControls tone;

		template <sample_func_t F, int OVERSAMPLE>
			void one_cycle (int frames);

	public:
		static PortInfo port_info[];

		sample_t adding_gain;

		void init();
		void activate()
			{
				current.g = 1;

				tone.activate (ports + 3);

				up.reset();
				down.reset();
				dc_blocker.reset();
			}

		void run (int n)
			{
				one_cycle<store_func, OVERSAMPLE> (n);
			}

		void run_adding (int n)
			{
				one_cycle<adding_func, OVERSAMPLE> (n);
			}
};

/* /////////////////////////////////////////////////////////////////////// */

class AmpV
: public AmpStub
{
	public:
		template <sample_func_t F, int OVERSAMPLE>
			void one_cycle (int frames);

		DSP::BiQuad filter[3];

		sample_t cut, tone;

		/* supply voltage sag */
		sample_t supply;
		DSP::BiQuad power_cap[2];

	public:
		static PortInfo port_info[];

		sample_t adding_gain;

		void init();
		void activate()
			{
				current.g = 1;

				for (int i = 0; i < 2; ++i)
					filter[i].reset(),
					power_cap[i].reset();

				up.reset();
				down.reset();
				dc_blocker.reset();

				cut = 2;
				supply = 0.;

				tone = -1; /* causes initialisation of the filter at first cycle */
			}

		void run (int n)
			{
				one_cycle<store_func, OVERSAMPLE> (n);
			}

		void run_adding (int n)
			{
				one_cycle<adding_func, OVERSAMPLE> (n);
			}
};

/* /////////////////////////////////////////////////////////////////////// */

class AmpVTS
: public AmpStub
{
	public:
		DSP::ToneStack tonestack;

		template <sample_func_t F, int OVERSAMPLE>
			void one_cycle (int frames);

		sample_t cut, tone;

		/* supply voltage sag */
		sample_t supply;
		DSP::BiQuad power_cap[2];

	public:
		static PortInfo port_info[];

		sample_t adding_gain;

		void init();
		void activate()
			{
				current.g = 1;

				for (int i = 0; i < 2; ++i)
					power_cap[i].reset();

				up.reset();
				down.reset();
				dc_blocker.reset();

				cut = 2;
				supply = 0.;
			}

		void run (int n)
			{
				one_cycle<store_func, OVERSAMPLE> (n);
			}

		void run_adding (int n)
			{
				one_cycle<adding_func, OVERSAMPLE> (n);
			}
};

#endif /* _AMP_H_ */


code:
0.4.5
	* Narrower plugin added
	* fixed 'configure.py' to work with python3
	* fixed Sin, Roessler and Lorenz gain smoothing on activation

0.4.4

0.4.3
	* basics.h cleanup / comments
	* minor Makefile cleanup
	* comment cosmetics
	* Eq and Eq2x2 per-band Q changed to 1.414 (= 1 octave)
	* Eq lowest band default value fixed to read 0
	* Niclas' fix for the bessel function implemented
	* uninitialised plugin states eliminated thanks to Damon
	* linker options for OSX added to the Makefile

0.4.2
	* fixed the 'model' port index for AmpVTS in the RDF generator

0.4.1
	* cleaned up Eq.h and Eq.cc (many g++ versions choke on the unused code
		there)
	* changed -O3 to -O2 in the g++ invocation

0.4.0
	* ToneStack plugins, by David Yeh
	* AmpV + Tone stack plugin, employing David Yeh's fine work
	* comment cosmetics
	* Amp* denormal protection fixed (or is it, Dave? ;)
	* minor code cleanup in Amp.cc
	* caps.rdf updated with plugin categories (thanks to Paul Winkler)
	* caps.rdf Cabinet* RDF preset labels renamed
	* AutoWah plugin
	* DSP::RMS reworked, may affect Compress plugin
	* DSP::Eq reworked for double precision and denormal protection
	* ./configure.py checks SSE availability
	* in case of SSE math denormal flush to zero activated for all plugins
	* all plugins renamed C* .. instead of CAPS: ..
	* Eq modified to play nice with ardour
	* Eq2x2
	* introduced the Plugin base class, collecting common traits (normal etc)
	* getport() -- read access to control ports which is clamped to port bounds
		and maps inf and nan to 0 as well
	* all LADSPA_HINT_SAMPLE_RATE ports changed to *_LOGARITHMIC because
		of broken implementations (no surprise given the vagueness of ladspa.h
		regarding this matter) -- this means changed default parameters of the
		affected ports, too
	* VCO* "latency" output ports removed
	* actual activate() call is deferred to first run() after activate()
		in order to prevent inadvertent parameter smoothing sweeps during the first
		block	of audio after activation, this should fix all problems with ardour
		(except those caused by denormals or invalid audio input)
	* caps.rdf installed by 'make install'
	* fixed a bug in tools/make-ps.py that caused the spectrum plots to
		be inaccurate for multi-channel plugins

0.3.0
	* TwelveAX7_3 changed to clip slightly early in the upper lobe
	* Scape plugin added
	* plugin names rewritten, prefixed with "CAPS:"
	* new ChorusII, StereoChorusII plugins
	* Chorus, StereoChorus relabeled, appended 'I' suffix
	* new PhaserII plugin (great stuff if I may say so)
	* Phaser relabeled, appended 'I' suffix
	* new AmpV plugin, based on AmpIII, emulates compression and distortion
		modulation through power supply shortcomings, plus lots of fine-tuning
		and an additional biquad. We're getting there!
	* all Preamp and Amp models fitted with a new 12AX7 model, linear
		interpolation of a sample table obtained from spice simulation

0.2.4
	* feedback default reverted to 0 for the Chorus units
	* fixed Cabinet to switch to correct gain at 'model' control change
	* fixed 'model' control in Cabinet to work with a broader range of hosts
	* Cabinet name changed to CabinetI
	* CabinetII plugin: Cabinet with 32nd order IIR filters, more fidelity
	  to the original frequency responses, supplied coefficients for 4 of the
		most used sample rates
	* applied the gcc-4 enabling patch
	* SweepVF renamed to SweepVFI
	* new SweepVFII plugin, variant of SweepVFI with Q modulated by a
		second Lorenz fractal
	* dsp/exp2 dumped in favour of libm's exp2(3)

0.2.3
	* StereoChorus denormal protection made functional
		(Thanks again to S. Savolainen)
	* Phaser denormal protected

0.2.2
	* Build was _not_ fixed for g++-4.0.
	* AmpIV gain control restored to operate as expected
	* Chorus/StereoChorus denormal protection (thanks to S. Savolainen)
	* a few cosmetic changes elsewhere

0.2.1
	* Build fixed for g++-4.0, PPC and AMD64
		(Thanks to Niklas Werner, Andreas Jochens and Mario Lang)
	* Reverb.* cosmetics
	* AmpIV tone controls moved to after initial tube transfer

0.2.0
	* denormal protection for Preamp*, Amp*
	* Capitalized plugin Names
	* PDF now lists audio in- and outputs as well as control inputs, only
	  gives average CPU rating
	* AmpIV: PreampIV + power amp stage
	* Plate2x2: Plate with 2-in, 2-out audio routing
	* Plate damping and bandwidth controls changed to map to filter fc, fixes
		behaviour in hosts that handle the log hint incorrectly

0.1.13
	* AmpIII activate() resets the boost filter

0.1.12
	* PreampIV band controls fixed to operate as expected

0.1.11
	* amps changed back to old tube model :) but new temp & gain behaviour stays
	* SweepVF, AmpIII default value adjustments

0.1.10
	* HRTF recursion runs in doubles
	* Cabinet recursion runs in doubles for much clearer sound
	* all amps fitted with a common tube voltage mapping, dsp/TwelveAX7.h
	* all amps: temperature and gain controls changed slightly
	* all amps declared in one common Amp.h
	* Pan echo fixed to be filtered independent of sample rate
	* Cabinet cosmetics and activate() from port values fix
	* SweepVF fixed to activate() from the current control settings
	* rid all *amp* plugins of the initial hi-pass, not needed anymore
	* PreampIII and AmpIII more authentic with an rbj lo-shelve, +6 dB > 1.2 kHz
		as hinted by circuit analysis
	* something_random() removed, stdlib for random generation

0.1.9
	* Pan plugin
	* 'make depend' instead of 'make dep', uses $(CC) -MM instead of 'makedepend'
	* *Chorus, AmpIII, Plate defaults changed
	* *Chorus optimizations, reintroduces funny zipper noise when 'feedback' is
		non-zero and 't' is changed
	* experimental HRTF plugin
	* Plate 'blend' goes all the way to wet output only
	* dsp/White offers a get_31() method for reduced number of bitshifts needed
	* *Chorus delay line tapping changed to employ cubic interpolation, sounds
		better
	* SweepVF modulation mix algorithm changed to clamp if over-fed, makes
		for wider sweeps

0.1.8
	* all oversampling plugins use Kaiser windows instead of Blackman-Harris,
		for much better performance
	* SweepVF modulation range slightly increased
	* Cabinet filter loop cosmetics (slight speedup)
	* new AmpIII Plugin: Preamp plus power amp emulation
	* lowered NOISE_FLOOR (equals 'renormal' number)

0.1.7
	* connect ports to lower bound on instantiate()
	* Plate delay line lengths raised, sound changed
	* Eq activate() fixed to initialize from the current settings
	* Preamp* cutoff reverted to 0.1.3 setting, thanks to Ben Saylor for
		testing
	* old IIR-based Preamp cleaned from the sources
	* zipper-noise in *Chorus units for t changes with feedback > 0 eliminated
	* all plugin constructor code moved to init() calls

0.1.6
	* SweepVF modulation mix algorithm changed to maintain proportion, not
		absolute value if x + y + z > 1, for better control
	* create $(DEST) directory on make install, pointed out by Daniel James

0.1.5
	* fixed delay line length miscalculation in ModLattice

0.1.4
	* SweepVF modulation source can be mixed now
	* latency port for VCO*
	* Lorenz and Roessler get x, y, z mixing knobs
	* PreampIV eq bands slightly tuned and coefficients moved into common struct
	* Preamp*, VCO* downsampler filter cutoff lowered
	* Clip downsampler filter cutoff lowered
	* nonsensical audio output bounds removed
	* simplified VCO* implementation
	* JVRev rewritten for code clarity (funny enough, it also got quicker)
	* fixed JVRev to reset its history on activate()
	* added purpose, copyright and licensing information to all (i think) files.
	* HACKING file
	* CHANGES file

0.1.3
	* fixed all compilation problems with gcc 3.3, with the patient help
		of the lad mailing list community
	* dsp/Eq.h SSE assembler code had to go (gcc > 3 doesn't like multi-line
		asm, and efficiency and even reliability go down if we allow gcc to
		intersperse its 'optimization' code with our asm)

0.1.2
	* fixed more compilation problems with gcc >= 3.0

0.1.1
	* tried to (but didn't really) fix compilation problem with ladspa.h

0.1.0
	* initial release

path: plugins/LadspaEffect/caps/CHANGES


code:
INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include")
FILE(GLOB SOURCES *.cc)
LIST(SORT SOURCES)
ADD_LIBRARY(caps MODULE ${SOURCES})
INSTALL(TARGETS caps LIBRARY DESTINATION "${PLUGIN_DIR}/ladspa")
IF(LMMS_BUILD_WIN64)
ADD_DEFINITIONS(-DLMMS_BUILD_WIN64)
ENDIF(LMMS_BUILD_WIN64)
SET_TARGET_PROPERTIES(caps PROPERTIES PREFIX "")
SET_TARGET_PROPERTIES(caps PROPERTIES COMPILE_FLAGS "-O2 -funroll-loops -Wno-write-strings")

IF(LMMS_BUILD_WIN32)
	ADD_CUSTOM_COMMAND(TARGET caps POST_BUILD COMMAND "${STRIP}" \"$<TARGET_FILE:caps>\")
ENDIF(LMMS_BUILD_WIN32)
IF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD)
	SET_TARGET_PROPERTIES(caps PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined")
ENDIF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD)

IF(LMMS_BUILD_LINUX)
	SET_TARGET_PROPERTIES(caps PROPERTIES LINK_FLAGS "${LINK_FLAGS}")
ENDIF(LMMS_BUILD_LINUX)