code:
/*
 * Watsyn.cpp - a 4-oscillator modulating wavetable synth
 *
 * 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 <QDomElement>

#include "Watsyn.h"
#include "base64.h"
#include "Engine.h"
#include "InstrumentTrack.h"
#include "ToolTip.h"
#include "Song.h"
#include "lmms_math.h"
#include "Mixer.h"
#include "interpolation.h"

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

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT watsyn_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"Watsyn",
	QT_TRANSLATE_NOOP( "PluginBrowser",
				"4-oscillator modulatable wavetable synth" ),
	"Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>",
	0x0100,
	Plugin::Instrument,
	new PluginPixmapLoader( "logo" ),
	NULL,
	NULL
} ;

}

WatsynObject::WatsynObject( float * _A1wave, float * _A2wave,
					float * _B1wave, float * _B2wave,
					int _amod, int _bmod, const sample_rate_t _samplerate, NotePlayHandle * _nph, fpp_t _frames,
					WatsynInstrument * _w ) :
				m_amod( _amod ),
				m_bmod( _bmod ),
				m_samplerate( _samplerate ),
				m_nph( _nph ),
				m_fpp( _frames ),
				m_parent( _w )
{
	m_abuf = new sampleFrame[_frames];
	m_bbuf = new sampleFrame[_frames];

	m_lphase[A1_OSC] = 0.0f;
	m_lphase[A2_OSC] = 0.0f;
	m_lphase[B1_OSC] = 0.0f;
	m_lphase[B2_OSC] = 0.0f;

	m_rphase[A1_OSC] = 0.0f;
	m_rphase[A2_OSC] = 0.0f;
	m_rphase[B1_OSC] = 0.0f;
	m_rphase[B2_OSC] = 0.0f;

	// copy wavegraphs to the synth object to prevent race conditions

	memcpy( &m_A1wave, _A1wave, sizeof( m_A1wave ) );
	memcpy( &m_A2wave, _A2wave, sizeof( m_A2wave ) );
	memcpy( &m_B1wave, _B1wave, sizeof( m_B1wave ) );
	memcpy( &m_B2wave, _B2wave, sizeof( m_B2wave ) );
}

WatsynObject::~WatsynObject()
{
	delete[] m_abuf;
	delete[] m_bbuf;
}

void WatsynObject::renderOutput( fpp_t _frames )
{
	if( m_abuf == NULL )
		m_abuf = new sampleFrame[m_fpp];
	if( m_bbuf == NULL )
		m_bbuf = new sampleFrame[m_fpp];

	for( fpp_t frame = 0; frame < _frames; frame++ )
	{
		// put phases of 1-series oscs into variables because phase modulation might happen
		float A1_lphase = m_lphase[A1_OSC];
		float A1_rphase = m_rphase[A1_OSC];
		float B1_lphase = m_lphase[B1_OSC];
		float B1_rphase = m_rphase[B1_OSC];

		/////////////   A-series   /////////////////

		// A2
		sample_t A2_L = linearInterpolate( m_A2wave[ static_cast<int>( m_lphase[A2_OSC] ) ],
							m_A2wave[ static_cast<int>( m_lphase[A2_OSC] + 1 ) % WAVELEN ],
							fraction( m_lphase[A2_OSC] ) ) * m_parent->m_lvol[A2_OSC];
		sample_t A2_R = linearInterpolate( m_A2wave[ static_cast<int>( m_rphase[A2_OSC] ) ],
							m_A2wave[ static_cast<int>( m_rphase[A2_OSC] + 1 ) % WAVELEN ],
							fraction( m_rphase[A2_OSC] ) ) * m_parent->m_rvol[A2_OSC];

		// if phase mod, add to phases
		if( m_amod == MOD_PM )
		{
			A1_lphase = fmodf( A1_lphase + A2_L * PMOD_AMT, WAVELEN );
			if( A1_lphase < 0 ) A1_lphase += WAVELEN;
			A1_rphase = fmodf( A1_rphase + A2_R * PMOD_AMT, WAVELEN );
			if( A1_rphase < 0 ) A1_rphase += WAVELEN;
		}
		// A1
		sample_t A1_L = linearInterpolate( m_A1wave[ static_cast<int>( A1_lphase ) ],
							m_A1wave[ static_cast<int>( A1_lphase + 1 ) % WAVELEN ],
							fraction( A1_lphase ) ) * m_parent->m_lvol[A1_OSC];
		sample_t A1_R = linearInterpolate( m_A1wave[ static_cast<int>( A1_rphase ) ],
							m_A1wave[ static_cast<int>( A1_rphase + 1 ) % WAVELEN ],
							fraction( A1_rphase ) ) * m_parent->m_rvol[A1_OSC];

		/////////////   B-series   /////////////////

		// B2
		sample_t B2_L = linearInterpolate( m_B2wave[ static_cast<int>( m_lphase[B2_OSC] ) ],
							m_B2wave[ static_cast<int>( m_lphase[B2_OSC] + 1 ) % WAVELEN ],
							fraction( m_lphase[B2_OSC] ) ) * m_parent->m_lvol[B2_OSC];
		sample_t B2_R = linearInterpolate( m_B2wave[ static_cast<int>( m_rphase[B2_OSC] ) ],
							m_B2wave[ static_cast<int>( m_rphase[B2_OSC] + 1 ) % WAVELEN ],
							fraction( m_rphase[B2_OSC] ) ) * m_parent->m_rvol[B2_OSC];

		// if crosstalk active, add a1
		const float xt = m_parent->m_xtalk.value();
		if( xt > 0.0 )
		{
			B2_L += ( A1_L * xt ) * 0.01f;
			B2_R += ( A1_R * xt ) * 0.01f;
		}

		// if phase mod, add to phases
		if( m_bmod == MOD_PM )
		{
			B1_lphase = fmodf( B1_lphase + B2_L * PMOD_AMT, WAVELEN );
			if( B1_lphase < 0 ) B1_lphase += WAVELEN;
			B1_rphase = fmodf( B1_rphase + B2_R * PMOD_AMT, WAVELEN );
			if( B1_rphase < 0 ) B1_rphase += WAVELEN;
		}
		// B1
		sample_t B1_L = linearInterpolate( m_B1wave[ static_cast<int>( B1_lphase ) % WAVELEN ],
							m_B1wave[ static_cast<int>( B1_lphase + 1 ) % WAVELEN ],
							fraction( B1_lphase ) ) * m_parent->m_lvol[B1_OSC];
		sample_t B1_R = linearInterpolate( m_B1wave[ static_cast<int>( B1_rphase ) % WAVELEN ],
							m_B1wave[ static_cast<int>( B1_rphase + 1 ) % WAVELEN ],
							fraction( B1_rphase ) ) * m_parent->m_rvol[B1_OSC];

		// A-series modulation)
		switch( m_amod )
		{
			case MOD_MIX:
				A1_L = ( A1_L + A2_L ) / 2.0;
				A1_R = ( A1_R + A2_R ) / 2.0;
				break;
			case MOD_AM:
				A1_L *= qMax( 0.0f, A2_L + 1.0f );
				A1_R *= qMax( 0.0f, A2_R + 1.0f );
				break;
			case MOD_RM:
				A1_L *= A2_L;
				A1_R *= A2_R;
				break;
		}
		m_abuf[frame][0] = A1_L;
		m_abuf[frame][1] = A1_R;

		// B-series modulation (other than phase mod)
		switch( m_bmod )
		{
			case MOD_MIX:
				B1_L = ( B1_L + B2_L ) / 2.0;
				B1_R = ( B1_R + B2_R ) / 2.0;
				break;
			case MOD_AM:
				B1_L *= qMax( 0.0f, B2_L + 1.0f );
				B1_R *= qMax( 0.0f, B2_R + 1.0f );
				break;
			case MOD_RM:
				B1_L *= B2_L;
				B1_R *= B2_R;
				break;
		}
		m_bbuf[frame][0] = B1_L;
		m_bbuf[frame][1] = B1_R;

		// update phases
		for( int i = 0; i < NUM_OSCS; i++ )
		{
			m_lphase[i] += ( static_cast<float>( WAVELEN ) / ( m_samplerate / ( m_nph->frequency() * m_parent->m_lfreq[i] ) ) );
			m_lphase[i] = fmodf( m_lphase[i], WAVELEN );
			m_rphase[i] += ( static_cast<float>( WAVELEN ) / ( m_samplerate / ( m_nph->frequency() * m_parent->m_rfreq[i] ) ) );
			m_rphase[i] = fmodf( m_rphase[i], WAVELEN );
		}
	}

}

WatsynInstrument::WatsynInstrument( InstrumentTrack * _instrument_track ) :
		Instrument( _instrument_track, &watsyn_plugin_descriptor ),

		a1_vol( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Volume A1" ) ),
		a2_vol( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Volume A2" ) ),
		b1_vol( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Volume B1" ) ),
		b2_vol( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Volume B2" ) ),

		a1_pan( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "Panning A1" ) ),
		a2_pan( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "Panning A2" ) ),
		b1_pan( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "Panning B1" ) ),
		b2_pan( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "Panning B2" ) ),

		a1_mult( 8.0f, 1.0, 24.0, 1.0, this, tr( "Freq. multiplier A1" ) ),
		a2_mult( 8.0f, 1.0, 24.0, 1.0, this, tr( "Freq. multiplier A2" ) ),
		b1_mult( 8.0f, 1.0, 24.0, 1.0, this, tr( "Freq. multiplier B1" ) ),
		b2_mult( 8.0f, 1.0, 24.0, 1.0, this, tr( "Freq. multiplier B2" ) ),

		a1_ltune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Left detune A1" ) ),
		a2_ltune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Left detune A2" ) ),
		b1_ltune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Left detune B1" ) ),
		b2_ltune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Left detune B2" ) ),

		a1_rtune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Right detune A1" ) ),
		a2_rtune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Right detune A2" ) ),
		b1_rtune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Right detune B1" ) ),
		b2_rtune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Right detune B2" ) ),

		a1_graph( -1.0f, 1.0f, GRAPHLEN, this ),
		a2_graph( -1.0f, 1.0f, GRAPHLEN, this ),
		b1_graph( -1.0f, 1.0f, GRAPHLEN, this ),
		b2_graph( -1.0f, 1.0f, GRAPHLEN, this ),

		m_abmix( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "A-B Mix" ) ),
		m_envAmt( 0.0f, -200.0f, 200.0f, 1.0f, this, tr( "A-B Mix envelope amount" ) ),

		m_envAtt( 0.0f, 0.0f, 2000.0f, 1.0f, 2000.0f, this, tr( "A-B Mix envelope attack" ) ),
		m_envHold( 0.0f, 0.0f, 2000.0f, 1.0f, 2000.0f, this, tr( "A-B Mix envelope hold" ) ),
		m_envDec( 0.0f, 0.0f, 2000.0f, 1.0f, 2000.0f, this, tr( "A-B Mix envelope decay" ) ),

		m_xtalk( 0.0f, 0.0f, 100.0f, 0.1f, this, tr( "A1-B2 Crosstalk" ) ),

		m_amod( 0, 0, 3, this, tr( "A2-A1 modulation" ) ),
		m_bmod( 0, 0, 3, this, tr( "B2-B1 modulation" ) ),

		m_selectedGraph( 0, 0, 3, this, tr( "Selected graph" ) )
{
	connect( &a1_vol, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) );
	connect( &a2_vol, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) );
	connect( &b1_vol, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) );
	connect( &b2_vol, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) );

	connect( &a1_pan, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) );
	connect( &a2_pan, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) );
	connect( &b1_pan, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) );
	connect( &b2_pan, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) );

	connect( &a1_mult, SIGNAL( dataChanged() ), this, SLOT( updateFreqA1() ) );
	connect( &a2_mult, SIGNAL( dataChanged() ), this, SLOT( updateFreqA2() ) );
	connect( &b1_mult, SIGNAL( dataChanged() ), this, SLOT( updateFreqB1() ) );
	connect( &b2_mult, SIGNAL( dataChanged() ), this, SLOT( updateFreqB2() ) );

	connect( &a1_ltune, SIGNAL( dataChanged() ), this, SLOT( updateFreqA1() ) );
	connect( &a2_ltune, SIGNAL( dataChanged() ), this, SLOT( updateFreqA2() ) );
	connect( &b1_ltune, SIGNAL( dataChanged() ), this, SLOT( updateFreqB1() ) );
	connect( &b2_ltune, SIGNAL( dataChanged() ), this, SLOT( updateFreqB2() ) );

	connect( &a1_rtune, SIGNAL( dataChanged() ), this, SLOT( updateFreqA1() ) );
	connect( &a2_rtune, SIGNAL( dataChanged() ), this, SLOT( updateFreqA2() ) );
	connect( &b1_rtune, SIGNAL( dataChanged() ), this, SLOT( updateFreqB1() ) );
	connect( &b2_rtune, SIGNAL( dataChanged() ), this, SLOT( updateFreqB2() ) );

	connect( &a1_graph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( updateWaveA1() ) );
	connect( &a2_graph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( updateWaveA2() ) );
	connect( &b1_graph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( updateWaveB1() ) );
	connect( &b2_graph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( updateWaveB2() ) );

	a1_graph.setWaveToSine();
	a2_graph.setWaveToSine();
	b1_graph.setWaveToSine();
	b2_graph.setWaveToSine();

	updateVolumes();
	updateFreqA1();
	updateFreqA2();
	updateFreqB1();
	updateFreqB2();
	updateWaveA1();
	updateWaveA2();
	updateWaveB1();
	updateWaveB2();
}

WatsynInstrument::~WatsynInstrument()
{
}

void WatsynInstrument::playNote( NotePlayHandle * _n,
						sampleFrame * _working_buffer )
{
	if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == NULL )
	{
		WatsynObject * w = new WatsynObject(
				&A1_wave[0],
				&A2_wave[0],
				&B1_wave[0],
				&B2_wave[0],
				m_amod.value(), m_bmod.value(),
				Engine::mixer()->processingSampleRate(), _n,
				Engine::mixer()->framesPerPeriod(), this );

		_n->m_pluginData = w;
	}

	const fpp_t frames = _n->framesLeftForCurrentPeriod();
	const f_cnt_t offset = _n->noteOffset();
	sampleFrame * buffer = _working_buffer + offset;

	WatsynObject * w = static_cast<WatsynObject *>( _n->m_pluginData );

	sampleFrame * abuf = w->abuf();
	sampleFrame * bbuf = w->bbuf();

	w-> renderOutput( frames );

	// envelope parameters
	const float envAmt = m_envAmt.value();
	const float envAtt = ( m_envAtt.value() * w->samplerate() ) / 1000.0f;
	const float envHold = ( m_envHold.value() * w->samplerate() ) / 1000.0f;
	const float envDec = ( m_envDec.value() * w->samplerate() ) / 1000.0f;
	const float envLen = envAtt + envDec + envHold;
	const float tfp_ = static_cast<float>( _n->totalFramesPlayed() );

	// if sample-exact is enabled, use sample-exact calculations...
	// disabled pending proper implementation of sample-exactness
/*	if( engine::mixer()->currentQualitySettings().sampleExactControllers )
	{
		for( fpp_t f=0; f < frames; f++ )
		{
			const float tfp = tfp_ + f;
			// handle mixing envelope
			float mixvalue = m_abmix.value( f );
			if( envAmt != 0.0f && tfp < envLen )
			{
				if( tfp < envAtt )
				{
					mixvalue = qBound( -100.0f, mixvalue + ( tfp / envAtt * envAmt ), 100.0f );
				}
				else if ( tfp >= envAtt && tfp < envAtt + envHold )
				{
					mixvalue = qBound( -100.0f, mixvalue + envAmt, 100.0f );
				}
				else
				{
					mixvalue = qBound( -100.0f, mixvalue + envAmt - ( ( tfp - ( envAtt + envHold ) ) / envDec * envAmt ), 100.0f );
				}
			}
			// get knob values in sample-exact way
			const float bmix = ( ( mixvalue + 100.0 ) / 200.0 );
			const float amix = 1.0 - bmix;

			// mix a/b streams according to mixing knob
			_working_buffer[f][0] = ( abuf[f][0] * amix ) +
									( bbuf[f][0] * bmix );
			_working_buffer[f][1] = ( abuf[f][1] * amix ) +
									( bbuf[f][1] * bmix );
		}
	}
	else*/

	// if sample-exact is not enabled, use simpler calculations:
	// if mix envelope is active, and we haven't gone past the envelope end, use envelope-aware calculation...
	if( envAmt != 0.0f && tfp_ < envLen )
	{
		const float mixvalue_ = m_abmix.value();
		for( fpp_t f=0; f < frames; f++ )
		{
			float mixvalue = mixvalue_;
			const float tfp = tfp_ + f;
			// handle mixing envelope
			if( tfp < envAtt )
			{
				mixvalue = qBound( -100.0f, mixvalue + ( tfp / envAtt * envAmt ), 100.0f );
			}
			else if ( tfp >= envAtt && tfp < envAtt + envHold )
			{
				mixvalue = qBound( -100.0f, mixvalue + envAmt, 100.0f );
			}
			else
			{
				mixvalue = qBound( -100.0f, mixvalue + envAmt - ( ( tfp - ( envAtt + envHold ) ) / envDec * envAmt ), 100.0f );
			}

			// get knob values
			const float bmix = ( ( mixvalue + 100.0 ) / 200.0 );
			const float amix = 1.0 - bmix;

			// mix a/b streams according to mixing knob
			buffer[f][0] = ( abuf[f][0] * amix ) +
									( bbuf[f][0] * bmix );
			buffer[f][1] = ( abuf[f][1] * amix ) +
									( bbuf[f][1] * bmix );
		}
	}

	// ... mix envelope is inactive or we've past the end of envelope, so use a faster calculation to save cpu
	else
	{
		// get knob values
		const float bmix = ( ( m_abmix.value() + 100.0 ) / 200.0 );
		const float amix = 1.0 - bmix;
		for( fpp_t f=0; f < frames; f++ )
		{
			// mix a/b streams according to mixing knob
			buffer[f][0] = ( abuf[f][0] * amix ) +
									( bbuf[f][0] * bmix );
			buffer[f][1] = ( abuf[f][1] * amix ) +
									( bbuf[f][1] * bmix );
		}
	}

	applyRelease( _working_buffer, _n );

	instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n );
}

void WatsynInstrument::deleteNotePluginData( NotePlayHandle * _n )
{
	delete static_cast<WatsynObject *>( _n->m_pluginData );
}

void WatsynInstrument::saveSettings( QDomDocument & _doc,
							QDomElement & _this )
{
	a1_vol.saveSettings( _doc, _this, "a1_vol" );
	a2_vol.saveSettings( _doc, _this, "a2_vol" );
	b1_vol.saveSettings( _doc, _this, "b1_vol" );
	b2_vol.saveSettings( _doc, _this, "b2_vol" );

	a1_pan.saveSettings( _doc, _this, "a1_pan" );
	a2_pan.saveSettings( _doc, _this, "a2_pan" );
	b1_pan.saveSettings( _doc, _this, "b1_pan" );
	b2_pan.saveSettings( _doc, _this, "b2_pan" );

	a1_mult.saveSettings( _doc, _this, "a1_mult" );
	a2_mult.saveSettings( _doc, _this, "a2_mult" );
	b1_mult.saveSettings( _doc, _this, "b1_mult" );
	b2_mult.saveSettings( _doc, _this, "b2_mult" );

	a1_ltune.saveSettings( _doc, _this, "a1_ltune" );
	a2_ltune.saveSettings( _doc, _this, "a2_ltune" );
	b1_ltune.saveSettings( _doc, _this, "b1_ltune" );
	b2_ltune.saveSettings( _doc, _this, "b2_ltune" );

	a1_rtune.saveSettings( _doc, _this, "a1_rtune" );
	a2_rtune.saveSettings( _doc, _this, "a2_rtune" );
	b1_rtune.saveSettings( _doc, _this, "b1_rtune" );
	b2_rtune.saveSettings( _doc, _this, "b2_rtune" );

	// save graphs
	QString sampleString;

	base64::encode( (const char *)a1_graph.samples(), a1_graph.length() * sizeof(float), sampleString );
	_this.setAttribute( "a1_wave", sampleString );
	base64::encode( (const char *)a2_graph.samples(), a2_graph.length() * sizeof(float), sampleString );
	_this.setAttribute( "a2_wave", sampleString );
	base64::encode( (const char *)b1_graph.samples(), b1_graph.length() * sizeof(float), sampleString );
	_this.setAttribute( "b1_wave", sampleString );
	base64::encode( (const char *)b2_graph.samples(), b2_graph.length() * sizeof(float), sampleString );
	_this.setAttribute( "b2_wave", sampleString );

	m_abmix.saveSettings( _doc, _this, "abmix" );
	m_envAmt.saveSettings( _doc, _this, "envAmt" );
	m_envAtt.saveSettings( _doc, _this, "envAtt" );
	m_envHold.saveSettings( _doc, _this, "envHold" );
	m_envDec.saveSettings( _doc, _this, "envDec" );

	m_xtalk.saveSettings( _doc, _this, "xtalk" );

	m_amod.saveSettings( _doc, _this, "amod" );
	m_bmod.saveSettings( _doc, _this, "bmod" );
/*	m_selectedGraph.saveSettings( _doc, _this, "selgraph" );*/
}

void WatsynInstrument::loadSettings( const QDomElement & _this )
{
	a1_vol.loadSettings( _this, "a1_vol" );
	a2_vol.loadSettings( _this, "a2_vol" );
	b1_vol.loadSettings( _this, "b1_vol" );
	b2_vol.loadSettings( _this, "b2_vol" );

	a1_pan.loadSettings( _this, "a1_pan" );
	a2_pan.loadSettings( _this, "a2_pan" );
	b1_pan.loadSettings( _this, "b1_pan" );
	b2_pan.loadSettings( _this, "b2_pan" );

	a1_mult.loadSettings( _this, "a1_mult" );
	a2_mult.loadSettings( _this, "a2_mult" );
	b1_mult.loadSettings( _this, "b1_mult" );
	b2_mult.loadSettings( _this, "b2_mult" );

	a1_ltune.loadSettings( _this, "a1_ltune" );
	a2_ltune.loadSettings( _this, "a2_ltune" );
	b1_ltune.loadSettings( _this, "b1_ltune" );
	b2_ltune.loadSettings( _this, "b2_ltune" );

	a1_rtune.loadSettings( _this, "a1_rtune" );
	a2_rtune.loadSettings( _this, "a2_rtune" );
	b1_rtune.loadSettings( _this, "b1_rtune" );
	b2_rtune.loadSettings( _this, "b2_rtune" );

	// load graphs
	int size = 0;
	char * dst = 0;

	base64::decode( _this.attribute( "a1_wave"), &dst, &size );
	a1_graph.setSamples( (float*) dst );
	base64::decode( _this.attribute( "a2_wave"), &dst, &size );
	a2_graph.setSamples( (float*) dst );
	base64::decode( _this.attribute( "b1_wave"), &dst, &size );
	b1_graph.setSamples( (float*) dst );
	base64::decode( _this.attribute( "b2_wave"), &dst, &size );
	b2_graph.setSamples( (float*) dst );

	delete[] dst;

	m_abmix.loadSettings( _this, "abmix" );

	m_envAmt.loadSettings( _this, "envAmt" );
	m_envAtt.loadSettings( _this, "envAtt" );
	m_envHold.loadSettings( _this, "envHold" );
	m_envDec.loadSettings( _this, "envDec" );

	m_xtalk.loadSettings( _this, "xtalk" );

	m_amod.loadSettings( _this, "amod" );
	m_bmod.loadSettings( _this, "bmod" );
/*	m_selectedGraph.loadSettings( _this, "selgraph" );*/
}

QString WatsynInstrument::nodeName() const
{
	return( watsyn_plugin_descriptor.name );
}

PluginView * WatsynInstrument::instantiateView( QWidget * _parent )
{
	return( new WatsynView( this, _parent ) );
}

void WatsynInstrument::updateVolumes()
{
	m_lvol[A1_OSC] = leftCh( a1_vol.value(), a1_pan.value() );
	m_rvol[A1_OSC] = rightCh( a1_vol.value(), a1_pan.value() );

	m_lvol[A2_OSC] = leftCh( a2_vol.value(), a2_pan.value() );
	m_rvol[A2_OSC] = rightCh( a2_vol.value(), a2_pan.value() );

	m_lvol[B1_OSC] = leftCh( b1_vol.value(), b1_pan.value() );
	m_rvol[B1_OSC] = rightCh( b1_vol.value(), b1_pan.value() );

	m_lvol[B2_OSC] = leftCh( b2_vol.value(), b2_pan.value() );
	m_rvol[B2_OSC] = rightCh( b2_vol.value(), b2_pan.value() );
}

void WatsynInstrument::updateFreqA1()
{
	// calculate frequencies
	m_lfreq[A1_OSC] = ( a1_mult.value() / 8 ) * powf( 2, a1_ltune.value() / 1200 );
	m_rfreq[A1_OSC] = ( a1_mult.value() / 8 ) * powf( 2, a1_rtune.value() / 1200 );
}

void WatsynInstrument::updateFreqA2()
{
	// calculate frequencies
	m_lfreq[A2_OSC] = ( a2_mult.value() / 8 ) * powf( 2, a2_ltune.value() / 1200 );
	m_rfreq[A2_OSC] = ( a2_mult.value() / 8 ) * powf( 2, a2_rtune.value() / 1200 );
}

void WatsynInstrument::updateFreqB1()
{
	// calculate frequencies
	m_lfreq[B1_OSC] = ( b1_mult.value() / 8 ) * powf( 2, b1_ltune.value() / 1200 );
	m_rfreq[B1_OSC] = ( b1_mult.value() / 8 ) * powf( 2, b1_rtune.value() / 1200 );
}

void WatsynInstrument::updateFreqB2()
{
	// calculate frequencies
	m_lfreq[B2_OSC] = ( b2_mult.value() / 8 ) * powf( 2, b2_ltune.value() / 1200 );
	m_rfreq[B2_OSC] = ( b2_mult.value() / 8 ) * powf( 2, b2_rtune.value() / 1200 );
}

void WatsynInstrument::updateWaveA1()
{
	// do sinc+oversampling on the wavetables to improve quality
	srccpy( &A1_wave[0], const_cast<float*>( a1_graph.samples() ) );
}

void WatsynInstrument::updateWaveA2()
{
	// do sinc+oversampling on the wavetables to improve quality
	srccpy( &A2_wave[0], const_cast<float*>( a2_graph.samples() ) );
}

void WatsynInstrument::updateWaveB1()
{
	// do sinc+oversampling on the wavetables to improve quality
	srccpy( &B1_wave[0], const_cast<float*>( b1_graph.samples() ) );
}

void WatsynInstrument::updateWaveB2()
{
	// do sinc+oversampling on the wavetables to improve quality
	srccpy( &B2_wave[0], const_cast<float*>( b2_graph.samples() ) );
}

WatsynView::WatsynView( Instrument * _instrument,
					QWidget * _parent ) :
	InstrumentViewFixedSize( _instrument, _parent )
{
	setAutoFillBackground( true );
	QPalette pal;

	pal.setBrush( backgroundRole(),	PLUGIN_NAME::getIconPixmap(	"artwork" ) );
	setPalette( pal );

// knobs... lots of em

	makeknob( a1_volKnob, 130, A1ROW, tr( "Volume" ), "%", "aKnob" )
	makeknob( a2_volKnob, 130, A2ROW, tr( "Volume" ), "%", "aKnob" )
	makeknob( b1_volKnob, 130, B1ROW, tr( "Volume" ), "%", "bKnob" )
	makeknob( b2_volKnob, 130, B2ROW, tr( "Volume" ), "%", "bKnob"  )

	makeknob( a1_panKnob, 154, A1ROW, tr( "Panning" ), "", "aKnob" )
	makeknob( a2_panKnob, 154, A2ROW, tr( "Panning" ), "", "aKnob" )
	makeknob( b1_panKnob, 154, B1ROW, tr( "Panning" ), "", "bKnob"  )
	makeknob( b2_panKnob, 154, B2ROW, tr( "Panning" ), "", "bKnob"  )

	makeknob( a1_multKnob, 178, A1ROW, tr( "Freq. multiplier" ), "/8", "aKnob" )
	makeknob( a2_multKnob, 178, A2ROW, tr( "Freq. multiplier" ), "/8", "aKnob" )
	makeknob( b1_multKnob, 178, B1ROW, tr( "Freq. multiplier" ), "/8", "bKnob"  )
	makeknob( b2_multKnob, 178, B2ROW, tr( "Freq. multiplier" ), "/8", "bKnob"  )

	makeknob( a1_ltuneKnob, 202, A1ROW, tr( "Left detune" ), tr( " cents" ), "aKnob" )
	makeknob( a2_ltuneKnob, 202, A2ROW, tr( "Left detune" ), tr( " cents" ), "aKnob" )
	makeknob( b1_ltuneKnob, 202, B1ROW, tr( "Left detune" ), tr( " cents" ), "bKnob"  )
	makeknob( b2_ltuneKnob, 202, B2ROW, tr( "Left detune" ), tr( " cents" ), "bKnob"  )

	makeknob( a1_rtuneKnob, 226, A1ROW, tr( "Right detune" ), tr( " cents" ), "aKnob" )
	makeknob( a2_rtuneKnob, 226, A2ROW, tr( "Right detune" ), tr( " cents" ), "aKnob" )
	makeknob( b1_rtuneKnob, 226, B1ROW, tr( "Right detune" ), tr( " cents" ), "bKnob"  )
	makeknob( b2_rtuneKnob, 226, B2ROW, tr( "Right detune" ), tr( " cents" ), "bKnob"  )

	makeknob( m_abmixKnob, 4, 3, tr( "A-B Mix" ), "", "mixKnob" )

	makeknob( m_envAmtKnob, 88, 3, tr( "Mix envelope amount" ), "", "mixenvKnob" )

	maketsknob( m_envAttKnob, 88, A1ROW, tr( "Mix envelope attack" ), " ms", "mixenvKnob" )
	maketsknob( m_envHoldKnob, 88, A2ROW, tr( "Mix envelope hold" ), " ms", "mixenvKnob" )
	maketsknob( m_envDecKnob, 88, B1ROW, tr( "Mix envelope decay" ), " ms", "mixenvKnob" )

	makeknob( m_xtalkKnob, 88, B2ROW, tr( "Crosstalk" ), "", "xtalkKnob" )

// let's set volume knobs
	a1_volKnob -> setVolumeKnob( true );
	a2_volKnob -> setVolumeKnob( true );
	b1_volKnob -> setVolumeKnob( true );
	b2_volKnob -> setVolumeKnob( true );

	m_abmixKnob -> setFixedSize( 31, 31 );

// button groups next.
// graph select buttons
	PixmapButton * a1_selectButton = new PixmapButton( this, NULL );
	a1_selectButton -> move( 4, 121 );
	a1_selectButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "a1_active" ) );
	a1_selectButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "a1_inactive" ) );
	ToolTip::add( a1_selectButton, tr( "Select oscillator A1") );

	PixmapButton * a2_selectButton = new PixmapButton( this, NULL );
	a2_selectButton -> move( 44, 121 );
	a2_selectButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "a2_active" ) );
	a2_selectButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "a2_inactive" ) );
	ToolTip::add( a2_selectButton, tr( "Select oscillator A2") );

	PixmapButton * b1_selectButton = new PixmapButton( this, NULL );
	b1_selectButton -> move( 84, 121 );
	b1_selectButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "b1_active" ) );
	b1_selectButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "b1_inactive" ) );
	ToolTip::add( b1_selectButton, tr( "Select oscillator B1") );

	PixmapButton * b2_selectButton = new PixmapButton( this, NULL );
	b2_selectButton -> move( 124, 121 );
	b2_selectButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "b2_active" ) );
	b2_selectButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "b2_inactive" ) );
	ToolTip::add( b2_selectButton, tr( "Select oscillator B2") );

	m_selectedGraphGroup = new automatableButtonGroup( this );
	m_selectedGraphGroup -> addButton( a1_selectButton );
	m_selectedGraphGroup -> addButton( a2_selectButton );
	m_selectedGraphGroup -> addButton( b1_selectButton );
	m_selectedGraphGroup -> addButton( b2_selectButton );
	WatsynInstrument * w = castModel<WatsynInstrument>();
	m_selectedGraphGroup -> setModel( &w -> m_selectedGraph);

// A-modulation button group
	PixmapButton * amod_mixButton = new PixmapButton( this, NULL );
	amod_mixButton -> move( 4, 50 );
	amod_mixButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "amix_active" ) );
	amod_mixButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "amix_inactive" ) );
	ToolTip::add( amod_mixButton, tr( "Mix output of A2 to A1" ) );

	PixmapButton * amod_amButton = new PixmapButton( this, NULL );
	amod_amButton -> move( 4, 66 );
	amod_amButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "aam_active" ) );
	amod_amButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "aam_inactive" ) );
	ToolTip::add( amod_amButton, tr( "Modulate amplitude of A1 by output of A2" ) );

	PixmapButton * amod_rmButton = new PixmapButton( this, NULL );
	amod_rmButton -> move( 4, 82 );
	amod_rmButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "arm_active" ) );
	amod_rmButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "arm_inactive" ) );
	ToolTip::add( amod_rmButton, tr( "Ring modulate A1 and A2" ) );

	PixmapButton * amod_pmButton = new PixmapButton( this, NULL );
	amod_pmButton -> move( 4, 98 );
	amod_pmButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "apm_active" ) );
	amod_pmButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "apm_inactive" ) );
	ToolTip::add( amod_pmButton, tr( "Modulate phase of A1 by output of A2" ) );

	m_aModGroup = new automatableButtonGroup( this );
	m_aModGroup -> addButton( amod_mixButton );
	m_aModGroup -> addButton( amod_amButton );
	m_aModGroup -> addButton( amod_rmButton );
	m_aModGroup -> addButton( amod_pmButton );

// B-modulation button group
	PixmapButton * bmod_mixButton = new PixmapButton( this, NULL );
	bmod_mixButton -> move( 44, 50 );
	bmod_mixButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "bmix_active" ) );
	bmod_mixButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "bmix_inactive" ) );
	ToolTip::add( bmod_mixButton, tr( "Mix output of B2 to B1" ) );

	PixmapButton * bmod_amButton = new PixmapButton( this, NULL );
	bmod_amButton -> move( 44, 66 );
	bmod_amButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "bam_active" ) );
	bmod_amButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "bam_inactive" ) );
	ToolTip::add( bmod_amButton, tr( "Modulate amplitude of B1 by output of B2" ) );

	PixmapButton * bmod_rmButton = new PixmapButton( this, NULL );
	bmod_rmButton -> move( 44, 82 );
	bmod_rmButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "brm_active" ) );
	bmod_rmButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "brm_inactive" ) );
	ToolTip::add( bmod_rmButton, tr( "Ring modulate B1 and B2" ) );

	PixmapButton * bmod_pmButton = new PixmapButton( this, NULL );
	bmod_pmButton -> move( 44, 98 );
	bmod_pmButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "bpm_active" ) );
	bmod_pmButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "bpm_inactive" ) );
	ToolTip::add( bmod_pmButton, tr( "Modulate phase of B1 by output of B2" ) );

	m_bModGroup = new automatableButtonGroup( this );
	m_bModGroup -> addButton( bmod_mixButton );
	m_bModGroup -> addButton( bmod_amButton );
	m_bModGroup -> addButton( bmod_rmButton );
	m_bModGroup -> addButton( bmod_pmButton );

// graph widgets
	pal = QPalette();
	pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap("wavegraph") );
// a1 graph
	a1_graph = new Graph( this, Graph::LinearStyle, 224, 105 );
	a1_graph->move( 4, 141 );
	a1_graph->setAutoFillBackground( true );
	a1_graph->setGraphColor( QColor( 0x43, 0xb2, 0xff ) );
	ToolTip::add( a1_graph, tr ( "Draw your own waveform here by dragging your mouse on this graph." ) );
	a1_graph->setPalette( pal );

// a2 graph
	a2_graph = new Graph( this, Graph::LinearStyle, 224, 105 );
	a2_graph->move( 4, 141 );
	a2_graph->setAutoFillBackground( true );
	a2_graph->setGraphColor( QColor( 0x43, 0xb2, 0xff ) );
	ToolTip::add( a2_graph, tr ( "Draw your own waveform here by dragging your mouse on this graph." ) );
	a2_graph->setPalette( pal );

// b1 graph
	b1_graph = new Graph( this, Graph::LinearStyle, 224, 105 );
	b1_graph->move( 4, 141 );
	b1_graph->setAutoFillBackground( true );
	b1_graph->setGraphColor( QColor( 0xfc, 0x54, 0x31 ) );
	ToolTip::add( b1_graph, tr ( "Draw your own waveform here by dragging your mouse on this graph." ) );
	b1_graph->setPalette( pal );

// b2 graph
	b2_graph = new Graph( this, Graph::LinearStyle, 224, 105 );
	b2_graph->move( 4, 141 );
	b2_graph->setAutoFillBackground( true );
	b2_graph->setGraphColor( QColor( 0xfc, 0x54, 0x31 ) );
	ToolTip::add( b2_graph, tr ( "Draw your own waveform here by dragging your mouse on this graph." ) );
	b2_graph->setPalette( pal );

// misc pushbuttons
// waveform modifications

	m_loadButton = new PixmapButton( this, tr( "Load waveform" ) );
	m_loadButton -> move ( 173, 121 );
	m_loadButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "load_active" ) );
	m_loadButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "load_inactive" ) );
	ToolTip::add( m_loadButton, tr( "Load a waveform from a sample file" ) );

	m_phaseLeftButton = new PixmapButton( this, tr( "Phase left" ) );
	m_phaseLeftButton -> move ( 193, 121 );
	m_phaseLeftButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "phl_active" ) );
	m_phaseLeftButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "phl_inactive" ) );
	ToolTip::add( m_phaseLeftButton, tr( "Shift phase by -15 degrees" ) );

	m_phaseRightButton = new PixmapButton( this, tr( "Phase right" ) );
	m_phaseRightButton -> move ( 210, 121 );
	m_phaseRightButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "phr_active" ) );
	m_phaseRightButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "phr_inactive" ) );
	ToolTip::add( m_phaseRightButton, tr( "Shift phase by +15 degrees" ) );

	m_normalizeButton = new PixmapButton( this, tr( "Normalize" ) );
	m_normalizeButton -> move ( 230, 121 );
	m_normalizeButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "norm_active" ) );
	m_normalizeButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "norm_inactive" ) );
	ToolTip::add( m_normalizeButton, tr( "Normalize" ) );

	m_invertButton = new PixmapButton( this, tr( "Invert" ) );
	m_invertButton -> move ( 230, 138 );
	m_invertButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "inv_active" ) );
	m_invertButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "inv_inactive" ) );
	ToolTip::add( m_invertButton, tr( "Invert" ) );

	m_smoothButton = new PixmapButton( this, tr( "Smooth" ) );
	m_smoothButton -> move ( 230, 155 );
	m_smoothButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "smooth_active" ) );
	m_smoothButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "smooth_inactive" ) );
	ToolTip::add( m_smoothButton, tr( "Smooth" ) );

// waveforms

	m_sinWaveButton = new PixmapButton( this, tr( "Sine wave" ) );
	m_sinWaveButton -> move ( 230, 176 );
	m_sinWaveButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "sin_active" ) );
	m_sinWaveButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "sin_inactive" ) );
	ToolTip::add( m_sinWaveButton, tr( "Sine wave" ) );

	m_triWaveButton = new PixmapButton( this, tr( "Triangle wave" ) );
	m_triWaveButton -> move ( 230, 194 );
	m_triWaveButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "tri_active" ) );
	m_triWaveButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "tri_inactive" ) );
	ToolTip::add( m_triWaveButton, tr( "Triangle wave" ) );

	m_sawWaveButton = new PixmapButton( this, tr( "Triangle wave" ) );
	m_sawWaveButton -> move ( 230, 212 );
	m_sawWaveButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "saw_active" ) );
	m_sawWaveButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "saw_inactive" ) );
	ToolTip::add( m_sawWaveButton, tr( "Saw wave" ) );

	m_sqrWaveButton = new PixmapButton( this, tr( "Square wave" ) );
	m_sqrWaveButton -> move ( 230, 230 );
	m_sqrWaveButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "sqr_active" ) );
	m_sqrWaveButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "sqr_inactive" ) );
	ToolTip::add( m_sqrWaveButton, tr( "Square wave" ) );

	connect( m_sinWaveButton, SIGNAL( clicked() ), this, SLOT( sinWaveClicked() ) );
	connect( m_triWaveButton, SIGNAL( clicked() ), this, SLOT( triWaveClicked() ) );
	connect( m_sawWaveButton, SIGNAL( clicked() ), this, SLOT( sawWaveClicked() ) );
	connect( m_sqrWaveButton, SIGNAL( clicked() ), this, SLOT( sqrWaveClicked() ) );
	connect( m_normalizeButton, SIGNAL( clicked() ), this, SLOT( normalizeClicked() ) );
	connect( m_invertButton, SIGNAL( clicked() ), this, SLOT( invertClicked() ) );
	connect( m_smoothButton, SIGNAL( clicked() ), this, SLOT( smoothClicked() ) );
	connect( m_phaseLeftButton, SIGNAL( clicked() ), this, SLOT( phaseLeftClicked() ) );
	connect( m_phaseRightButton, SIGNAL( clicked() ), this, SLOT( phaseRightClicked() ) );
	connect( m_loadButton, SIGNAL( clicked() ), this, SLOT( loadClicked() ) );

	connect( a1_selectButton, SIGNAL( clicked() ), this, SLOT( updateLayout() ) );
	connect( a2_selectButton, SIGNAL( clicked() ), this, SLOT( updateLayout() ) );
	connect( b1_selectButton, SIGNAL( clicked() ), this, SLOT( updateLayout() ) );
	connect( b2_selectButton, SIGNAL( clicked() ), this, SLOT( updateLayout() ) );

	updateLayout();
}

WatsynView::~WatsynView()
{
}

void WatsynView::updateLayout()
{
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->show();
			a2_graph->hide();
			b1_graph->hide();
			b2_graph->hide();
			break;
		case A2_OSC:
			a1_graph->hide();
			a2_graph->show();
			b1_graph->hide();
			b2_graph->hide();
			break;
		case B1_OSC:
			a1_graph->hide();
			a2_graph->hide();
			b1_graph->show();
			b2_graph->hide();
			break;
		case B2_OSC:
			a1_graph->hide();
			a2_graph->hide();
			b1_graph->hide();
			b2_graph->show();
			break;
	}
}

void WatsynView::sinWaveClicked()
{
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->model()->setWaveToSine();
			Engine::getSong()->setModified();
			break;
		case A2_OSC:
			a2_graph->model()->setWaveToSine();
			Engine::getSong()->setModified();
			break;
		case B1_OSC:
			b1_graph->model()->setWaveToSine();
			Engine::getSong()->setModified();
			break;
		case B2_OSC:
			b2_graph->model()->setWaveToSine();
			Engine::getSong()->setModified();
			break;
	}
}

void WatsynView::triWaveClicked()
{
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->model()->setWaveToTriangle();
			Engine::getSong()->setModified();
			break;
		case A2_OSC:
			a2_graph->model()->setWaveToTriangle();
			Engine::getSong()->setModified();
			break;
		case B1_OSC:
			b1_graph->model()->setWaveToTriangle();
			Engine::getSong()->setModified();
			break;
		case B2_OSC:
			b2_graph->model()->setWaveToTriangle();
			Engine::getSong()->setModified();
			break;
	}
}

void WatsynView::sawWaveClicked()
{
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->model()->setWaveToSaw();
			Engine::getSong()->setModified();
			break;
		case A2_OSC:
			a2_graph->model()->setWaveToSaw();
			Engine::getSong()->setModified();
			break;
		case B1_OSC:
			b1_graph->model()->setWaveToSaw();
			Engine::getSong()->setModified();
			break;
		case B2_OSC:
			b2_graph->model()->setWaveToSaw();
			Engine::getSong()->setModified();
			break;
	}
}

void WatsynView::sqrWaveClicked()
{
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->model()->setWaveToSquare();
			Engine::getSong()->setModified();
			break;
		case A2_OSC:
			a2_graph->model()->setWaveToSquare();
			Engine::getSong()->setModified();
			break;
		case B1_OSC:
			b1_graph->model()->setWaveToSquare();
			Engine::getSong()->setModified();
			break;
		case B2_OSC:
			b2_graph->model()->setWaveToSquare();
			Engine::getSong()->setModified();
			break;
	}
}

void WatsynView::normalizeClicked()
{
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->model()->normalize();
			Engine::getSong()->setModified();
			break;
		case A2_OSC:
			a2_graph->model()->normalize();
			Engine::getSong()->setModified();
			break;
		case B1_OSC:
			b1_graph->model()->normalize();
			Engine::getSong()->setModified();
			break;
		case B2_OSC:
			b2_graph->model()->normalize();
			Engine::getSong()->setModified();
			break;
	}
}

void WatsynView::invertClicked()
{
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->model()->invert();
			Engine::getSong()->setModified();
			break;
		case A2_OSC:
			a2_graph->model()->invert();
			Engine::getSong()->setModified();
			break;
		case B1_OSC:
			b1_graph->model()->invert();
			Engine::getSong()->setModified();
			break;
		case B2_OSC:
			b2_graph->model()->invert();
			Engine::getSong()->setModified();
			break;
	}
}

void WatsynView::smoothClicked()
{
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->model()->smooth();
			Engine::getSong()->setModified();
			break;
		case A2_OSC:
			a2_graph->model()->smooth();
			Engine::getSong()->setModified();
			break;
		case B1_OSC:
			b1_graph->model()->smooth();
			Engine::getSong()->setModified();
			break;
		case B2_OSC:
			b2_graph->model()->smooth();
			Engine::getSong()->setModified();
			break;
	}
}

void WatsynView::phaseLeftClicked()
{
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->model()->shiftPhase( -15 );
			Engine::getSong()->setModified();
			break;
		case A2_OSC:
			a2_graph->model()->shiftPhase( -15 );
			Engine::getSong()->setModified();
			break;
		case B1_OSC:
			b1_graph->model()->shiftPhase( -15 );
			Engine::getSong()->setModified();
			break;
		case B2_OSC:
			b2_graph->model()->shiftPhase( -15 );
			Engine::getSong()->setModified();
			break;
	}
}

void WatsynView::phaseRightClicked()
{
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->model()->shiftPhase( 15 );
			Engine::getSong()->setModified();
			break;
		case A2_OSC:
			a2_graph->model()->shiftPhase( 15 );
			Engine::getSong()->setModified();
			break;
		case B1_OSC:
			b1_graph->model()->shiftPhase( 15 );
			Engine::getSong()->setModified();
			break;
		case B2_OSC:
			b2_graph->model()->shiftPhase( 15 );
			Engine::getSong()->setModified();
			break;
	}
}

void WatsynView::loadClicked()
{
	QString fileName;
	switch( m_selectedGraphGroup->model()->value() )
	{
		case A1_OSC:
			a1_graph->model()->setWaveToUser();
			Engine::getSong()->setModified();
			break;
		case A2_OSC:
			a2_graph->model()->setWaveToUser();
			Engine::getSong()->setModified();
			break;
		case B1_OSC:
			b1_graph->model()->setWaveToUser();
			Engine::getSong()->setModified();
			break;
		case B2_OSC:
			b2_graph->model()->setWaveToUser();
			Engine::getSong()->setModified();
			break;
	}
}

void WatsynView::modelChanged()
{
	WatsynInstrument * w = castModel<WatsynInstrument>();

	a1_volKnob -> setModel( &w -> a1_vol );
	a2_volKnob -> setModel( &w -> a2_vol );
	b1_volKnob -> setModel( &w -> b1_vol );
	b2_volKnob -> setModel( &w -> b2_vol );

	a1_panKnob -> setModel( &w -> a1_pan );
	a2_panKnob -> setModel( &w -> a2_pan );
	b1_panKnob -> setModel( &w -> b1_pan );
	b2_panKnob -> setModel( &w -> b2_pan );

	a1_multKnob -> setModel( &w -> a1_mult );
	a2_multKnob -> setModel( &w -> a2_mult );
	b1_multKnob -> setModel( &w -> b1_mult );
	b2_multKnob -> setModel( &w -> b2_mult );

	a1_ltuneKnob -> setModel( &w -> a1_ltune );
	a2_ltuneKnob -> setModel( &w -> a2_ltune );
	b1_ltuneKnob -> setModel( &w -> b1_ltune );
	b2_ltuneKnob -> setModel( &w -> b2_ltune );

	a1_rtuneKnob -> setModel( &w -> a1_rtune );
	a2_rtuneKnob -> setModel( &w -> a2_rtune );
	b1_rtuneKnob -> setModel( &w -> b1_rtune );
	b2_rtuneKnob -> setModel( &w -> b2_rtune );

	m_abmixKnob -> setModel( &w -> m_abmix );

	m_selectedGraphGroup -> setModel( &w -> m_selectedGraph );

	m_aModGroup -> setModel( &w -> m_amod );
	m_bModGroup -> setModel( &w -> m_bmod );

	a1_graph -> setModel( &w -> a1_graph );
	a2_graph -> setModel( &w -> a2_graph );
	b1_graph -> setModel( &w -> b1_graph );
	b2_graph -> setModel( &w -> b2_graph );

	m_envAmtKnob -> setModel( &w -> m_envAmt );
	m_envAttKnob -> setModel( &w -> m_envAtt );
	m_envHoldKnob -> setModel( &w -> m_envHold );
	m_envDecKnob -> setModel( &w -> m_envDec );

	m_xtalkKnob -> setModel( &w -> m_xtalk );
}

extern "C"
{

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

}


code:
/*
 * Watsyn.h - a 4-oscillator modulating wavetable synth
 *
 * 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.
 *
 */

#ifndef WATSYN_H
#define WATSYN_H

#include "Instrument.h"
#include "InstrumentView.h"
#include "Graph.h"
#include "AutomatableModel.h"
#include "AutomatableButton.h"
#include "TempoSyncKnob.h"
#include "NotePlayHandle.h"
#include "PixmapButton.h"
#include <samplerate.h>
#include "MemoryManager.h"

#define makeknob( name, x, y, hint, unit, oname ) 		\
	name = new Knob( knobStyled, this ); 				\
	name ->move( x, y );								\
	name ->setHintText( hint, unit );		\
	name ->setObjectName( oname );						\
	name ->setFixedSize( 19, 19 );

#define maketsknob( name, x, y, hint, unit, oname ) 		\
	name = new TempoSyncKnob( knobStyled, this ); 				\
	name ->move( x, y );								\
	name ->setHintText( hint, unit );		\
	name ->setObjectName( oname );						\
	name ->setFixedSize( 19, 19 );

#define A1ROW 26
#define A2ROW 49
#define B1ROW 72
#define B2ROW 95

const int GRAPHLEN = 220; // don't change - must be same as the size of the widget

const int WAVERATIO = 32; // oversampling ratio

const int WAVELEN = GRAPHLEN * WAVERATIO;
const int PMOD_AMT = WAVELEN / 2;

const int	MOD_MIX = 0;
const int	MOD_AM = 1;
const int	MOD_RM = 2;
const int	MOD_PM = 3;
const int  NUM_MODS = 4;

const int	A1_OSC = 0;
const int	A2_OSC = 1;
const int	B1_OSC = 2;
const int	B2_OSC = 3;
const int	NUM_OSCS = 4;

class WatsynInstrument;

class WatsynObject
{
	MM_OPERATORS
public:
	WatsynObject( 	float * _A1wave, float * _A2wave,
					float * _B1wave, float * _B2wave,
					int _amod, int _bmod, const sample_rate_t _samplerate, NotePlayHandle * _nph, fpp_t _frames,
					WatsynInstrument * _w );
	virtual ~WatsynObject();

	void renderOutput( fpp_t _frames );

	inline sampleFrame * abuf() const
	{
		return m_abuf;
	}
	inline sampleFrame * bbuf() const
	{
		return m_bbuf;
	}
	inline sample_rate_t samplerate() const
	{
		return m_samplerate;
	}

private:
	int m_amod;
	int m_bmod;

	const sample_rate_t m_samplerate;
	NotePlayHandle * m_nph;

	fpp_t m_fpp;

	WatsynInstrument * m_parent;

	sampleFrame * m_abuf;
	sampleFrame * m_bbuf;

	float m_lphase [NUM_OSCS];
	float m_rphase [NUM_OSCS];

	float m_A1wave [WAVELEN];
	float m_A2wave [WAVELEN];
	float m_B1wave [WAVELEN];
	float m_B2wave [WAVELEN];
};

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

	virtual void playNote( NotePlayHandle * _n,
						sampleFrame * _working_buffer );
	virtual void deleteNotePluginData( NotePlayHandle * _n );

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

	virtual QString nodeName() const;

	virtual f_cnt_t desiredReleaseFrames() const
	{
		return( 64 );
	}

	virtual PluginView * instantiateView( QWidget * _parent );

public slots:
	void updateVolumes();
	void updateFreqA1();
	void updateFreqA2();
	void updateFreqB1();
	void updateFreqB2();
	void updateWaveA1();
	void updateWaveA2();
	void updateWaveB1();
	void updateWaveB2();

protected:
	float m_lvol [NUM_OSCS];
    float m_rvol [NUM_OSCS];

	float m_lfreq [NUM_OSCS];
	float m_rfreq [NUM_OSCS];

private:
	inline float leftCh( float _vol, float _pan )
	{
		return ( _pan <= 0 ? 1.0 : 1.0 - ( _pan / 100.0 ) ) * _vol / 100.0;
	}

	inline float rightCh( float _vol, float _pan )
	{
		return ( _pan >= 0 ? 1.0 : 1.0 + ( _pan / 100.0 ) ) * _vol / 100.0;
	}

	// memcpy utilizing libsamplerate (src) for sinc interpolation
	inline void srccpy( float * _dst, float * _src )
	{
		int err;
		const int margin = 64;

		// copy to temp array
		float tmps [ GRAPHLEN + margin ]; // temp array in stack
		float * tmp = &tmps[0];

		memcpy( tmp, _src, sizeof( float ) * GRAPHLEN );
		memcpy( tmp + GRAPHLEN, _src, sizeof( float ) * margin );
		SRC_STATE * src_state = src_new( SRC_SINC_FASTEST, 1, &err );
		SRC_DATA src_data;
		src_data.data_in = tmp;
		src_data.input_frames = GRAPHLEN + margin;
		src_data.data_out = _dst;
		src_data.output_frames = WAVELEN;
		src_data.src_ratio = static_cast<double>( WAVERATIO );
		src_data.end_of_input = 0;
		err = src_process( src_state, &src_data );
		if( err ) { qDebug( "Watsyn SRC error: %s", src_strerror( err ) ); }
		src_delete( src_state );
	}

	// memcpy utilizing cubic interpolation
/*	inline void cipcpy( float * _dst, float * _src )
	{
		// calculate cyclic tangents
		float tang[GRAPHLEN];
		tang[0] = ( _src[1] - _src[ GRAPHLEN - 1] ) / 2;
		tang[ GRAPHLEN - 1 ] = ( _src[0] - _src[ GRAPHLEN - 2 ] ) / 2;
		for( int i = 1; i < GRAPHLEN-1; i++ )
		{
			tang[i] = ( _src[i+1] - _src[i-1] ) / 2;
		}

		// calculate cspline
		for( int i=0; i < WAVELEN; i++ )
		{
			const float s1 = _src[ i / WAVERATIO ];
			const float s2 = _src[ ( i / WAVERATIO + 1 ) % GRAPHLEN ];
			const float m1 = tang[ i / WAVERATIO ];
			const float m2 = tang[ ( i / WAVERATIO + 1 ) % GRAPHLEN ];

			const float x = static_cast<float>( i % WAVERATIO ) / WAVERATIO;
			const float x2 = x * x;
			const float x3 = x * x * x;

			_dst[i] = ( ( x3 * 2.0 - x2 * 3.0 + 1.0 ) * s1 ) +
				( ( x3 * -2.0 + x2 * 3.0 ) * s2 ) +
				( ( x3 - x2 * 2 + x ) * m1 ) +
				( ( x3 - x2 ) * m2 );
		}
	}*/

	FloatModel a1_vol;
	FloatModel a2_vol;
	FloatModel b1_vol;
	FloatModel b2_vol;

	FloatModel a1_pan;
	FloatModel a2_pan;
	FloatModel b1_pan;
	FloatModel b2_pan;

	FloatModel a1_mult;
	FloatModel a2_mult;
	FloatModel b1_mult;
	FloatModel b2_mult;

	FloatModel a1_ltune;
	FloatModel a2_ltune;
	FloatModel b1_ltune;
	FloatModel b2_ltune;

	FloatModel a1_rtune;
	FloatModel a2_rtune;
	FloatModel b1_rtune;
	FloatModel b2_rtune;

	graphModel a1_graph;
	graphModel a2_graph;
	graphModel b1_graph;
	graphModel b2_graph;

	FloatModel m_abmix;

	FloatModel m_envAmt;

	TempoSyncKnobModel m_envAtt;
	TempoSyncKnobModel m_envHold;
	TempoSyncKnobModel m_envDec;

	FloatModel m_xtalk;

	IntModel m_amod;
	IntModel m_bmod;

	IntModel m_selectedGraph;

	float A1_wave [WAVELEN];
	float A2_wave [WAVELEN];
	float B1_wave [WAVELEN];
	float B2_wave [WAVELEN];

	friend class WatsynObject;
	friend class WatsynView;
};

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

protected slots:
	void updateLayout();

	void sinWaveClicked();
	void triWaveClicked();
	void sawWaveClicked();
	void sqrWaveClicked();

	void smoothClicked();
	void normalizeClicked();
	void invertClicked();
	void phaseLeftClicked();
	void phaseRightClicked();
	void loadClicked();

private:
	virtual void modelChanged();

// knobs
	Knob * a1_volKnob;
	Knob * a2_volKnob;
	Knob * b1_volKnob;
	Knob * b2_volKnob;

	Knob * a1_panKnob;
	Knob * a2_panKnob;
	Knob * b1_panKnob;
	Knob * b2_panKnob;

	Knob * a1_multKnob;
	Knob * a2_multKnob;
	Knob * b1_multKnob;
	Knob * b2_multKnob;

	Knob * a1_ltuneKnob;
	Knob * a2_ltuneKnob;
	Knob * b1_ltuneKnob;
	Knob * b2_ltuneKnob;

	Knob * a1_rtuneKnob;
	Knob * a2_rtuneKnob;
	Knob * b1_rtuneKnob;
	Knob * b2_rtuneKnob;

	Knob * m_abmixKnob;

	Knob * m_envAmtKnob;

	TempoSyncKnob * m_envAttKnob;
	TempoSyncKnob * m_envHoldKnob;
	TempoSyncKnob * m_envDecKnob;

	Knob * m_xtalkKnob;

	automatableButtonGroup * m_selectedGraphGroup;
	automatableButtonGroup * m_aModGroup;
	automatableButtonGroup * m_bModGroup;

	Graph * a1_graph;
	Graph * a2_graph;
	Graph * b1_graph;
	Graph * b2_graph;

	PixmapButton * m_sinWaveButton;
	PixmapButton * m_triWaveButton;
	PixmapButton * m_sawWaveButton;
	PixmapButton * m_sqrWaveButton;
	PixmapButton * m_normalizeButton;
	PixmapButton * m_invertButton;
	PixmapButton * m_smoothButton;
	PixmapButton * m_phaseLeftButton;
	PixmapButton * m_phaseRightButton;
	PixmapButton * m_loadButton;

};

#endif

repo_name: CYBERDEViL/LMMS

license: mit


code:
INCLUDE(BuildPlugin)

BUILD_PLUGIN(waveshaper waveshaper.cpp waveshaper_controls.cpp waveshaper_control_dialog.cpp MOCFILES waveshaper_controls.h waveshaper_control_dialog.h EMBEDDED_RESOURCES *.png)


code:
/*
 * waveshaper.cpp - waveshaper effect-plugin
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * 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.
 *
 */

#include "waveshaper.h"
#include "lmms_math.h"
#include "embed.h"
#include "interpolation.h"

#include "plugin_export.h"

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT waveshaper_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"Waveshaper Effect",
	QT_TRANSLATE_NOOP( "PluginBrowser",
				"plugin for waveshaping" ),
	"Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>",
	0x0100,
	Plugin::Effect,
	new PluginPixmapLoader("logo"),
	NULL,
	NULL
} ;

}

waveShaperEffect::waveShaperEffect( Model * _parent,
			const Descriptor::SubPluginFeatures::Key * _key ) :
	Effect( &waveshaper_plugin_descriptor, _parent, _key ),
	m_wsControls( this )
{
}

waveShaperEffect::~waveShaperEffect()
{
}

bool waveShaperEffect::processAudioBuffer( sampleFrame * _buf,
							const fpp_t _frames )
{
	if( !isEnabled() || !isRunning () )
	{
		return( false );
	}

// variables for effect
	int i = 0;

	double out_sum = 0.0;
	const float d = dryLevel();
	const float w = wetLevel();
	float input = m_wsControls.m_inputModel.value();
	float output = m_wsControls.m_outputModel.value();
	const float * samples = m_wsControls.m_wavegraphModel.samples();
	const bool clip = m_wsControls.m_clipModel.value();

	ValueBuffer *inputBuffer = m_wsControls.m_inputModel.valueBuffer();
	ValueBuffer *outputBufer = m_wsControls.m_outputModel.valueBuffer();

	int inputInc = inputBuffer ? 1 : 0;
	int outputInc = outputBufer ? 1 : 0;

	const float *inputPtr = inputBuffer ? &( inputBuffer->values()[ 0 ] ) : &input;
	const float *outputPtr = outputBufer ? &( outputBufer->values()[ 0 ] ) : &output;

	for( fpp_t f = 0; f < _frames; ++f )
	{
		float s[2] = { _buf[f][0], _buf[f][1] };

// apply input gain
		s[0] *= *inputPtr;
		s[1] *= *inputPtr;

// clip if clip enabled
		if( clip )
		{
			s[0] = qBound( -1.0f, s[0], 1.0f );
			s[1] = qBound( -1.0f, s[1], 1.0f );
		}

// start effect

		for( i=0; i <= 1; ++i )
		{
			const int lookup = static_cast<int>( qAbs( s[i] ) * 200.0f );
			const float frac = fraction( qAbs( s[i] ) * 200.0f );
			const float posneg = s[i] < 0 ? -1.0f : 1.0f;

			if( lookup < 1 )
			{
				s[i] = frac * samples[0] * posneg;
			}
			else if( lookup < 200 )
			{
				s[i] = linearInterpolate( samples[ lookup - 1 ],
						samples[ lookup ], frac )
						* posneg;
			}
			else
			{
				s[i] *= samples[199];
			}
		}

// apply output gain
		s[0] *= *outputPtr;
		s[1] *= *outputPtr;

// mix wet/dry signals
		_buf[f][0] = d * _buf[f][0] + w * s[0];
		_buf[f][1] = d * _buf[f][1] + w * s[1];
		out_sum += _buf[f][0] * _buf[f][0] + _buf[f][1] * _buf[f][1];

		outputPtr += outputInc;
		inputPtr += inputInc;
	}

	checkGate( out_sum / _frames );

	return( isRunning() );
}

extern "C"
{

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

}


code:
/*
 * waveshaper.h - waveshaper effect-plugin
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2006-2008 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 _WAVESHAPER_H
#define _WAVESHAPER_H

#include "Effect.h"
#include "waveshaper_controls.h"

class waveShaperEffect : public Effect
{
public:
	waveShaperEffect( Model * _parent,
			const Descriptor::SubPluginFeatures::Key * _key );
	virtual ~waveShaperEffect();
	virtual bool processAudioBuffer( sampleFrame * _buf,
							const fpp_t _frames );

	virtual EffectControls * controls()
	{
		return( &m_wsControls );
	}

private:

	waveShaperControls m_wsControls;

	friend class waveShaperControls;

} ;

#endif


code:
/*
 * waveshaper_control_dialog.cpp - control-dialog for waveshaper-effect
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2006-2008 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 <QLayout>

#include "waveshaper_control_dialog.h"
#include "waveshaper_controls.h"
#include "embed.h"
#include "Graph.h"
#include "PixmapButton.h"
#include "ToolTip.h"
#include "LedCheckbox.h"

waveShaperControlDialog::waveShaperControlDialog(
					waveShaperControls * _controls ) :
	EffectControlDialog( _controls )
{
	setAutoFillBackground( true );
	QPalette pal;
	pal.setBrush( backgroundRole(),
				PLUGIN_NAME::getIconPixmap( "artwork" ) );
	setPalette( pal );
	setFixedSize( 224, 274 );

	Graph * waveGraph = new Graph( this, Graph::LinearNonCyclicStyle, 204, 205 );
	waveGraph -> move( 10, 6 );
	waveGraph -> setModel( &_controls -> m_wavegraphModel );
	waveGraph -> setAutoFillBackground( true );
	pal = QPalette();
	pal.setBrush( backgroundRole(),
			PLUGIN_NAME::getIconPixmap("wavegraph") );
	waveGraph->setPalette( pal );
	waveGraph->setGraphColor( QColor( 85, 204, 145 ) );
	waveGraph -> setMaximumSize( 204, 205 );

	Knob * inputKnob = new Knob( knobBright_26, this);
	inputKnob -> setVolumeKnob( true );
	inputKnob -> setVolumeRatio( 1.0 );
	inputKnob -> move( 26, 225 );
	inputKnob->setModel( &_controls->m_inputModel );
	inputKnob->setLabel( tr( "INPUT" ) );
	inputKnob->setHintText( tr( "Input gain:" ) , "" );

	Knob * outputKnob = new Knob( knobBright_26, this );
	outputKnob -> setVolumeKnob( true );
	outputKnob -> setVolumeRatio( 1.0 );
	outputKnob -> move( 76, 225 );
	outputKnob->setModel( &_controls->m_outputModel );
	outputKnob->setLabel( tr( "OUTPUT" ) );
	outputKnob->setHintText( tr( "Output gain:" ), "" );

	PixmapButton * resetButton = new PixmapButton( this, tr("Reset wavegraph") );
	resetButton -> move( 162, 221 );
	resetButton -> resize( 13, 46 );
	resetButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "reset_active" ) );
	resetButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "reset_inactive" ) );
	ToolTip::add( resetButton, tr( "Reset wavegraph" ) );

	PixmapButton * smoothButton = new PixmapButton( this, tr("Smooth wavegraph") );
	smoothButton -> move( 162, 237 );
	smoothButton -> resize( 13, 46 );
	smoothButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "smooth_active" ) );
	smoothButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "smooth_inactive" ) );
	ToolTip::add( smoothButton, tr( "Smooth wavegraph" ) );

	PixmapButton * addOneButton = new PixmapButton( this, tr("Increase wavegraph amplitude by 1 dB") );
	addOneButton -> move( 131, 221 );
	addOneButton -> resize( 13, 29 );
	addOneButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "add1_active" ) );
	addOneButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "add1_inactive" ) );
	ToolTip::add( addOneButton, tr( "Increase wavegraph amplitude by 1 dB" ) );

	PixmapButton * subOneButton = new PixmapButton( this, tr("Decrease wavegraph amplitude by 1 dB") );
	subOneButton -> move( 131, 237 );
	subOneButton -> resize( 13, 29 );
	subOneButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "sub1_active" ) );
	subOneButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "sub1_inactive" ) );
	ToolTip::add( subOneButton, tr( "Decrease wavegraph amplitude by 1 dB" ) );

	LedCheckBox * clipInputToggle = new LedCheckBox( "Clip input", this,
							tr( "Clip input" ), LedCheckBox::Green );
	clipInputToggle -> move( 131, 252 );
	clipInputToggle -> setModel( &_controls -> m_clipModel );
	ToolTip::add( clipInputToggle, tr( "Clip input signal to 0 dB" ) );

	connect( resetButton, SIGNAL (clicked () ),
			_controls, SLOT ( resetClicked() ) );
	connect( smoothButton, SIGNAL (clicked () ),
			_controls, SLOT ( smoothClicked() ) );
	connect( addOneButton, SIGNAL( clicked() ),
			_controls, SLOT( addOneClicked() ) );
	connect( subOneButton, SIGNAL( clicked() ),
			_controls, SLOT( subOneClicked() ) );
}


code:
/*
 * waveshaper_control_dialog.h - control-dialog for waveshaper-effect
 *
 * * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2006-2008 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 _WAVESHAPER_CONTROL_DIALOG_H
#define _WAVESHAPER_CONTROL_DIALOG_H

#include "EffectControlDialog.h"

class waveShaperControls;

class waveShaperControlDialog : public EffectControlDialog
{
	Q_OBJECT
public:
	waveShaperControlDialog( waveShaperControls * _controls );
	virtual ~waveShaperControlDialog()
	{
	}

private:

} ;

#endif


code:
/*
 * waveshaper_controls.cpp - controls for waveshaper-effect
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2008 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 "waveshaper_controls.h"
#include "waveshaper.h"
#include "base64.h"
#include "Graph.h"
#include "Engine.h"
#include "Song.h"

#define onedB 1.1220184543019633f

waveShaperControls::waveShaperControls( waveShaperEffect * _eff ) :
	EffectControls( _eff ),
	m_effect( _eff ),
	m_inputModel( 1.0f, 0.0f, 5.0f, 0.01f, this, tr( "Input gain" ) ),
	m_outputModel( 1.0f, 0.0f, 5.0f, 0.01f, this, tr( "Output gain" ) ),
	m_wavegraphModel( 0.0f, 1.0f, 200, this ),
	m_clipModel( false, this )
{
	connect( &m_wavegraphModel, SIGNAL( samplesChanged( int, int ) ),
			this, SLOT( samplesChanged( int, int ) ) );

	setDefaultShape();
}

void waveShaperControls::samplesChanged( int _begin, int _end)
{
	Engine::getSong()->setModified();
}

void waveShaperControls::loadSettings( const QDomElement & _this )
{
//load input, output knobs
	m_inputModel.loadSettings( _this, "inputGain" );
	m_outputModel.loadSettings( _this, "outputGain" );

	m_clipModel.loadSettings( _this, "clipInput" );

//load waveshape
	int size = 0;
	char * dst = 0;
	base64::decode( _this.attribute( "waveShape"), &dst, &size );

	m_wavegraphModel.setSamples( (float*) dst );
	delete[] dst;

}

void waveShaperControls::saveSettings( QDomDocument & _doc,
							QDomElement & _this )
{
//save input, output knobs
	m_inputModel.saveSettings( _doc, _this, "inputGain" );
	m_outputModel.saveSettings( _doc, _this, "outputGain" );

	m_clipModel.saveSettings( _doc, _this, "clipInput" );

//save waveshape
	QString sampleString;
	base64::encode( (const char *)m_wavegraphModel.samples(),
		m_wavegraphModel.length() * sizeof(float), sampleString );
	_this.setAttribute( "waveShape", sampleString );

}

void waveShaperControls::setDefaultShape()
{
	float shp [200] = { };
	for ( int i = 0; i<200; i++)
	{
		shp[i] = ((float)i + 1.0f) / 200.0f;
	}

	m_wavegraphModel.setLength( 200 );
	m_wavegraphModel.setSamples( (float*)&shp );
}

void waveShaperControls::resetClicked()
{
	setDefaultShape();
	Engine::getSong()->setModified();
}

void waveShaperControls::smoothClicked()
{
	m_wavegraphModel.smoothNonCyclic();
	Engine::getSong()->setModified();
}

void waveShaperControls::addOneClicked()
{
	for( int i=0; i<200; i++ )
	{
		m_wavegraphModel.setSampleAt( i, qBound( 0.0f, m_wavegraphModel.samples()[i] * onedB, 1.0f ) );
	}
	Engine::getSong()->setModified();
}

void waveShaperControls::subOneClicked()
{
	for( int i=0; i<200; i++ )
	{
		m_wavegraphModel.setSampleAt( i, qBound( 0.0f, m_wavegraphModel.samples()[i] / onedB, 1.0f ) );
	}
	Engine::getSong()->setModified();
}


code:
/*
 * waveshaper_controls.h - controls for waveshaper-effect
 *
 * Copyright (c) 2014 Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>
 * Copyright (c) 2008 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 WAVESHAPER_CONTROLS_H
#define WAVESHAPER_CONTROLS_H

#include "EffectControls.h"
#include "waveshaper_control_dialog.h"
#include "Knob.h"
#include "Graph.h"

class waveShaperEffect;

class waveShaperControls : public EffectControls
{
	Q_OBJECT
public:
	waveShaperControls( waveShaperEffect * _eff );
	virtual ~waveShaperControls()
	{
	}

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

	virtual void setDefaultShape();

	virtual int controlCount()
	{
		return( 4 );
	}

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

private slots:
	void samplesChanged( int, int );

	void resetClicked();
	void smoothClicked();

	void addOneClicked();
	void subOneClicked();

private:
	waveShaperEffect * m_effect;
	FloatModel m_inputModel;
	FloatModel m_outputModel;
	graphModel m_wavegraphModel;
	BoolModel  m_clipModel;

	friend class waveShaperControlDialog;
	friend class waveShaperEffect;

} ;

#endif


code:
INCLUDE(BuildPlugin)

set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# definitions for ZynAddSubFX
IF(LMMS_BUILD_LINUX OR LMMS_BUILD_APPLE OR LMMS_BUILD_OPENBSD OR LMMS_BUILD_FREEBSD)
	FIND_PACKAGE(X11)
	INCLUDE_DIRECTORIES(${X11_INCLUDE_DIR})
	ADD_DEFINITIONS(-DOS_LINUX)
ELSE()
	ADD_DEFINITIONS(-DOS_WINDOWS)
ENDIF()

# do not conflict with LMMS' Controller class
ADD_DEFINITIONS(-DController=ZynController)

# use asm optimizations when on x86 or x86_64
IF(LMMS_HOST_X86 OR LMMS_HOST_X86_64)
	ADD_DEFINITIONS(-DASM_F2I_YES)
ENDIF(LMMS_HOST_X86 OR LMMS_HOST_X86_64)

# build ZynAddSubFX with full optimizations
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -Wno-write-strings -Wno-deprecated-declarations -fpermissive")

IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND NOT "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "6.0.0")
	SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-misleading-indentation")
ENDIF()

IF(LMMS_BUILD_WIN32)
	# link system-libraries
	ADD_DEFINITIONS(-DPTW32_STATIC_LIB)
	# fix X11 headers errors caused by bug with mingw + c++11 (enable -std=gnu++0x)
	SET(CMAKE_CXX_EXTENSIONS ON)
ENDIF(LMMS_BUILD_WIN32)

SET(FLTK_SKIP_OPENGL TRUE)
SET(FLTK_SKIP_FORMS TRUE)
SET(FLTK_SKIP_IMAGES TRUE)
SET(FLTK_SKIP_MATH TRUE)
IF(MINGW_PREFIX)
	SET(FLTK_SKIP_FLUID TRUE)
ENDIF()

IF(NOT FLTK_FOUND)
	RETURN()
ENDIF()

IF(MINGW_PREFIX)
	SET(FLTK_FLUID_EXECUTABLE "${MINGW_PREFIX}/bin/fluid")
ENDIF()

IF(NOT EXISTS ${FLTK_FLUID_EXECUTABLE})
    # Try to detect full path anyway
    EXECUTE_PROCESS(COMMAND which fluid OUTPUT_VARIABLE FLTK_FLUID_EXECUTABLE OUTPUT_STRIP_TRAILING_WHITESPACE)
    IF(NOT EXISTS ${FLTK_FLUID_EXECUTABLE})
        MESSAGE(FATAL_ERROR "Please install the fluid binary which is part of FLTK.")
    ENDIF()
ENDIF()

INCLUDE_DIRECTORIES("${FLTK_INCLUDE_DIR}"
                    "${CMAKE_CURRENT_SOURCE_DIR}"
                    ${FFTW3F_INCLUDE_DIRS}
                    "${CMAKE_CURRENT_BINARY_DIR}"
                    "${CMAKE_BINARY_DIR}")

include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/zynaddsubfx/src/UI)

ADD_DEFINITIONS(-DPLUGINVERSION)	# removes exit confirmation dialogs etc. in MasterUI.fl
add_subdirectory(zynaddsubfx/src/Nio)
add_subdirectory(zynaddsubfx/src/UI)

SET(zynaddsubfx_core_SRCS
    zynaddsubfx/src/DSP/AnalogFilter.cpp
    zynaddsubfx/src/DSP/FFTwrapper.cpp
    zynaddsubfx/src/DSP/Filter.cpp
    zynaddsubfx/src/DSP/FormantFilter.cpp
    zynaddsubfx/src/DSP/SVFilter.cpp
    zynaddsubfx/src/DSP/Unison.cpp
    zynaddsubfx/src/Effects/Alienwah.cpp
	zynaddsubfx/src/Effects/Chorus.cpp
	zynaddsubfx/src/Effects/Distorsion.cpp
	zynaddsubfx/src/Effects/DynamicFilter.cpp
	zynaddsubfx/src/Effects/Echo.cpp
	zynaddsubfx/src/Effects/Effect.cpp
	zynaddsubfx/src/Effects/EffectLFO.cpp
	zynaddsubfx/src/Effects/EffectMgr.cpp
	zynaddsubfx/src/Effects/EQ.cpp
	zynaddsubfx/src/Effects/Phaser.cpp
	zynaddsubfx/src/Effects/Reverb.cpp
	zynaddsubfx/src/Misc/Bank.cpp
	zynaddsubfx/src/Misc/Config.cpp
	zynaddsubfx/src/Misc/Dump.cpp
	zynaddsubfx/src/Misc/Master.cpp
	zynaddsubfx/src/Misc/Microtonal.cpp
	zynaddsubfx/src/Misc/Part.cpp
	zynaddsubfx/src/Misc/Util.cpp
	zynaddsubfx/src/Misc/QtXmlWrapper.cpp
	zynaddsubfx/src/Misc/Recorder.cpp
	zynaddsubfx/src/Misc/WavFile.cpp
	zynaddsubfx/src/Misc/WaveShapeSmps.cpp
	zynaddsubfx/src/Params/ADnoteParameters.cpp
	zynaddsubfx/src/Params/Controller.cpp
	zynaddsubfx/src/Params/EnvelopeParams.cpp
	zynaddsubfx/src/Params/FilterParams.cpp
	zynaddsubfx/src/Params/LFOParams.cpp
	zynaddsubfx/src/Params/PADnoteParameters.cpp
	zynaddsubfx/src/Params/Presets.cpp
	zynaddsubfx/src/Params/PresetsArray.cpp
	zynaddsubfx/src/Params/PresetsStore.cpp
	zynaddsubfx/src/Params/SUBnoteParameters.cpp
	zynaddsubfx/src/Synth/SynthNote.cpp
	zynaddsubfx/src/Synth/ADnote.cpp
	zynaddsubfx/src/Synth/Envelope.cpp
	zynaddsubfx/src/Synth/LFO.cpp
	zynaddsubfx/src/Synth/OscilGen.cpp
	zynaddsubfx/src/Synth/PADnote.cpp
	zynaddsubfx/src/Synth/Resonance.cpp
	zynaddsubfx/src/Synth/SUBnote.cpp
    )

IF(LMMS_BUILD_LINUX)
	ADD_LIBRARY(ZynAddSubFxCore MODULE LocalZynAddSubFx.cpp ${zynaddsubfx_core_SRCS})
ELSE()
	ADD_LIBRARY(ZynAddSubFxCore SHARED LocalZynAddSubFx.cpp ${zynaddsubfx_core_SRCS})
ENDIF()
TARGET_LINK_LIBRARIES(ZynAddSubFxCore zynaddsubfx_nio ${FFTW3F_LIBRARIES} ${QT_LIBRARIES} -lz -lpthread)

TARGET_LINK_LIBRARIES(ZynAddSubFxCore Qt5::Widgets Qt5::Xml)

IF(LMMS_BUILD_WIN32)
	TARGET_LINK_LIBRARIES(ZynAddSubFxCore -lws2_32)
	INSTALL(TARGETS ZynAddSubFxCore RUNTIME DESTINATION "${PLUGIN_DIR}")
ELSE(LMMS_BUILD_WIN32)
	INSTALL(TARGETS ZynAddSubFxCore LIBRARY DESTINATION "${PLUGIN_DIR}")
ENDIF(LMMS_BUILD_WIN32)

LINK_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}/..")
IF(LMMS_BUILD_LINUX)
	LINK_LIBRARIES(-Wl,--enable-new-dtags)
	SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
	SET(CMAKE_INSTALL_RPATH "$ORIGIN")
ELSE()
	SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${PLUGIN_DIR}")
ENDIF()
BUILD_PLUGIN(zynaddsubfx ZynAddSubFx.cpp ZynAddSubFx.h MOCFILES ZynAddSubFx.h EMBEDDED_RESOURCES artwork.png logo.png)
TARGET_LINK_LIBRARIES(zynaddsubfx -lZynAddSubFxCore)
ADD_DEPENDENCIES(zynaddsubfx ZynAddSubFxCore)

IF(WIN32)
	SET(WINRC "${CMAKE_CURRENT_BINARY_DIR}/zynaddsubfxrc.obj")
	ADD_CUSTOM_COMMAND(OUTPUT "${WINRC}"
				COMMAND "${CMAKE_RC_COMPILER}"
					"-I\"${CMAKE_CURRENT_SOURCE_DIR}\""
					"-o\"${CMAKE_CURRENT_BINARY_DIR}/zynaddsubfxrc.obj\""
					"-i\"${CMAKE_CURRENT_BINARY_DIR}/zynaddsubfx.rc\""
				DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/zynaddsubfx.rc")
ENDIF(WIN32)

# Use libraries in non-standard directories (e.g., another version of Qt)
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

ADD_EXECUTABLE(RemoteZynAddSubFx RemoteZynAddSubFx.cpp "${WINRC}")
INSTALL(TARGETS RemoteZynAddSubFx RUNTIME DESTINATION "${PLUGIN_DIR}")
# Needed to deploy dependencies of RemoteZynAddSubFx
SET_PROPERTY(GLOBAL APPEND PROPERTY PLUGINS_BUILT "RemoteZynAddSubFx")

IF(LMMS_BUILD_WIN32)
	SET_TARGET_PROPERTIES(RemoteZynAddSubFx PROPERTIES LINK_FLAGS "${LINK_FLAGS} -mwindows")
ENDIF(LMMS_BUILD_WIN32)

# Remove useless dependencies from FLTK. Use fltk-config to avoid static library
# in older environments
SET(FLTK_FILTERED_LDFLAGS ${FLTK_LIBRARIES})
FIND_PROGRAM(FLTK_CONFIG fltk-config)
IF(FLTK_CONFIG AND NOT (LMMS_BUILD_APPLE OR LMMS_BUILD_WIN32))
	EXECUTE_PROCESS(COMMAND ${FLTK_CONFIG} --ldflags OUTPUT_VARIABLE FLTK_FILTERED_LDFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
	STRING(REPLACE " " ";" FLTK_FILTERED_LDFLAGS ${FLTK_FILTERED_LDFLAGS})
	LIST(REMOVE_ITEM FLTK_FILTERED_LDFLAGS -lX11)
ENDIF()
TARGET_LINK_LIBRARIES(RemoteZynAddSubFx zynaddsubfx_gui -lZynAddSubFxCore ${FLTK_FILTERED_LDFLAGS} -lpthread )
ADD_DEPENDENCIES(RemoteZynAddSubFx ZynAddSubFxCore)

# Support qt_version_tag in Qt 5.6
TARGET_LINK_LIBRARIES(RemoteZynAddSubFx Qt5::Core)

# link Qt libraries when on win32
IF(LMMS_BUILD_WIN32)
	TARGET_LINK_LIBRARIES(RemoteZynAddSubFx ${QT_LIBRARIES})
ENDIF(LMMS_BUILD_WIN32)

IF(LMMS_BUILD_WIN32)
	ADD_CUSTOM_COMMAND(TARGET ZynAddSubFxCore POST_BUILD COMMAND "${STRIP}" \"$<TARGET_FILE:ZynAddSubFxCore>\")
	ADD_CUSTOM_COMMAND(TARGET RemoteZynAddSubFx POST_BUILD COMMAND "${STRIP}" \"$<TARGET_FILE:RemoteZynAddSubFx>\")
ENDIF(LMMS_BUILD_WIN32)


code:
/*
 * LocalZynAddSubFx.cpp - local implementation of ZynAddSubFx plugin
 *
 * 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 <lmmsconfig.h>

#include "zynaddsubfx/src/Misc/Util.h"
#include <unistd.h>
#include <ctime>

#include "LocalZynAddSubFx.h"

#include "zynaddsubfx/src/Nio/NulEngine.h"
#include "zynaddsubfx/src/Misc/Master.h"
#include "zynaddsubfx/src/Misc/Part.h"
#include "zynaddsubfx/src/Misc/Dump.h"

SYNTH_T* synth = NULL;

int LocalZynAddSubFx::s_instanceCount = 0;

LocalZynAddSubFx::LocalZynAddSubFx() :
	m_master( NULL ),
	m_ioEngine( NULL )
{
	for( int i = 0; i < NumKeys; ++i )
	{
		m_runningNotes[i] = 0;
	}

	if( s_instanceCount == 0 )
	{
#ifdef LMMS_BUILD_WIN32
#ifndef __WINPTHREADS_VERSION
		// (non-portable) initialization of statically linked pthread library
		pthread_win32_process_attach_np();
		pthread_win32_thread_attach_np();
#endif
#endif

		initConfig();

		synth = new SYNTH_T;
		synth->oscilsize = config.cfg.OscilSize;
		synth->alias();

		srand( time( NULL ) );

		denormalkillbuf = new float[synth->buffersize];
		for( int i = 0; i < synth->buffersize; ++i )
		{
			denormalkillbuf[i] = (RND-0.5)*1e-16;
		}
	}

	++s_instanceCount;

	m_ioEngine = new NulEngine;

	m_master = new Master();
	m_master->swaplr = 0;
}

LocalZynAddSubFx::~LocalZynAddSubFx()
{
	delete m_master;
	delete m_ioEngine;

	if( --s_instanceCount == 0 )
	{
		delete[] denormalkillbuf;
	}
}

void LocalZynAddSubFx::initConfig()
{
	config.init();

	config.cfg.GzipCompression = 0;
}

void LocalZynAddSubFx::setSampleRate( int sampleRate )
{
	synth->samplerate = sampleRate;
	synth->alias();
}

void LocalZynAddSubFx::setBufferSize( int bufferSize )
{
	synth->buffersize = bufferSize;
	synth->alias();
}

void LocalZynAddSubFx::saveXML( const std::string & _filename )
{
	char * name = strdup( _filename.c_str() );
	m_master->saveXML( name );
	free( name );
}

void LocalZynAddSubFx::loadXML( const std::string & _filename )
{
	char * f = strdup( _filename.c_str() );

	pthread_mutex_lock( &m_master->mutex );
	m_master->defaults();
	m_master->loadXML( f );
	pthread_mutex_unlock( &m_master->mutex );

	m_master->applyparameters();

	unlink( f );
	free( f );
}

void LocalZynAddSubFx::loadPreset( const std::string & _filename, int _part )
{
	char * f = strdup( _filename.c_str() );

	pthread_mutex_lock( &m_master->mutex );
	m_master->part[_part]->defaultsinstrument();
	m_master->part[_part]->loadXMLinstrument( f );
	pthread_mutex_unlock( &m_master->mutex );

	m_master->applyparameters();

	free( f );
}

void LocalZynAddSubFx::setPresetDir( const std::string & _dir )
{
	m_presetsDir = _dir;
	for( int i = 0; i < MAX_BANK_ROOT_DIRS; ++i )
	{
		if( config.cfg.bankRootDirList[i].empty() )
		{
			config.cfg.bankRootDirList[i] = m_presetsDir;
			break;
		}
		else if( config.cfg.bankRootDirList[i] == m_presetsDir )
		{
			break;
		}
	}
}

void LocalZynAddSubFx::setLmmsWorkingDir( const std::string & _dir )
{
	if( config.workingDir != NULL )
	{
		free( config.workingDir );
	}
	config.workingDir = strdup( _dir.c_str() );

	initConfig();
}

void LocalZynAddSubFx::setPitchWheelBendRange( int semitones )
{
	for( int i = 0; i < NUM_MIDI_PARTS; ++i )
	{
		m_master->part[i]->ctl.setpitchwheelbendrange( semitones * 100 );
	}
}

void LocalZynAddSubFx::processMidiEvent( const MidiEvent& event )
{
	switch( event.type() )
	{
		case MidiNoteOn:
			if( event.velocity() > 0 )
			{
				if( event.key() < 0 || event.key() > MidiMaxKey )
				{
					break;
				}
				if( m_runningNotes[event.key()] > 0 )
				{
					m_master->noteOff( event.channel(), event.key() );
				}
				++m_runningNotes[event.key()];
				m_master->noteOn( event.channel(), event.key(), event.velocity() );
				break;
			}
		case MidiNoteOff:
			if( event.key() < 0 || event.key() > MidiMaxKey )
			{
				break;
			}
			if( --m_runningNotes[event.key()] <= 0 )
			{
				m_master->noteOff( event.channel(), event.key() );
			}
			break;
		case MidiPitchBend:
			m_master->setController( event.channel(), C_pitchwheel, event.pitchBend()-8192 );
			break;
		case MidiControlChange:
			m_master->setController( event.channel(), event.controllerNumber(), event.controllerValue() );
			break;
		default:
			break;
	}
}

void LocalZynAddSubFx::processAudio( sampleFrame * _out )
{
	float outputl[synth->buffersize];
	float outputr[synth->buffersize];

	m_master->GetAudioOutSamples( synth->buffersize, synth->samplerate, outputl, outputr );

	// TODO: move to MixHelpers
	for( int f = 0; f < synth->buffersize; ++f )
	{
		_out[f][0] = outputl[f];
		_out[f][1] = outputr[f];
	}
}


code:
/*
 * LocalZynAddSubFx.h - local implementation of ZynAddSubFx plugin
 *
 * 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.
 *
 */

#ifndef LOCAL_ZYNADDSUBFX_H
#define LOCAL_ZYNADDSUBFX_H

#include "MidiEvent.h"
#include "Note.h"

class Master;
class NulEngine;

class LocalZynAddSubFx
{
public:
	LocalZynAddSubFx();
	~LocalZynAddSubFx();

	void initConfig();

	void setSampleRate( int _sampleRate );
	void setBufferSize( int _bufferSize );

	void saveXML( const std::string & _filename );
	void loadXML( const std::string & _filename );

	void loadPreset( const std::string & _filename, int _part = 0 );

	void setPresetDir( const std::string & _dir );
	void setLmmsWorkingDir( const std::string & _dir );

	void setPitchWheelBendRange( int semitones );

	void processMidiEvent( const MidiEvent& event );

	void processAudio( sampleFrame * _out );

	inline Master * master()
	{
		return m_master;
	}

protected:
	static int s_instanceCount;

	std::string m_presetsDir;

	int m_runningNotes[NumKeys];
	Master * m_master;
	NulEngine* m_ioEngine;

} ;

#endif


code:
/*
 * RemoteZynAddSubFx.cpp - ZynAddSubFx-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>
#ifdef LMMS_BUILD_WIN32
#include <winsock2.h>
#endif

#include <queue>

#define BUILD_REMOTE_PLUGIN_CLIENT
#include "Note.h"
#include "RemotePlugin.h"
#include "RemoteZynAddSubFx.h"
#include "LocalZynAddSubFx.h"

#include "zynaddsubfx/src/Nio/Nio.h"
#include "zynaddsubfx/src/UI/MasterUI.h"

#include <FL/x.H>

class RemoteZynAddSubFx : public RemotePluginClient, public LocalZynAddSubFx
{
public:
#ifdef SYNC_WITH_SHM_FIFO
	RemoteZynAddSubFx( int _shm_in, int _shm_out ) :
		RemotePluginClient( _shm_in, _shm_out ),
#else
	RemoteZynAddSubFx( const char * socketPath ) :
		RemotePluginClient( socketPath ),
#endif
		LocalZynAddSubFx(),
		m_guiSleepTime( 100 ),
		m_guiExit( false )
	{
		Nio::start();

		setInputCount( 0 );
		sendMessage( IdInitDone );
		waitForMessage( IdInitDone );

		pthread_mutex_init( &m_guiMutex, NULL );
		pthread_create( &m_messageThreadHandle, NULL, messageLoop, this );
	}

	virtual ~RemoteZynAddSubFx()
	{
		Nio::stop();
	}

	virtual void updateSampleRate()
	{
		LocalZynAddSubFx::setSampleRate( sampleRate() );
	}

	virtual void updateBufferSize()
	{
		LocalZynAddSubFx::setBufferSize( bufferSize() );
	}

	void messageLoop()
	{
		message m;
		while( ( m = receiveMessage() ).id != IdQuit )
		{
			pthread_mutex_lock( &m_master->mutex );
			processMessage( m );
			pthread_mutex_unlock( &m_master->mutex );
		}
		m_guiExit = true;
	}

	virtual bool processMessage( const message & _m )
	{
		switch( _m.id )
		{
			case IdQuit:
				break;

			case IdShowUI:
			case IdHideUI:
			case IdLoadSettingsFromFile:
			case IdLoadPresetFile:
				pthread_mutex_lock( &m_guiMutex );
				m_guiMessages.push( _m );
				pthread_mutex_unlock( &m_guiMutex );
				break;

			case IdSaveSettingsToFile:
			{
				LocalZynAddSubFx::saveXML( _m.getString() );
				sendMessage( IdSaveSettingsToFile );
				break;
			}

			case IdZasfPresetDirectory:
				LocalZynAddSubFx::setPresetDir( _m.getString() );
				break;

			case IdZasfLmmsWorkingDirectory:
				LocalZynAddSubFx::setLmmsWorkingDir( _m.getString() );
				break;

			case IdZasfSetPitchWheelBendRange:
				LocalZynAddSubFx::setPitchWheelBendRange( _m.getInt() );
				break;

			default:
				return RemotePluginClient::processMessage( _m );
		}
		return true;
	}

	// all functions are called while m_master->mutex is held
	virtual void processMidiEvent( const MidiEvent& event, const f_cnt_t /* _offset */ )
	{
		LocalZynAddSubFx::processMidiEvent( event );
	}

	virtual void process( const sampleFrame * _in, sampleFrame * _out )
	{
		LocalZynAddSubFx::processAudio( _out );
	}

	static void * messageLoop( void * _arg )
	{
		RemoteZynAddSubFx * _this =
					static_cast<RemoteZynAddSubFx *>( _arg );

		_this->messageLoop();

		return NULL;
	}

	void guiLoop();

private:
	const int m_guiSleepTime;

	pthread_t m_messageThreadHandle;
	pthread_mutex_t m_guiMutex;
	std::queue<RemotePluginClient::message> m_guiMessages;
	bool m_guiExit;

} ;

void RemoteZynAddSubFx::guiLoop()
{
	int exitProgram = 0;
	MasterUI * ui = NULL;

	while( !m_guiExit )
	{
		if( ui )
		{
			Fl::wait( m_guiSleepTime / 1000.0 );
		}
		else
		{
#ifdef LMMS_BUILD_WIN32
			Sleep( m_guiSleepTime );
#else
			usleep( m_guiSleepTime*1000 );
#endif
		}
		if( exitProgram == 1 )
		{
			pthread_mutex_lock( &m_master->mutex );
			sendMessage( IdHideUI );
			exitProgram = 0;
			pthread_mutex_unlock( &m_master->mutex );
		}
		pthread_mutex_lock( &m_guiMutex );
		while( m_guiMessages.size() )
		{
			RemotePluginClient::message m = m_guiMessages.front();
			m_guiMessages.pop();
			switch( m.id )
			{
				case IdShowUI:
					// we only create GUI
					if( !ui )
					{
						Fl::scheme( "plastic" );
						ui = new MasterUI( m_master, &exitProgram );
					}
					ui->showUI();
					ui->refresh_master_ui();
					break;

				case IdLoadSettingsFromFile:
				{
					LocalZynAddSubFx::loadXML( m.getString() );
					if( ui )
					{
						ui->refresh_master_ui();
					}
					pthread_mutex_lock( &m_master->mutex );
					sendMessage( IdLoadSettingsFromFile );
					pthread_mutex_unlock( &m_master->mutex );
					break;
				}

				case IdLoadPresetFile:
				{
					LocalZynAddSubFx::loadPreset( m.getString(), ui ?
											ui->npartcounter->value()-1 : 0 );
					if( ui )
					{
						ui->npartcounter->do_callback();
						ui->updatepanel();
						ui->refresh_master_ui();
					}
					pthread_mutex_lock( &m_master->mutex );
					sendMessage( IdLoadPresetFile );
					pthread_mutex_unlock( &m_master->mutex );
					break;
				}

				default:
					break;
			}
		}
		pthread_mutex_unlock( &m_guiMutex );
	}
	Fl::flush();

	delete ui;
}

int main( int _argc, char * * _argv )
{
#ifdef SYNC_WITH_SHM_FIFO
	if( _argc < 3 )
#else
	if( _argc < 2 )
#endif
	{
		fprintf( stderr, "not enough arguments\n" );
		return -1;
	}

#ifdef LMMS_BUILD_WIN32
#ifndef __WINPTHREADS_VERSION
	// (non-portable) initialization of statically linked pthread library
	pthread_win32_process_attach_np();
	pthread_win32_thread_attach_np();
#endif
#endif

#ifdef SYNC_WITH_SHM_FIFO
	RemoteZynAddSubFx * remoteZASF =
		new RemoteZynAddSubFx( atoi( _argv[1] ), atoi( _argv[2] ) );
#else
	RemoteZynAddSubFx * remoteZASF = new RemoteZynAddSubFx( _argv[1] );
#endif

	remoteZASF->guiLoop();

	delete remoteZASF;

#ifdef LMMS_BUILD_WIN32
#ifndef __WINPTHREADS_VERSION
	pthread_win32_thread_detach_np();
	pthread_win32_process_detach_np();
#endif
#endif

	return 0;
}

#ifdef NTK_GUI
static Fl_Tiled_Image *module_backdrop;
#endif

void set_module_parameters ( Fl_Widget *o )
{
#ifdef NTK_GUI
	o->box( FL_DOWN_FRAME );
	o->align( o->align() | FL_ALIGN_IMAGE_BACKDROP );
	o->color( FL_BLACK );
	o->image( module_backdrop );
	o->labeltype( FL_SHADOW_LABEL );
#else
	o->box( FL_PLASTIC_UP_BOX );
	o->color( FL_CYAN );
	o->labeltype( FL_EMBOSSED_LABEL );
#endif
}


code:
/*
 * RemoteZynAddSubFx.h - ZynAddSubFX-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.
 *
 */

#ifndef REMOTE_ZYNADDSUBFX_H
#define REMOTE_ZYNADDSUBFX_H

#include "RemotePlugin.h"

enum ZasfRemoteMessageIDs
{
	IdZasfPresetDirectory = IdUserBase,
	IdZasfLmmsWorkingDirectory,
	IdZasfSetPitchWheelBendRange
} ;

#endif