code:
/* Nes.cpp - A NES instrument plugin for LMMS
 *
 * Copyright (c) 2014 Vesa Kivimäki
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QDomElement>

#include "Nes.h"
#include "Engine.h"
#include "InstrumentTrack.h"
#include "ToolTip.h"
#include "Song.h"
#include "lmms_math.h"
#include "interpolation.h"
#include "Mixer.h"
#include "Oscillator.h"

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

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT nes_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"Nescaline",
	QT_TRANSLATE_NOOP( "PluginBrowser",
				"A NES-like synthesizer" ),
	"Vesa Kivimäki <contact/dot/diizy/at/nbl/dot/fi>",
	0x0100,
	Plugin::Instrument,
	new PluginPixmapLoader( "logo" ),
	NULL,
	NULL
} ;

}

NesObject::NesObject( NesInstrument * nes, const sample_rate_t samplerate, NotePlayHandle * nph ) :
	m_parent( nes ),
	m_samplerate( samplerate ),
	m_nph( nph )
{
	m_pitchUpdateCounter = 0;
	m_pitchUpdateFreq = wavelength( 60.0f );

	m_LFSR = LFSR_INIT;

	m_ch1Counter = 0;
	m_ch2Counter = 0;
	m_ch3Counter = 0;
	m_ch4Counter = 0;

	m_ch1EnvCounter = 0;
	m_ch2EnvCounter = 0;
	m_ch4EnvCounter = 0;

	m_ch1EnvValue = 15;
	m_ch2EnvValue = 15;
	m_ch4EnvValue = 15;

	m_ch1SweepCounter = 0;
	m_ch2SweepCounter = 0;
	m_ch4SweepCounter = 0;

	m_12Last = 0.0f;
	m_34Last = 0.0f;

	m_maxWlen = wavelength( MIN_FREQ );

	m_nsf = NES_SIMPLE_FILTER * ( m_samplerate / 44100.0 );

	m_lastNoteFreq = 0;
	m_lastNoiseFreq = -1.0f; // value that is always different than noisefreq so it gets updated at start

	m_vibratoPhase = 0;

	updatePitch();
}

NesObject::~NesObject()
{
}

void NesObject::renderOutput( sampleFrame * buf, fpp_t frames )
{
	////////////////////////////////
	//	                          //
	//  variables for processing  //
	//                            //
	////////////////////////////////

	bool ch1Enabled = m_parent->m_ch1Enabled.value();
	bool ch2Enabled = m_parent->m_ch2Enabled.value();
	bool ch3Enabled = m_parent->m_ch3Enabled.value();
	bool ch4Enabled = m_parent->m_ch4Enabled.value();

	float ch1DutyCycle = DUTY_CYCLE[ m_parent->m_ch1DutyCycle.value() ];
	int ch1EnvLen = wavelength( floorf( 240.0 / ( m_parent->m_ch1EnvLen.value() + 1 ) ) );
	bool ch1EnvLoop = m_parent->m_ch1EnvLooped.value();

	float ch2DutyCycle = DUTY_CYCLE[ m_parent->m_ch2DutyCycle.value() ];
	int ch2EnvLen = wavelength( floorf( 240.0 / ( m_parent->m_ch2EnvLen.value() + 1 ) ) );
	bool ch2EnvLoop = m_parent->m_ch2EnvLooped.value();

	int ch4EnvLen = wavelength( floorf( 240.0 / ( m_parent->m_ch4EnvLen.value() + 1 ) ) );
	bool ch4EnvLoop = m_parent->m_ch4EnvLooped.value();

	// processing variables for operators
	int ch1;
	int ch2;
	int ch3;
	int ch4;

	// levels for generators (used for dc offset compensation)
	int ch1Level;
	int ch2Level;
	int ch3Level;
	int ch4Level;

	int ch1SweepRate = wavelength( floorf( 120.0 / ( m_parent->m_ch1SweepRate.value() + 1 ) ) );
	int ch2SweepRate = wavelength( floorf( 120.0 / ( m_parent->m_ch2SweepRate.value() + 1 ) ) );
	int ch4SweepRate = wavelength( floorf( 60.0f / ( 8.0f - qAbs( m_parent->m_ch4Sweep.value() ) ) ) );

	int ch1Sweep = static_cast<int>( m_parent->m_ch1SweepAmt.value() * -1.0 );
	int ch2Sweep = static_cast<int>( m_parent->m_ch2SweepAmt.value() * -1.0 );

	int ch4Sweep = 0;
	if( m_parent->m_ch4Sweep.value() != 0.0f )
	{
		ch4Sweep = m_parent->m_ch4Sweep.value() > 0.0f
			? -1
			: 1;
	}

	// the amounts are inverted so we correct them here
	if( ch1Sweep > 0 )
	{
		ch1Sweep = 8 - ch1Sweep;
	}
	if( ch1Sweep < 0 )
	{
		ch1Sweep = -8 - ch1Sweep;
	}

	if( ch2Sweep > 0 )
	{
		ch2Sweep = 8 - ch2Sweep;
	}
	if( ch2Sweep < 0 )
	{
		ch2Sweep = -8 - ch2Sweep;
	}

	// start framebuffer loop

	for( f_cnt_t f = 0; f < frames; f++ )
	{
		////////////////////////////////
		//	                          //
		//        pitch update        //
		//                            //
		////////////////////////////////

		m_pitchUpdateCounter++;
		if( m_pitchUpdateCounter >= m_pitchUpdateFreq )
		{
			updatePitch();
			m_pitchUpdateCounter = 0;
		}

		////////////////////////////////
		//	                          //
		//        channel 1           //
		//                            //
		////////////////////////////////

		// render pulse wave
		if( m_wlen1 <= m_maxWlen && m_wlen1 >= MIN_WLEN && ch1Enabled )
		{
			ch1Level = m_parent->m_ch1EnvEnabled.value()
				? static_cast<int>( ( m_parent->m_ch1Volume.value() * m_ch1EnvValue ) / 15.0 )
				: static_cast<int>( m_parent->m_ch1Volume.value() );
			ch1 = m_ch1Counter > m_wlen1 * ch1DutyCycle
				? 0
				: ch1Level;
		}
		else ch1 = ch1Level = 0;

		// update sweep
		m_ch1SweepCounter++;
		if( m_ch1SweepCounter >= ch1SweepRate )
		{
			m_ch1SweepCounter = 0;
			if( m_parent->m_ch1SweepEnabled.value() && m_wlen1 <= m_maxWlen && m_wlen1 >= MIN_WLEN )
			{
				// check if the sweep goes up or down
				if( ch1Sweep > 0 )
				{
					m_wlen1 += m_wlen1 >> qAbs( ch1Sweep );
				}
				if( ch1Sweep < 0 )
				{
					m_wlen1 -= m_wlen1 >> qAbs( ch1Sweep );
					m_wlen1--;  // additional minus 1 for ch1 only
				}
			}
		}

		// update framecounters
		m_ch1Counter++;
		m_ch1Counter = m_wlen1 ? m_ch1Counter % m_wlen1 : 0;

		m_ch1EnvCounter++;
		if( m_ch1EnvCounter >= ch1EnvLen )
		{
			m_ch1EnvCounter = 0;
			m_ch1EnvValue--;
			if( m_ch1EnvValue < 0 )
			{
				m_ch1EnvValue = ch1EnvLoop ? 15	: 0;
			}
		}

		////////////////////////////////
		//	                          //
		//        channel 2           //
		//                            //
		////////////////////////////////

		// render pulse wave
		if( m_wlen2 <= m_maxWlen && m_wlen2 >= MIN_WLEN && ch2Enabled )
		{
			ch2Level = m_parent->m_ch2EnvEnabled.value()
				? static_cast<int>( ( m_parent->m_ch2Volume.value() * m_ch2EnvValue ) / 15.0 )
				: static_cast<int>( m_parent->m_ch2Volume.value() );
			ch2 = m_ch2Counter > m_wlen2 * ch2DutyCycle
				? 0
				: ch2Level;
		}
		else ch2 = ch2Level = 0;

		// update sweep
		m_ch2SweepCounter++;
		if( m_ch2SweepCounter >= ch2SweepRate )
		{
			m_ch2SweepCounter = 0;
			if( m_parent->m_ch2SweepEnabled.value() && m_wlen2 <= m_maxWlen && m_wlen2 >= MIN_WLEN )
			{
				// check if the sweep goes up or down
				if( ch2Sweep > 0 )
				{
					m_wlen2 += m_wlen2 >> qAbs( ch2Sweep );
				}
				if( ch2Sweep < 0 )
				{
					m_wlen2 -= m_wlen2 >> qAbs( ch2Sweep );
				}
			}
		}

		// update framecounters
		m_ch2Counter++;
		m_ch2Counter = m_wlen2 ? m_ch2Counter % m_wlen2 : 0;

		m_ch2EnvCounter++;
		if( m_ch2EnvCounter >= ch2EnvLen )
		{
			m_ch2EnvCounter = 0;
			m_ch2EnvValue--;
			if( m_ch2EnvValue < 0 )
			{
				m_ch2EnvValue = ch2EnvLoop
					? 15
					: 0;
			}
		}

		////////////////////////////////
		//	                          //
		//        channel 3           //
		//                            //
		////////////////////////////////

		// make sure we don't overflow
		m_ch3Counter = m_wlen3 ? m_ch3Counter % m_wlen3 : 0;

		// render triangle wave
		if( m_wlen3 <= m_maxWlen && ch3Enabled )
		{
			ch3Level = static_cast<int>( m_parent->m_ch3Volume.value() );
			ch3 = m_wlen3 ? TRIANGLE_WAVETABLE[ ( m_ch3Counter * 32 ) / m_wlen3 ] : 0;
			ch3 = ( ch3 * ch3Level ) / 15;
		}
		else ch3 = ch3Level = 0;

		m_ch3Counter++;

		////////////////////////////////
		//	                          //
		//        channel 4           //
		//                            //
		////////////////////////////////

		// render pseudo noise
		if( ch4Enabled )
		{
			ch4Level = m_parent->m_ch4EnvEnabled.value()
				? ( static_cast<int>( m_parent->m_ch4Volume.value() ) * m_ch4EnvValue ) / 15
				: static_cast<int>( m_parent->m_ch4Volume.value() );
			ch4 = LFSR()
				? ch4Level
				: 0;
		}
		else ch4 = ch4Level = 0;

		// update framecounters
		m_ch4Counter++;
		if( m_ch4Counter >= m_wlen4 )
		{
			m_ch4Counter = 0;
			updateLFSR( m_parent->m_ch4NoiseMode.value() );
		}
		m_ch4EnvCounter++;
		if( m_ch4EnvCounter >= ch4EnvLen )
		{
			m_ch4EnvCounter = 0;
			m_ch4EnvValue--;
			if( m_ch4EnvValue < 0 )
			{
				m_ch4EnvValue = ch4EnvLoop
					? 15
					: 0;
			}
		}

		m_ch4SweepCounter++;
		if( m_ch4SweepCounter >= ch4SweepRate )
		{
			m_ch4SweepCounter = 0;
			if( ch4Sweep != 0 )
			{
				int freqN = nearestNoiseFreq( static_cast<float>( m_samplerate ) / m_wlen4 );
				freqN = qBound( 0, freqN + ch4Sweep, 15 );
				m_wlen4 = wavelength( NOISE_FREQS[ freqN ] );

				if( m_wlen4 == 0 && ch4Sweep == 1 ) // a workaround for sweep getting stuck on 0 wavelength
				{
					while( m_wlen4 == 0 )
					{
						m_wlen4 = wavelength( NOISE_FREQS[ ++freqN ] );
					}
				}
			}
		}

		////////////////////////////////
		//	                          //
		//  final stage - mixing      //
		//                            //
		////////////////////////////////

		float pin1 = static_cast<float>( ch1 + ch2 );
		// add dithering noise
		pin1 *= 1.0 + ( Oscillator::noiseSample( 0.0f ) * DITHER_AMP );
		pin1 = pin1 / 30.0f;

		pin1 = signedPow( pin1, NES_DIST );

		pin1 = pin1 * 2.0f - 1.0f;

		// simple first order iir filter, to simulate the frequency response falloff in nes analog audio output
		pin1 = linearInterpolate( pin1, m_12Last, m_nsf );
		m_12Last = pin1;

		// compensate DC offset
		pin1 += 1.0f - signedPow( static_cast<float>( ch1Level + ch2Level ) / 30.0f, NES_DIST );

		pin1 *= NES_MIXING_12;

		float pin2 = static_cast<float>( ch3 + ch4 );
		// add dithering noise
		pin2 *= 1.0 + ( Oscillator::noiseSample( 0.0f ) * DITHER_AMP );
		pin2 = pin2 / 30.0f;

		pin2 = signedPow( pin2, NES_DIST );

		pin2 = pin2 * 2.0f - 1.0f;

		// simple first order iir filter, to simulate the frequency response falloff in nes analog audio output
		pin2 = linearInterpolate( pin2, m_34Last, m_nsf );
		m_34Last = pin2;

		// compensate DC offset
		pin2 += 1.0f - signedPow( static_cast<float>( ch3Level + ch4Level ) / 30.0f, NES_DIST );

		pin2 *= NES_MIXING_34;

		const float mixdown = ( pin1 + pin2 ) * NES_MIXING_ALL * m_parent->m_masterVol.value();

		buf[f][0] = mixdown;
		buf[f][1] = mixdown;

	} // end framebuffer loop

}

void NesObject::updateVibrato( float * freq )
{
	float vibratoAmt = floorf( m_parent->m_vibrato.value() ) / 15.0f;
	m_vibratoPhase++;
	m_vibratoPhase %= 32;
	float vibratoRatio = 1.0f + ( static_cast<float>( TRIANGLE_WAVETABLE[ m_vibratoPhase ] ) * 0.02f * vibratoAmt );
	*freq *= vibratoRatio;
}

void NesObject::updatePitch()
{
	float freq = m_nph->frequency();
	// if vibrato is active, update vibrato
	if( m_parent->m_vibrato.value() > 0 )
	{
		updateVibrato( &freq );
	}
	// check if frequency has changed, if so, update wavelengths of ch1-3
	if( freq != m_lastNoteFreq )
	{
		m_wlen1 = wavelength( freq * m_parent->m_freq1 );
		m_wlen2 = wavelength( freq * m_parent->m_freq2 );
		m_wlen3 = wavelength( freq * m_parent->m_freq3 );
	}
	// noise channel can use either note freq or preset freqs
	if( m_parent->m_ch4NoiseFreqMode.value() && freq != m_lastNoteFreq )
	{
		float f = freq * 2.0f;
		if( m_parent->m_ch4NoiseQuantize.value() ) // note freq can be quantized to the preset freqs
		{
			f = NOISE_FREQS[ nearestNoiseFreq( f ) ];
		}
		m_wlen4 = wavelength( f );
	}
	if( ! m_parent->m_ch4NoiseFreqMode.value() && m_lastNoiseFreq != m_parent->m_ch4NoiseFreq.value() )
	{
		m_wlen4 = wavelength( NOISE_FREQS[ 15 - static_cast<int>( m_parent->m_ch4NoiseFreq.value() ) ] );
		m_lastNoiseFreq = m_parent->m_ch4NoiseFreq.value();
	}

	m_lastNoteFreq = freq;
}

NesInstrument::NesInstrument( InstrumentTrack * instrumentTrack ) :
	Instrument( instrumentTrack, &nes_plugin_descriptor ),
	m_ch1Enabled( true, this ),
	m_ch1Crs( 0.f, -24.f, 24.f, 1.f, this, tr( "Channel 1 coarse detune" ) ),
	m_ch1Volume( 15.f, 0.f, 15.f, 1.f, this, tr( "Channel 1 volume" ) ),

	m_ch1EnvEnabled( false, this ),
	m_ch1EnvLooped( false, this ),
	m_ch1EnvLen( 0.f, 0.f, 15.f, 1.f, this, tr( "Channel 1 envelope length" ) ),

	m_ch1DutyCycle( 0, 0, 3, this, tr( "Channel 1 duty cycle" ) ),

	m_ch1SweepEnabled( false, this ),
	m_ch1SweepAmt( 0.f, -7.f, 7.f, 1.f, this, tr( "Channel 1 sweep amount" ) ),
	m_ch1SweepRate( 0.f, 0.f, 7.f, 1.f, this, tr( "Channel 1 sweep rate" ) ),

	m_ch2Enabled( true, this ),
	m_ch2Crs( 0.f, -24.f, 24.f, 1.f, this, tr( "Channel 2 Coarse detune" ) ),
	m_ch2Volume( 15.f, 0.f, 15.f, 1.f, this, tr( "Channel 2 Volume" ) ),

	m_ch2EnvEnabled( false, this ),
	m_ch2EnvLooped( false, this ),
	m_ch2EnvLen( 0.f, 0.f, 15.f, 1.f, this, tr( "Channel 2 envelope length" ) ),

	m_ch2DutyCycle( 2, 0, 3, this, tr( "Channel 2 duty cycle" ) ),

	m_ch2SweepEnabled( false, this ),
	m_ch2SweepAmt( 0.f, -7.f, 7.f, 1.f, this, tr( "Channel 2 sweep amount" ) ),
	m_ch2SweepRate( 0.f, 0.f, 7.f, 1.f, this, tr( "Channel 2 sweep rate" ) ),

	//channel 3
	m_ch3Enabled( true, this ),
	m_ch3Crs( 0.f, -24.f, 24.f, 1.f, this, tr( "Channel 3 coarse detune" ) ),
	m_ch3Volume( 15.f, 0.f, 15.f, 1.f, this, tr( "Channel 3 volume" ) ),

	//channel 4
	m_ch4Enabled( false, this ),
	m_ch4Volume( 15.f, 0.f, 15.f, 1.f, this, tr( "Channel 4 volume" ) ),

	m_ch4EnvEnabled( false, this ),
	m_ch4EnvLooped( false, this ),
	m_ch4EnvLen( 0.f, 0.f, 15.f, 1.f, this, tr( "Channel 4 envelope length" ) ),

	m_ch4NoiseMode( false, this ),
	m_ch4NoiseFreqMode( false, this ),
	m_ch4NoiseFreq( 0.f, 0.f, 15.f, 1.f, this, tr( "Channel 4 noise frequency" ) ),

	m_ch4Sweep( 0.f, -7.f, 7.f, 1.f, this, tr( "Channel 4 noise frequency sweep" ) ),
	m_ch4NoiseQuantize( true, this ),

	//master
	m_masterVol( 1.0f, 0.0f, 2.0f, 0.01f, this, tr( "Master volume" ) ),
	m_vibrato( 0.0f, 0.0f, 15.0f, 1.0f, this, tr( "Vibrato" ) )
{
	connect( &m_ch1Crs, SIGNAL( dataChanged() ), this, SLOT( updateFreq1() ), Qt::DirectConnection );
	connect( &m_ch2Crs, SIGNAL( dataChanged() ), this, SLOT( updateFreq2() ), Qt::DirectConnection );
	connect( &m_ch3Crs, SIGNAL( dataChanged() ), this, SLOT( updateFreq3() ), Qt::DirectConnection );

	updateFreq1();
	updateFreq2();
	updateFreq3();
}

NesInstrument::~NesInstrument()
{
}

void NesInstrument::playNote( NotePlayHandle * n, sampleFrame * workingBuffer )
{
	const fpp_t frames = n->framesLeftForCurrentPeriod();
	const f_cnt_t offset = n->noteOffset();

	if ( n->totalFramesPlayed() == 0 || n->m_pluginData == NULL )
	{
		NesObject * nes = new NesObject( this, Engine::mixer()->processingSampleRate(), n );
		n->m_pluginData = nes;
	}

	NesObject * nes = static_cast<NesObject *>( n->m_pluginData );

	nes->renderOutput( workingBuffer + offset, frames );

	applyRelease( workingBuffer, n );

	instrumentTrack()->processAudioBuffer( workingBuffer, frames + offset, n );
}

void NesInstrument::deleteNotePluginData( NotePlayHandle * n )
{
	delete static_cast<NesObject *>( n->m_pluginData );
}

void NesInstrument::saveSettings(  QDomDocument & doc, QDomElement & element )
{
		m_ch1Enabled.saveSettings( doc, element, "on1" );
		m_ch1Crs.saveSettings( doc, element, "crs1" );
		m_ch1Volume.saveSettings( doc, element, "vol1" );

		m_ch1EnvEnabled.saveSettings( doc, element, "envon1" );
		m_ch1EnvLooped.saveSettings( doc, element, "envloop1" );
		m_ch1EnvLen.saveSettings( doc, element, "envlen1" );

		m_ch1DutyCycle.saveSettings( doc, element, "dc1" );

		m_ch1SweepEnabled.saveSettings( doc, element, "sweep1" );
		m_ch1SweepAmt.saveSettings( doc, element, "swamt1" );
		m_ch1SweepRate.saveSettings( doc, element, "swrate1" );

	// channel 2
		m_ch2Enabled.saveSettings( doc, element, "on2" );
		m_ch2Crs.saveSettings( doc, element, "crs2" );
		m_ch2Volume.saveSettings( doc, element, "vol2" );

		m_ch2EnvEnabled.saveSettings( doc, element, "envon2" );
		m_ch2EnvLooped.saveSettings( doc, element, "envloop2" );
		m_ch2EnvLen.saveSettings( doc, element, "envlen2" );

		m_ch2DutyCycle.saveSettings( doc, element, "dc2" );

		m_ch2SweepEnabled.saveSettings( doc, element, "sweep2" );
		m_ch2SweepAmt.saveSettings( doc, element, "swamt2" );
		m_ch2SweepRate.saveSettings( doc, element, "swrate2" );

	//channel 3
		m_ch3Enabled.saveSettings( doc, element, "on3" );
		m_ch3Crs.saveSettings( doc, element, "crs3" );
		m_ch3Volume.saveSettings( doc, element, "vol3" );

	//channel 4
		m_ch4Enabled.saveSettings( doc, element, "on4" );
		m_ch4Volume.saveSettings( doc, element, "vol4" );

		m_ch4EnvEnabled.saveSettings( doc, element, "envon4" );
		m_ch4EnvLooped.saveSettings( doc, element, "envloop4" );
		m_ch4EnvLen.saveSettings( doc, element, "envlen4" );

		m_ch4NoiseMode.saveSettings( doc, element, "nmode4" );
		m_ch4NoiseFreqMode.saveSettings( doc, element, "nfrqmode4" );
		m_ch4NoiseFreq.saveSettings( doc, element, "nfreq4" );

		m_ch4NoiseQuantize.saveSettings( doc, element, "nq4" );
		m_ch4Sweep.saveSettings( doc, element, "nswp4" );

	//master
		m_masterVol.saveSettings( doc, element, "vol" );
		m_vibrato.saveSettings( doc, element, "vibr" );
}

void NesInstrument::loadSettings( const QDomElement & element )
{
		m_ch1Enabled.loadSettings(  element, "on1" );
		m_ch1Crs.loadSettings(  element, "crs1" );
		m_ch1Volume.loadSettings(  element, "vol1" );

		m_ch1EnvEnabled.loadSettings(  element, "envon1" );
		m_ch1EnvLooped.loadSettings(  element, "envloop1" );
		m_ch1EnvLen.loadSettings(  element, "envlen1" );

		m_ch1DutyCycle.loadSettings(  element, "dc1" );

		m_ch1SweepEnabled.loadSettings(  element, "sweep1" );
		m_ch1SweepAmt.loadSettings(  element, "swamt1" );
		m_ch1SweepRate.loadSettings(  element, "swrate1" );

	// channel 2
		m_ch2Enabled.loadSettings(  element, "on2" );
		m_ch2Crs.loadSettings(  element, "crs2" );
		m_ch2Volume.loadSettings(  element, "vol2" );

		m_ch2EnvEnabled.loadSettings(  element, "envon2" );
		m_ch2EnvLooped.loadSettings(  element, "envloop2" );
		m_ch2EnvLen.loadSettings(  element, "envlen2" );

		m_ch2DutyCycle.loadSettings(  element, "dc2" );

		m_ch2SweepEnabled.loadSettings(  element, "sweep2" );
		m_ch2SweepAmt.loadSettings(  element, "swamt2" );
		m_ch2SweepRate.loadSettings(  element, "swrate2" );

	//channel 3
		m_ch3Enabled.loadSettings(  element, "on3" );
		m_ch3Crs.loadSettings(  element, "crs3" );
		m_ch3Volume.loadSettings(  element, "vol3" );

	//channel 4
		m_ch4Enabled.loadSettings(  element, "on4" );
		m_ch4Volume.loadSettings(  element, "vol4" );

		m_ch4EnvEnabled.loadSettings(  element, "envon4" );
		m_ch4EnvLooped.loadSettings(  element, "envloop4" );
		m_ch4EnvLen.loadSettings(  element, "envlen4" );

		m_ch4NoiseMode.loadSettings(  element, "nmode4" );
		m_ch4NoiseFreqMode.loadSettings(  element, "nfrqmode4" );
		m_ch4NoiseFreq.loadSettings(  element, "nfreq4" );

		m_ch4NoiseQuantize.loadSettings(  element, "nq4" );
		m_ch4Sweep.loadSettings(  element, "nswp4" );

	//master
		m_masterVol.loadSettings(  element, "vol" );
		m_vibrato.loadSettings(  element, "vibr" );
}

QString NesInstrument::nodeName() const
{
	return( nes_plugin_descriptor.name );
}

PluginView * NesInstrument::instantiateView( QWidget * parent )
{
	return( new NesInstrumentView( this, parent ) );
}

void NesInstrument::updateFreq1()
{
	m_freq1 = powf( 2, m_ch1Crs.value() / 12.0f );
}

void NesInstrument::updateFreq2()
{
	m_freq2 = powf( 2, m_ch2Crs.value() / 12.0f );
}

void NesInstrument::updateFreq3()
{
	m_freq3 = powf( 2, m_ch3Crs.value() / 12.0f );
}

QPixmap * NesInstrumentView::s_artwork = NULL;

NesInstrumentView::NesInstrumentView( Instrument * instrument,	QWidget * parent ) :
	InstrumentViewFixedSize( instrument, parent )
{
	setAutoFillBackground( true );
	QPalette pal;

	if( s_artwork == NULL )
	{
		s_artwork = new QPixmap( PLUGIN_NAME::getIconPixmap( "artwork" ) );
	}

	pal.setBrush( backgroundRole(),	*s_artwork );
	setPalette( pal );

	const int KNOB_Y1 = 24;
	const int KNOB_Y2 = 81;
	const int KNOB_Y3 = 138;
	const int KNOB_Y4 = 195;

	const int KNOB_X1 = 12;
	const int KNOB_X2 = 46;
	const int KNOB_X3 = 84;
	const int KNOB_X4 = 117;
	const int KNOB_X5 = 151;
	const int KNOB_X6 = 172;
	const int KNOB_X7 = 206;

	// channel 1

	makeknob( m_ch1VolumeKnob, KNOB_X1, KNOB_Y1, tr( "Volume" ), "", "" )
	makeknob( m_ch1CrsKnob, KNOB_X2, KNOB_Y1, tr( "Coarse detune" ), "", "" )
	makeknob( m_ch1EnvLenKnob, KNOB_X3, KNOB_Y1, tr( "Envelope length" ), "", "" )

	makenesled( m_ch1EnabledBtn, KNOB_X1, KNOB_Y1 - 12, tr( "Enable channel 1" ) )
	makenesled( m_ch1EnvEnabledBtn, KNOB_X3, KNOB_Y1 - 12, tr( "Enable envelope 1" ) )
	makenesled( m_ch1EnvLoopedBtn, 129, KNOB_Y1 - 12, tr( "Enable envelope 1 loop" ) )

	makenesled( m_ch1SweepEnabledBtn, KNOB_X6, KNOB_Y1 - 12, tr( "Enable sweep 1" ) )
	makeknob( m_ch1SweepAmtKnob, KNOB_X6, KNOB_Y1, tr( "Sweep amount" ), "", "" )
	makeknob( m_ch1SweepRateKnob, KNOB_X7, KNOB_Y1, tr( "Sweep rate" ), "", "" )

	int dcx = 117;
	makedcled( ch1_dc1, dcx, 42, tr( "12.5% Duty cycle" ), "nesdc1_on" )
	dcx += 13;
	makedcled( ch1_dc2, dcx, 42, tr( "25% Duty cycle" ), "nesdc2_on" )
	dcx += 13;
	makedcled( ch1_dc3, dcx, 42, tr( "50% Duty cycle" ), "nesdc3_on" )
	dcx += 13;
	makedcled( ch1_dc4, dcx, 42, tr( "75% Duty cycle" ), "nesdc4_on" )

	m_ch1DutyCycleGrp = new automatableButtonGroup( this );
	m_ch1DutyCycleGrp -> addButton( ch1_dc1 );
	m_ch1DutyCycleGrp -> addButton( ch1_dc2 );
	m_ch1DutyCycleGrp -> addButton( ch1_dc3 );
	m_ch1DutyCycleGrp -> addButton( ch1_dc4 );

	// channel 2

	makeknob( m_ch2VolumeKnob, KNOB_X1, KNOB_Y2, tr( "Volume" ), "", "" )
	makeknob( m_ch2CrsKnob, KNOB_X2, KNOB_Y2, tr( "Coarse detune" ), "", "" )
	makeknob( m_ch2EnvLenKnob, KNOB_X3, KNOB_Y2, tr( "Envelope length" ), "", "" )

	makenesled( m_ch2EnabledBtn, KNOB_X1, KNOB_Y2 - 12, tr( "Enable channel 2" ) )
	makenesled( m_ch2EnvEnabledBtn, KNOB_X3, KNOB_Y2 - 12, tr( "Enable envelope 2" ) )
	makenesled( m_ch2EnvLoopedBtn, 129, KNOB_Y2 - 12, tr( "Enable envelope 2 loop" ) )

	makenesled( m_ch2SweepEnabledBtn, KNOB_X6, KNOB_Y2 - 12, tr( "Enable sweep 2" ) )
	makeknob( m_ch2SweepAmtKnob, KNOB_X6, KNOB_Y2, tr( "Sweep amount" ), "", "" )
	makeknob( m_ch2SweepRateKnob, KNOB_X7, KNOB_Y2, tr( "Sweep rate" ), "", "" )

	dcx = 117;
	makedcled( ch2_dc1, dcx, 99, tr( "12.5% Duty cycle" ), "nesdc1_on" )
	dcx += 13;
	makedcled( ch2_dc2, dcx, 99, tr( "25% Duty cycle" ), "nesdc2_on" )
	dcx += 13;
	makedcled( ch2_dc3, dcx, 99, tr( "50% Duty cycle" ), "nesdc3_on" )
	dcx += 13;
	makedcled( ch2_dc4, dcx, 99, tr( "75% Duty cycle" ), "nesdc4_on" )

	m_ch2DutyCycleGrp = new automatableButtonGroup( this );
	m_ch2DutyCycleGrp -> addButton( ch2_dc1 );
	m_ch2DutyCycleGrp -> addButton( ch2_dc2 );
	m_ch2DutyCycleGrp -> addButton( ch2_dc3 );
	m_ch2DutyCycleGrp -> addButton( ch2_dc4 );

	//channel 3
	makenesled( m_ch3EnabledBtn, KNOB_X1, KNOB_Y3 - 12, tr( "Enable channel 3" ) )
	makeknob( m_ch3VolumeKnob, KNOB_X1, KNOB_Y3, tr( "Volume" ), "", "" )
	makeknob( m_ch3CrsKnob, KNOB_X2, KNOB_Y3, tr( "Coarse detune" ), "", "" )

	//channel 4
	makeknob( m_ch4VolumeKnob, KNOB_X1, KNOB_Y4, tr( "Volume" ), "", "" )
	makeknob( m_ch4NoiseFreqKnob, KNOB_X2, KNOB_Y4, tr( "Noise Frequency" ), "", "" )
	makeknob( m_ch4EnvLenKnob, KNOB_X3, KNOB_Y4, tr( "Envelope length" ), "", "" )
	makeknob( m_ch4SweepKnob, KNOB_X4, KNOB_Y4, tr( "Frequency sweep" ), "", "" )

	makenesled( m_ch4EnabledBtn, KNOB_X1, KNOB_Y4 - 12, tr( "Enable channel 4" ) )
	makenesled( m_ch4EnvEnabledBtn, KNOB_X3, KNOB_Y4 - 12, tr( "Enable envelope 4" ) )
	makenesled( m_ch4EnvLoopedBtn, 129, KNOB_Y4 - 12, tr( "Enable envelope 4 loop" ) )

	makenesled( m_ch4NoiseQuantizeBtn, 162, KNOB_Y4 - 12, tr( "Quantize noise frequency when using note frequency" ) )

	makenesled( m_ch4NoiseFreqModeBtn,  148, 203, tr( "Use note frequency for noise" ) )
	makenesled( m_ch4NoiseModeBtn, 148, 224, tr( "Noise mode" ) )

	//master
	makeknob( m_masterVolKnob, KNOB_X4, KNOB_Y3, tr( "Master volume" ), "", "" )
	makeknob( m_vibratoKnob, KNOB_X5, KNOB_Y3, tr( "Vibrato" ), "", "" )

}

NesInstrumentView::~NesInstrumentView()
{
}

void NesInstrumentView::modelChanged()
{
	NesInstrument * nes = castModel<NesInstrument>();

	m_ch1EnabledBtn->setModel( &nes->m_ch1Enabled );
	m_ch1CrsKnob->setModel( &nes->m_ch1Crs );
	m_ch1VolumeKnob->setModel( &nes->m_ch1Volume );

	m_ch1EnvEnabledBtn->setModel( &nes->m_ch1EnvEnabled );
	m_ch1EnvLoopedBtn->setModel( &nes->m_ch1EnvLooped );
	m_ch1EnvLenKnob->setModel( &nes->m_ch1EnvLen );

	m_ch1DutyCycleGrp->setModel( &nes->m_ch1DutyCycle );

	m_ch1SweepEnabledBtn->setModel( &nes->m_ch1SweepEnabled );
	m_ch1SweepAmtKnob->setModel( &nes->m_ch1SweepAmt );
	m_ch1SweepRateKnob->setModel( &nes->m_ch1SweepRate );

	// channel 2
	m_ch2EnabledBtn->setModel( &nes->m_ch2Enabled );
	m_ch2CrsKnob->setModel( &nes->m_ch2Crs );
	m_ch2VolumeKnob->setModel( &nes->m_ch2Volume );

	m_ch2EnvEnabledBtn->setModel( &nes->m_ch2EnvEnabled );
	m_ch2EnvLoopedBtn->setModel( &nes->m_ch2EnvLooped );
	m_ch2EnvLenKnob->setModel( &nes->m_ch2EnvLen );

	m_ch2DutyCycleGrp->setModel( &nes->m_ch2DutyCycle );

	m_ch2SweepEnabledBtn->setModel( &nes->m_ch2SweepEnabled );
	m_ch2SweepAmtKnob->setModel( &nes->m_ch2SweepAmt );
	m_ch2SweepRateKnob->setModel( &nes->m_ch2SweepRate );

	//channel 3
	m_ch3EnabledBtn->setModel( &nes->m_ch3Enabled );
	m_ch3CrsKnob->setModel( &nes->m_ch3Crs );
	m_ch3VolumeKnob->setModel( &nes->m_ch3Volume );

	//channel 4
	m_ch4EnabledBtn->setModel( &nes->m_ch4Enabled );
	m_ch4VolumeKnob->setModel( &nes->m_ch4Volume );

	m_ch4EnvEnabledBtn->setModel( &nes->m_ch4EnvEnabled );
	m_ch4EnvLoopedBtn->setModel( &nes->m_ch4EnvLooped );
	m_ch4EnvLenKnob->setModel( &nes->m_ch4EnvLen );

	m_ch4NoiseModeBtn->setModel( &nes->m_ch4NoiseMode );
	m_ch4NoiseFreqModeBtn->setModel( &nes->m_ch4NoiseFreqMode );
	m_ch4NoiseFreqKnob->setModel( &nes->m_ch4NoiseFreq );

	m_ch4SweepKnob->setModel( &nes->m_ch4Sweep );
	m_ch4NoiseQuantizeBtn->setModel( &nes->m_ch4NoiseQuantize );

	//master
	m_masterVolKnob->setModel( &nes->m_masterVol );
	m_vibratoKnob->setModel( &nes->m_vibrato );
}

extern "C"
{

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

}


code:
/* Nes.h - A NES instrument plugin for LMMS
 *
 * Copyright (c) 2014 Vesa Kivimäki
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef NES_H
#define NES_H

#include <cmath>

#include "Instrument.h"
#include "InstrumentView.h"
#include "AutomatableModel.h"
#include "AutomatableButton.h"
#include "TempoSyncKnob.h"
#include "NotePlayHandle.h"
#include "PixmapButton.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( 29, 29 );

#define makenesled( name, x, y, ttip ) \
	name = new PixmapButton( this, NULL ); 	\
	name -> setCheckable( true );			\
	name -> move( x, y );					\
	name -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "nesled_on" ) ); \
	name -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "nesled_off" ) ); \
	ToolTip::add( name, ttip );

#define makedcled( name, x, y, ttip, active ) \
	PixmapButton * name = new PixmapButton( this, NULL ); 	\
	name -> move( x, y );					\
	name -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( active ) ); \
	name -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "nesdc_off" ) ); \
	ToolTip::add( name, ttip );

const float NES_SIMPLE_FILTER = 1.0 / 20.0; // simulate nes analog audio output
const float NFB = 895000.0f;
const float NOISE_FREQS[16] =
	{ NFB/5, NFB/9, NFB/17, NFB/33, NFB/65, NFB/97, NFB/129, NFB/161, NFB/193, NFB/255, NFB/381, NFB/509, NFB/763, NFB/1017, NFB/2035, NFB/4069 };
const uint16_t LFSR_INIT = 1;
const float DUTY_CYCLE[4] = { 0.125, 0.25, 0.5, 0.75 };
const float DITHER_AMP = 1.0 / 60.0;
const float MIN_FREQ = 10.0;
const int TRIANGLE_WAVETABLE[32] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
									15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };

const float NES_DIST = 0.9f; // simulate the slight nonlinear distortion in nes audio output

const float NES_MIXING_12 = 1.0 / 20.0;
const float NES_MIXING_34 = 1.0 / 12.0;
const float NES_MIXING_ALL = 1.0 / ( NES_MIXING_12 + NES_MIXING_34 ); // constants to simulate the hardwired mixing values for nes channels

const int MIN_WLEN = 4;

class NesInstrument;

class NesObject
{
	MM_OPERATORS
public:
	NesObject( NesInstrument * nes, const sample_rate_t samplerate, NotePlayHandle * nph );
	virtual ~NesObject();

	void renderOutput( sampleFrame * buf, fpp_t frames );
	void updateVibrato( float * freq );
	void updatePitch();

	void updateLFSR( bool mode )
	{
		uint16_t LFSRx;
		if( mode )
		{
				LFSRx = m_LFSR & ( 1 << 8 ); // get bit 13
				LFSRx <<= 6; // shit to left so it overlaps with bit 14
		}
		else
		{
				LFSRx = m_LFSR & ( 1 << 13 ); // get bit 13
				LFSRx <<= 1; // shit to left so it overlaps with bit 14
		}
		m_LFSR ^= LFSRx; // xor bit 14 with bit 8/13 depending on mode
		m_LFSR <<= 1; // shift bit 14 to bit 15

		// cycle bit 14 to 0
		if( m_LFSR & ( 1 << 15 ) ) // if bit 15 is set
		{
			m_LFSR++; // set bit 0 - we know it to be 0 because of the left shift so we can just inc here
		}
	}

	inline bool LFSR() // returns true if bit 14 is set
	{
		if( m_LFSR & ( 1 << 14 ) )
		{
			return true;
		}
		return false;
	}

	inline int wavelength( float freq )
	{
		return static_cast<int>( m_samplerate / freq );
	}

	inline float signedPow( float f, float e )
	{
		return f < 0
			? powf( qAbs( f ), e ) * -1.0f
			: powf( f, e );
	}

	inline int nearestNoiseFreq( float f )
	{
		int n = 15;
		for( int i = 15; i >= 0; i-- )
		{
			if( f >= NOISE_FREQS[ i ] )
			{
				n = i;
			}
		}
		return n;
	}

private:
	NesInstrument * m_parent;
	const sample_rate_t m_samplerate;
	NotePlayHandle * m_nph;

	int m_pitchUpdateCounter;
	int m_pitchUpdateFreq;

	int m_ch1Counter;
	int m_ch2Counter;
	int m_ch3Counter;
	int m_ch4Counter;

	int m_ch1EnvCounter;
	int m_ch2EnvCounter;
	int m_ch4EnvCounter;

	int m_ch1EnvValue;
	int m_ch2EnvValue;
	int m_ch4EnvValue;

	int m_ch1SweepCounter;
	int m_ch2SweepCounter;
	int m_ch4SweepCounter;

	uint16_t m_LFSR;

	float m_12Last;
	float m_34Last;

	float m_lastNoteFreq;
	float m_lastNoiseFreq;

	int m_maxWlen;

	float m_nsf;

// wavelengths
	int m_wlen1;
	int m_wlen2;
	int m_wlen3;
	int m_wlen4;

// vibrato
	int m_vibratoPhase;
};

class NesInstrument : public Instrument
{
	Q_OBJECT
public:
	NesInstrument( InstrumentTrack * instrumentTrack );
	virtual ~NesInstrument();

	virtual void playNote( NotePlayHandle * n,
						sampleFrame * workingBuffer );
	virtual void deleteNotePluginData( NotePlayHandle * n );

	virtual void saveSettings( QDomDocument & doc,
							QDomElement & element );
	virtual void loadSettings( const QDomElement & element );

	virtual QString nodeName() const;

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

	virtual PluginView * instantiateView( QWidget * parent );

public slots:
	void updateFreq1();
	void updateFreq2();
	void updateFreq3();

protected:
	//freq  helpers
	float m_freq1;
	float m_freq2;
	float m_freq3;

private:
	// channel 1
	BoolModel	m_ch1Enabled;
	FloatModel	m_ch1Crs;
	FloatModel	m_ch1Volume;

	BoolModel	m_ch1EnvEnabled;
	BoolModel	m_ch1EnvLooped;
	FloatModel	m_ch1EnvLen;

	IntModel	m_ch1DutyCycle;

	BoolModel	m_ch1SweepEnabled;
	FloatModel	m_ch1SweepAmt;
	FloatModel	m_ch1SweepRate;

	// channel 2
	BoolModel	m_ch2Enabled;
	FloatModel	m_ch2Crs;
	FloatModel	m_ch2Volume;

	BoolModel	m_ch2EnvEnabled;
	BoolModel	m_ch2EnvLooped;
	FloatModel	m_ch2EnvLen;

	IntModel	m_ch2DutyCycle;

	BoolModel	m_ch2SweepEnabled;
	FloatModel	m_ch2SweepAmt;
	FloatModel	m_ch2SweepRate;

	//channel 3
	BoolModel	m_ch3Enabled;
	FloatModel	m_ch3Crs;
	FloatModel	m_ch3Volume;

	//channel 4
	BoolModel	m_ch4Enabled;
	FloatModel	m_ch4Volume;

	BoolModel	m_ch4EnvEnabled;
	BoolModel	m_ch4EnvLooped;
	FloatModel	m_ch4EnvLen;

	BoolModel	m_ch4NoiseMode;
	BoolModel	m_ch4NoiseFreqMode;
	FloatModel	m_ch4NoiseFreq;

	FloatModel	m_ch4Sweep;
	BoolModel	m_ch4NoiseQuantize;

	//master
	FloatModel	m_masterVol;
	FloatModel	m_vibrato;

	friend class NesObject;
	friend class NesInstrumentView;
};

class NesInstrumentView : public InstrumentViewFixedSize
{
	Q_OBJECT
public:
	NesInstrumentView( Instrument * instrument,
					QWidget * parent );
	virtual ~NesInstrumentView();

private:
	virtual void modelChanged();

	// channel 1
	PixmapButton * 	m_ch1EnabledBtn;
	Knob *			m_ch1CrsKnob;
	Knob *			m_ch1VolumeKnob;

	PixmapButton *	m_ch1EnvEnabledBtn;
	PixmapButton *	m_ch1EnvLoopedBtn;
	Knob *			m_ch1EnvLenKnob;

	automatableButtonGroup *	m_ch1DutyCycleGrp;

	PixmapButton *	m_ch1SweepEnabledBtn;
	Knob *			m_ch1SweepAmtKnob;
	Knob *			m_ch1SweepRateKnob;

	// channel 2
	PixmapButton * 	m_ch2EnabledBtn;
	Knob *			m_ch2CrsKnob;
	Knob *			m_ch2VolumeKnob;

	PixmapButton *	m_ch2EnvEnabledBtn;
	PixmapButton *	m_ch2EnvLoopedBtn;
	Knob *			m_ch2EnvLenKnob;

	automatableButtonGroup *	m_ch2DutyCycleGrp;

	PixmapButton *	m_ch2SweepEnabledBtn;
	Knob *			m_ch2SweepAmtKnob;
	Knob *			m_ch2SweepRateKnob;

	//channel 3
	PixmapButton * 	m_ch3EnabledBtn;
	Knob *			m_ch3CrsKnob;
	Knob *			m_ch3VolumeKnob;

	//channel 4
	PixmapButton * 	m_ch4EnabledBtn;
	Knob *			m_ch4VolumeKnob;

	PixmapButton * 	m_ch4EnvEnabledBtn;
	PixmapButton * 	m_ch4EnvLoopedBtn;
	Knob *			m_ch4EnvLenKnob;

	PixmapButton * 	m_ch4NoiseModeBtn;
	PixmapButton * 	m_ch4NoiseFreqModeBtn;
	Knob *			m_ch4NoiseFreqKnob;

	Knob *			m_ch4SweepKnob;
	PixmapButton *	m_ch4NoiseQuantizeBtn;

	//master
	Knob *			m_masterVolKnob;
	Knob *			m_vibratoKnob;

	static QPixmap *	s_artwork;
};

#endif

repo_name: CYBERDEViL/LMMS

license: mit


code:
INCLUDE(BuildPlugin)

BUILD_PLUGIN(organic organic.cpp organic.h MOCFILES organic.h EMBEDDED_RESOURCES *.png)


code:
/*
 * organic.cpp - additive synthesizer for organ-like sounds
 *
 * Copyright (c) 2006-2008 Andreas Brandmaier <andy/at/brandmaier/dot/de>
 *
 * 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 "organic.h"

#include <QDomElement>
#include <QPainter>

#include "Engine.h"
#include "InstrumentTrack.h"
#include "Knob.h"
#include "Mixer.h"
#include "NotePlayHandle.h"
#include "Oscillator.h"
#include "PixmapButton.h"
#include "ToolTip.h"

#include "embed.h"

#include "plugin_export.h"

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT organic_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"Organic",
	QT_TRANSLATE_NOOP( "PluginBrowser",
				"Additive Synthesizer for organ-like sounds" ),
	"Andreas Brandmaier <andreas/at/brandmaier.de>",
	0x0100,
	Plugin::Instrument,
	new PluginPixmapLoader( "logo" ),
	NULL,
	NULL
} ;

}

QPixmap * organicInstrumentView::s_artwork = NULL;
float * organicInstrument::s_harmonics = NULL;

/***********************************************************************
*
*	class OrganicInstrument
*
*	lmms - plugin
*
***********************************************************************/

organicInstrument::organicInstrument( InstrumentTrack * _instrument_track ) :
	Instrument( _instrument_track, &organic_plugin_descriptor ),
	m_modulationAlgo( Oscillator::SignalMix, Oscillator::SignalMix, Oscillator::SignalMix),
	m_fx1Model( 0.0f, 0.0f, 0.99f, 0.01f , this, tr( "Distortion" ) ),
	m_volModel( 100.0f, 0.0f, 200.0f, 1.0f, this, tr( "Volume" ) )
{
	m_numOscillators = NUM_OSCILLATORS;

	m_osc = new OscillatorObject*[ m_numOscillators ];
	for (int i=0; i < m_numOscillators; i++)
	{
		m_osc[i] = new OscillatorObject( this, i );
		m_osc[i]->m_numOscillators = m_numOscillators;

		// Connect events
		connect( &m_osc[i]->m_oscModel, SIGNAL( dataChanged() ),
				m_osc[i], SLOT ( oscButtonChanged() ) );
		connect( &m_osc[i]->m_harmModel, SIGNAL( dataChanged() ),
				m_osc[i], SLOT( updateDetuning() ) );
		connect( &m_osc[i]->m_volModel, SIGNAL( dataChanged() ),
				m_osc[i], SLOT( updateVolume() ) );
		connect( &m_osc[i]->m_panModel, SIGNAL( dataChanged() ),
				m_osc[i], SLOT( updateVolume() ) );
		connect( &m_osc[i]->m_detuneModel, SIGNAL( dataChanged() ),
				m_osc[i], SLOT( updateDetuning() ) );

		m_osc[i]->updateVolume();

	}

/*	m_osc[0]->m_harmonic = log2f( 0.5f );	// one octave below
	m_osc[1]->m_harmonic = log2f( 0.75f );	// a fifth below
	m_osc[2]->m_harmonic = log2f( 1.0f );	// base freq
	m_osc[3]->m_harmonic = log2f( 2.0f );	// first overtone
	m_osc[4]->m_harmonic = log2f( 3.0f );	// second overtone
	m_osc[5]->m_harmonic = log2f( 4.0f );	// .
	m_osc[6]->m_harmonic = log2f( 5.0f );	// .
	m_osc[7]->m_harmonic = log2f( 6.0f );	// .*/

	if( s_harmonics == NULL )
	{
		s_harmonics = new float[ NUM_HARMONICS ];
		s_harmonics[0] = log2f( 0.5f );
		s_harmonics[1] = log2f( 0.75f );
		s_harmonics[2] = log2f( 1.0f );
		s_harmonics[3] = log2f( 2.0f );
		s_harmonics[4] = log2f( 3.0f );
		s_harmonics[5] = log2f( 4.0f );
		s_harmonics[6] = log2f( 5.0f );
		s_harmonics[7] = log2f( 6.0f );
		s_harmonics[8] = log2f( 7.0f );
		s_harmonics[9] = log2f( 8.0f );
		s_harmonics[10] = log2f( 9.0f );
		s_harmonics[11] = log2f( 10.0f );
		s_harmonics[12] = log2f( 11.0f );
		s_harmonics[13] = log2f( 12.0f );
		s_harmonics[14] = log2f( 13.0f );
		s_harmonics[15] = log2f( 14.0f );
		s_harmonics[16] = log2f( 15.0f );
		s_harmonics[17] = log2f( 16.0f );
	}

	for (int i=0; i < m_numOscillators; i++) {
		m_osc[i]->updateVolume();
		m_osc[i]->updateDetuning();
	}

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

organicInstrument::~organicInstrument()
{
	delete[] m_osc;
}

void organicInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
	_this.setAttribute( "num_osc", QString::number( m_numOscillators ) );
	m_fx1Model.saveSettings( _doc, _this, "foldback" );
	m_volModel.saveSettings( _doc, _this, "vol" );

	for( int i = 0; i < m_numOscillators; ++i )
	{
		QString is = QString::number( i );
		m_osc[i]->m_volModel.saveSettings( _doc, _this, "vol" + is );
		m_osc[i]->m_panModel.saveSettings( _doc, _this, "pan" + is );
		m_osc[i]->m_harmModel.saveSettings( _doc, _this, "newharmonic" + is );

		m_osc[i]->m_detuneModel.saveSettings( _doc, _this, "newdetune"
									+ is );
		m_osc[i]->m_oscModel.saveSettings( _doc, _this, "wavetype"
									+ is );
	}
}

void organicInstrument::loadSettings( const QDomElement & _this )
{
//	m_numOscillators =  _this.attribute( "num_osc" ).
	//							toInt();

	for( int i = 0; i < m_numOscillators; ++i )
	{
		QString is = QString::number( i );
		m_osc[i]->m_volModel.loadSettings( _this, "vol" + is );
		if( _this.hasAttribute( "detune" + is ) )
		{
			m_osc[i]->m_detuneModel.setValue( _this.attribute( "detune" ).toInt() * 12 );
		}
		else
		{
			m_osc[i]->m_detuneModel.loadSettings( _this, "newdetune" + is );
		}
		m_osc[i]->m_panModel.loadSettings( _this, "pan" + is );
		m_osc[i]->m_oscModel.loadSettings( _this, "wavetype" + is );

		if( _this.hasAttribute( "newharmonic" + is ) )
		{
			m_osc[i]->m_harmModel.loadSettings( _this, "newharmonic" + is );
		}
		else
		{
			m_osc[i]->m_harmModel.setValue( static_cast<float>( i ) );
		}
	}

	m_volModel.loadSettings( _this, "vol" );
	m_fx1Model.loadSettings( _this, "foldback" );
}

QString organicInstrument::nodeName() const
{
	return( organic_plugin_descriptor.name );
}

void organicInstrument::playNote( NotePlayHandle * _n,
						sampleFrame * _working_buffer )
{
	const fpp_t frames = _n->framesLeftForCurrentPeriod();
	const f_cnt_t offset = _n->noteOffset();

	if( _n->totalFramesPlayed() == 0 || _n->m_pluginData == NULL )
	{
		Oscillator * oscs_l[NUM_OSCILLATORS];
		Oscillator * oscs_r[NUM_OSCILLATORS];

		_n->m_pluginData = new oscPtr;

		for( int i = m_numOscillators - 1; i >= 0; --i )
		{
			static_cast<oscPtr *>( _n->m_pluginData )->phaseOffsetLeft[i]
				= rand() / ( RAND_MAX + 1.0f );
			static_cast<oscPtr *>( _n->m_pluginData )->phaseOffsetRight[i]
				= rand() / ( RAND_MAX + 1.0f );

			// initialise ocillators

			if( i == m_numOscillators - 1 )
			{
				// create left oscillator
				oscs_l[i] = new Oscillator(
						&m_osc[i]->m_waveShape,
						&m_modulationAlgo,
						_n->frequency(),
						m_osc[i]->m_detuningLeft,
						static_cast<oscPtr *>( _n->m_pluginData )->phaseOffsetLeft[i],
						m_osc[i]->m_volumeLeft );
				// create right oscillator
				oscs_r[i] = new Oscillator(
						&m_osc[i]->m_waveShape,
						&m_modulationAlgo,
						_n->frequency(),
						m_osc[i]->m_detuningRight,
						static_cast<oscPtr *>( _n->m_pluginData )->phaseOffsetRight[i],
						m_osc[i]->m_volumeRight );
			}
			else
			{
				// create left oscillator
				oscs_l[i] = new Oscillator(
						&m_osc[i]->m_waveShape,
						&m_modulationAlgo,
						_n->frequency(),
						m_osc[i]->m_detuningLeft,
						static_cast<oscPtr *>( _n->m_pluginData )->phaseOffsetLeft[i],
						m_osc[i]->m_volumeLeft,
						oscs_l[i + 1] );
				// create right oscillator
				oscs_r[i] = new Oscillator(
						&m_osc[i]->m_waveShape,
						&m_modulationAlgo,
						_n->frequency(),
						m_osc[i]->m_detuningRight,
						static_cast<oscPtr *>( _n->m_pluginData )->phaseOffsetRight[i],
						m_osc[i]->m_volumeRight,
						oscs_r[i + 1] );
			}

		}

		static_cast<oscPtr *>( _n->m_pluginData )->oscLeft = oscs_l[0];
		static_cast<oscPtr *>( _n->m_pluginData )->oscRight = oscs_r[0];
	}

	Oscillator * osc_l = static_cast<oscPtr *>( _n->m_pluginData )->oscLeft;
	Oscillator * osc_r = static_cast<oscPtr *>( _n->m_pluginData)->oscRight;

	osc_l->update( _working_buffer + offset, frames, 0 );
	osc_r->update( _working_buffer + offset, frames, 1 );

	// -- fx section --

	// fxKnob is [0;1]
	float t =  m_fx1Model.value();

	for (int i=0 ; i < frames + offset ; i++)
	{
		_working_buffer[i][0] = waveshape( _working_buffer[i][0], t ) *
						m_volModel.value() / 100.0f;
		_working_buffer[i][1] = waveshape( _working_buffer[i][1], t ) *
						m_volModel.value() / 100.0f;
	}

	// -- --

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

void organicInstrument::deleteNotePluginData( NotePlayHandle * _n )
{
	delete static_cast<Oscillator *>( static_cast<oscPtr *>(
						_n->m_pluginData )->oscLeft );
	delete static_cast<Oscillator *>( static_cast<oscPtr *>(
						_n->m_pluginData )->oscRight );

	delete static_cast<oscPtr *>( _n->m_pluginData );
}

/*float inline organicInstrument::foldback(float in, float threshold)
{
  if (in>threshold || in<-threshold)
  {
    in= fabs(fabs(fmod(in - threshold, threshold*4)) - threshold*2) - threshold;
  }
  return in;
}
*/

float inline organicInstrument::waveshape(float in, float amount)
{
	float k = 2.0f * amount / ( 1.0f - amount );

	return( ( 1.0f + k ) * in / ( 1.0f + k * fabs( in ) ) );
}

void organicInstrument::randomiseSettings()
{

	for( int i = 0; i < m_numOscillators; i++ )
	{
		m_osc[i]->m_volModel.setValue( intRand( 0, 100 ) );

		m_osc[i]->m_detuneModel.setValue( intRand( -5, 5 ) );

		m_osc[i]->m_panModel.setValue( 0 );

		m_osc[i]->m_oscModel.setValue( intRand( 0, 5 ) );
	}

}

void organicInstrument::updateAllDetuning()
{
	for( int i = 0; i < m_numOscillators; ++i )
	{
		m_osc[i]->updateDetuning();
	}
}

int organicInstrument::intRand( int min, int max )
{
//	int randn = min+int((max-min)*rand()/(RAND_MAX + 1.0));
//	cout << randn << endl;
	int randn = ( rand() % (max - min) ) + min;
	return( randn );
}

PluginView * organicInstrument::instantiateView( QWidget * _parent )
{
	return( new organicInstrumentView( this, _parent ) );
}

class organicKnob : public Knob
{
public:
	organicKnob( QWidget * _parent ) :
		Knob( knobStyled, _parent )
	{
		setFixedSize( 21, 21 );
	}
};

organicInstrumentView::organicInstrumentView( Instrument * _instrument,
							QWidget * _parent ) :
	InstrumentViewFixedSize( _instrument, _parent ),
	m_oscKnobs( NULL )
{
	organicInstrument * oi = castModel<organicInstrument>();

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

	// setup knob for FX1
	m_fx1Knob = new organicKnob( this );
	m_fx1Knob->move( 15, 201 );
	m_fx1Knob->setFixedSize( 37, 47 );
	m_fx1Knob->setHintText( tr( "Distortion:" ), QString() );
	m_fx1Knob->setObjectName( "fx1Knob" );

	// setup volume-knob
	m_volKnob = new organicKnob( this );
	m_volKnob->setVolumeKnob( true );
	m_volKnob->move( 60, 201 );
	m_volKnob->setFixedSize( 37, 47 );
	m_volKnob->setHintText( tr( "Volume:" ), "%" );
	m_volKnob->setObjectName( "volKnob" );

	// randomise
	m_randBtn = new PixmapButton( this, tr( "Randomise" ) );
	m_randBtn->move( 148, 224 );
	m_randBtn->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
							"randomise_pressed" ) );
	m_randBtn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
								"randomise" ) );

	connect( m_randBtn, SIGNAL ( clicked() ),
					oi, SLOT( randomiseSettings() ) );

	if( s_artwork == NULL )
	{
		s_artwork = new QPixmap( PLUGIN_NAME::getIconPixmap(
								"artwork" ) );
	}

}

organicInstrumentView::~organicInstrumentView()
{
	delete[] m_oscKnobs;
}

void organicInstrumentView::modelChanged()
{
	organicInstrument * oi = castModel<organicInstrument>();

	const float y=91.0f;
	const float rowHeight = 26.0f;
	const float x=53.0f;
	const float colWidth = 24.0f;

	m_numOscillators = oi->m_numOscillators;

	m_fx1Knob->setModel( &oi->m_fx1Model );
	m_volKnob->setModel( &oi->m_volModel );

	if( m_oscKnobs != NULL )
	{
		delete[] m_oscKnobs;
	}

	m_oscKnobs = new OscillatorKnobs[ m_numOscillators ];

	// Create knobs, now that we know how many to make
	for( int i = 0; i < m_numOscillators; ++i )
	{
		// setup harmonic knob
		Knob * harmKnob = new organicKnob( this );
		harmKnob->move( x + i * colWidth, y - rowHeight );
		harmKnob->setObjectName( "harmKnob" );
		connect( &oi->m_osc[i]->m_harmModel, SIGNAL( dataChanged() ),
			this, SLOT( updateKnobHint() ) );

		// setup waveform-knob
		Knob * oscKnob = new organicKnob( this );
		oscKnob->move( x + i * colWidth, y );
		connect( &oi->m_osc[i]->m_oscModel, SIGNAL( dataChanged() ),
			this, SLOT( updateKnobHint() ) );

		oscKnob->setHintText( tr( "Osc %1 waveform:" ).arg( i + 1 ), QString() );

		// setup volume-knob
		Knob * volKnob = new Knob( knobStyled, this );
		volKnob->setVolumeKnob( true );
		volKnob->move( x + i * colWidth, y + rowHeight*1 );
		volKnob->setFixedSize( 21, 21 );
		volKnob->setHintText( tr( "Osc %1 volume:" ).arg(
								i + 1 ), "%" );

		// setup panning-knob
		Knob * panKnob = new organicKnob( this );
		panKnob->move( x + i  * colWidth, y + rowHeight*2 );
		panKnob->setHintText( tr("Osc %1 panning:").arg(
								i + 1 ), "" );

		// setup knob for fine-detuning
		Knob * detuneKnob = new organicKnob( this );
		detuneKnob->move( x + i * colWidth, y + rowHeight*3 );
		detuneKnob->setHintText( tr( "Osc %1 stereo detuning" ).arg( i + 1 )
							, " " +
							tr( "cents" ) );

		m_oscKnobs[i] = OscillatorKnobs( harmKnob, volKnob, oscKnob, panKnob, detuneKnob );

		// Attach to models
		m_oscKnobs[i].m_harmKnob->setModel( &oi->m_osc[i]->m_harmModel );
		m_oscKnobs[i].m_volKnob->setModel( &oi->m_osc[i]->m_volModel );
		m_oscKnobs[i].m_oscKnob->setModel( &oi->m_osc[i]->m_oscModel );
		m_oscKnobs[i].m_panKnob->setModel( &oi->m_osc[i]->m_panModel );
		m_oscKnobs[i].m_detuneKnob->setModel( &oi->m_osc[i]->m_detuneModel );
	}
	updateKnobHint();
}

void organicInstrumentView::updateKnobHint()
{
	organicInstrument * oi = castModel<organicInstrument>();
	for( int i = 0; i < m_numOscillators; ++i )
	{
		const float harm = oi->m_osc[i]->m_harmModel.value();
		const float wave = oi->m_osc[i]->m_oscModel.value();

		m_oscKnobs[i].m_harmKnob->setHintText( tr( "Osc %1 harmonic:" ).arg( i + 1 ), " (" +
			HARMONIC_NAMES[ static_cast<int>( harm ) ] + ")" );
		m_oscKnobs[i].m_oscKnob->setHintText( tr( "Osc %1 waveform:" ).arg( i + 1 ), " (" +
			WAVEFORM_NAMES[ static_cast<int>( wave ) ] + ")" );
	}
}

OscillatorObject::OscillatorObject( Model * _parent, int _index ) :
	Model( _parent ),
	m_waveShape( Oscillator::SineWave, 0, Oscillator::NumWaveShapes-1, this ),
	m_oscModel( 0.0f, 0.0f, 5.0f, 1.0f,
			this, tr( "Osc %1 waveform" ).arg( _index + 1 ) ),
	m_harmModel( static_cast<float>( _index ), 0.0f, 17.0f, 1.0f,
			this, tr( "Osc %1 harmonic" ).arg( _index + 1 ) ),
	m_volModel( 100.0f, 0.0f, 100.0f, 1.0f,
			this, tr( "Osc %1 volume" ).arg( _index + 1 ) ),
	m_panModel( DefaultPanning, PanningLeft, PanningRight, 1.0f,
			this, tr( "Osc %1 panning" ).arg( _index + 1 ) ),
	m_detuneModel( 0.0f, -1200.0f, 1200.0f, 1.0f,
			this, tr( "Osc %1 fine detuning left" ).arg( _index + 1 ) )
{
}

OscillatorObject::~OscillatorObject()
{
}

void OscillatorObject::oscButtonChanged()
{

	static Oscillator::WaveShapes shapes[] =
	{
		Oscillator::SineWave,
		Oscillator::SawWave,
		Oscillator::SquareWave,
		Oscillator::TriangleWave,
		Oscillator::MoogSawWave,
		Oscillator::ExponentialWave
	} ;

	m_waveShape.setValue( shapes[(int)roundf( m_oscModel.value() )] );

}

void OscillatorObject::updateVolume()
{
	m_volumeLeft = ( 1.0f - m_panModel.value() / (float)PanningRight )
			* m_volModel.value() / m_numOscillators / 100.0f;
	m_volumeRight = ( 1.0f + m_panModel.value() / (float)PanningRight )
			* m_volModel.value() / m_numOscillators / 100.0f;
}

void OscillatorObject::updateDetuning()
{
	m_detuningLeft = powf( 2.0f, organicInstrument::s_harmonics[ static_cast<int>( m_harmModel.value() ) ]
				+ (float)m_detuneModel.value() * CENT ) /
				Engine::mixer()->processingSampleRate();
	m_detuningRight = powf( 2.0f, organicInstrument::s_harmonics[ static_cast<int>( m_harmModel.value() ) ]
				- (float)m_detuneModel.value() * CENT ) /
				Engine::mixer()->processingSampleRate();
}

extern "C"
{

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

}

/*
 * some notes & ideas for the future of this plugin:
 *
 * - 32.692 Hz in the bass to 5919.85 Hz of treble in  a Hammond organ
 * => implement harmonic foldback
 *
 m_osc[i].m_oscModel->setInitValue( 0.0f );
 * - randomize preset
 */


code:
/*
 * organic.h - additive synthesizer for organ-like sounds
 *
 * Copyright (c) 2006-2015 Andreas Brandmaier <andy/at/brandmaier/dot/de>
 *
 * 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 ORGANIC_H
#define ORGANIC_H

#include <QString>

#include "Instrument.h"
#include "InstrumentView.h"
#include "Oscillator.h"
#include "AutomatableModel.h"

class QPixmap;

class Knob;
class NotePlayHandle;
class PixmapButton;

const int NUM_OSCILLATORS = 8;
const int NUM_HARMONICS = 18;
const QString HARMONIC_NAMES[NUM_HARMONICS] =  {
	"Octave below",
	"Fifth below",
	"Fundamental",
	"2nd harmonic",
	"3rd harmonic",
	"4th harmonic",
	"5th harmonic",
	"6th harmonic",
	"7th harmonic",
	"8th harmonic",
	"9th harmonic",
	"10th harmonic",
	"11th harmonic",
	"12th harmonic",
	"13th harmonic",
	"14th harmonic",
	"15th harmonic",
	"16th harmonic"
	};

const QString WAVEFORM_NAMES[6] = {
	"Sine wave",
	"Saw wave",
	"Square wave",
	"Triangle wave",
	"Moog saw wave",
	"Exponential wave"
	};

const float CENT = 1.0f / 1200.0f;

class OscillatorObject : public Model
{
	Q_OBJECT
	MM_OPERATORS
private:
	int m_numOscillators;
	IntModel m_waveShape;
	FloatModel m_oscModel;
	FloatModel m_harmModel;
	FloatModel m_volModel;
	FloatModel m_panModel;
	FloatModel m_detuneModel;

	float m_volumeLeft;
	float m_volumeRight;
	// normalized detuning -> x/sampleRate
	float m_detuningLeft;
	float m_detuningRight;
	// normalized offset -> x/360
	float m_phaseOffsetLeft;
	float m_phaseOffsetRight;

	OscillatorObject( Model * _parent, int _index );
	virtual ~OscillatorObject();

	friend class organicInstrument;
	friend class organicInstrumentView;

private slots:
	void oscButtonChanged();
	void updateVolume();
	void updateDetuning();

} ;

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

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

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

	virtual QString nodeName() const;

	int intRand( int min, int max );

	static float * s_harmonics;

public slots:
	void randomiseSettings();

private:
	float inline waveshape(float in, float amount);

	// fast atan, fast rather than accurate
	inline float fastatan( float x )
	{
    	return (x / (1.0 + 0.28 * (x * x)));
	}

	int m_numOscillators;

	OscillatorObject ** m_osc;

	struct oscPtr
	{
		MM_OPERATORS
		Oscillator * oscLeft;
		Oscillator * oscRight;
		float phaseOffsetLeft[NUM_OSCILLATORS];
		float phaseOffsetRight[NUM_OSCILLATORS];
	} ;

	const IntModel m_modulationAlgo;

	FloatModel  m_fx1Model;
	FloatModel  m_volModel;

	virtual PluginView * instantiateView( QWidget * _parent );

private slots:
	void updateAllDetuning();

	friend class organicInstrumentView;
} ;

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

private:
	virtual void modelChanged();

	struct OscillatorKnobs
	{
		MM_OPERATORS
		OscillatorKnobs(
					Knob * h,
					Knob * v,
					Knob * o,
					Knob * p,
					Knob * dt ) :
			m_harmKnob( h ),
			m_volKnob( v ),
			m_oscKnob( o ),
			m_panKnob( p ),
			m_detuneKnob( dt )
		{
		}
		OscillatorKnobs()
		{
		}

		Knob * m_harmKnob;
		Knob * m_volKnob;
		Knob * m_oscKnob;
		Knob * m_panKnob;
		Knob * m_detuneKnob;
	} ;

	OscillatorKnobs * m_oscKnobs;

	Knob * m_fx1Knob;
	Knob * m_volKnob;
	PixmapButton * m_randBtn;

	int m_numOscillators;

	static QPixmap * s_artwork;

protected slots:
	void updateKnobHint();
};

#endif


code:
INCLUDE(BuildPlugin)

BUILD_PLUGIN(patman patman.cpp patman.h MOCFILES patman.h EMBEDDED_RESOURCES *.png)


code:
/*
 * patman.cpp - a GUS-compatible patch instrument plugin
 *
 * Copyright (c) 2007-2008 Javier Serrano Polo <jasp00/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 "patman.h"

#include <QDragEnterEvent>
#include <QPainter>
#include <QDomElement>

#include "ConfigManager.h"
#include "endian_handling.h"
#include "Engine.h"
#include "FileDialog.h"
#include "gui_templates.h"
#include "InstrumentTrack.h"
#include "NotePlayHandle.h"
#include "PathUtil.h"
#include "PixmapButton.h"
#include "Song.h"
#include "StringPairDrag.h"
#include "ToolTip.h"
#include "Clipboard.h"

#include "embed.h"

#include "plugin_export.h"

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT patman_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"PatMan",
	QT_TRANSLATE_NOOP( "PluginBrowser",
				"GUS-compatible patch instrument" ),
	"Javier Serrano Polo <jasp00/at/users.sourceforge.net>",
	0x0100,
	Plugin::Instrument,
	new PluginPixmapLoader( "logo" ),
	"pat",
	NULL
} ;

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

}

patmanInstrument::patmanInstrument( InstrumentTrack * _instrument_track ) :
	Instrument( _instrument_track, &patman_plugin_descriptor ),
	m_loopedModel( true, this ),
	m_tunedModel( true, this )
{
}

patmanInstrument::~patmanInstrument()
{
	unloadCurrentPatch();
}

void patmanInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
	_this.setAttribute( "src", m_patchFile );
	m_loopedModel.saveSettings( _doc, _this, "looped" );
	m_tunedModel.saveSettings( _doc, _this, "tuned" );
}

void patmanInstrument::loadSettings( const QDomElement & _this )
{
	setFile( _this.attribute( "src" ), false );
	m_loopedModel.loadSettings( _this, "looped" );
	m_tunedModel.loadSettings( _this, "tuned" );
}

void patmanInstrument::loadFile( const QString & _file )
{
	setFile( _file );
}

QString patmanInstrument::nodeName( void ) const
{
	return( patman_plugin_descriptor.name );
}

void patmanInstrument::playNote( NotePlayHandle * _n,
						sampleFrame * _working_buffer )
{
	if( m_patchFile == "" )
	{
		return;
	}

	const fpp_t frames = _n->framesLeftForCurrentPeriod();
	const f_cnt_t offset = _n->noteOffset();

	if( !_n->m_pluginData )
	{
		selectSample( _n );
	}
	handle_data * hdata = (handle_data *)_n->m_pluginData;

	float play_freq = hdata->tuned ? _n->frequency() :
						hdata->sample->frequency();

	if( hdata->sample->play( _working_buffer + offset, hdata->state, frames,
					play_freq, m_loopedModel.value() ? SampleBuffer::LoopOn : SampleBuffer::LoopOff ) )
	{
		applyRelease( _working_buffer, _n );
		instrumentTrack()->processAudioBuffer( _working_buffer,
								frames + offset, _n );
	}
	else
	{
		memset( _working_buffer, 0, ( frames + offset ) * sizeof( sampleFrame ) );
	}
}

void patmanInstrument::deleteNotePluginData( NotePlayHandle * _n )
{
	handle_data * hdata = (handle_data *)_n->m_pluginData;
	sharedObject::unref( hdata->sample );
	delete hdata->state;
	delete hdata;
}

void patmanInstrument::setFile( const QString & _patch_file, bool _rename )
{
	if( _patch_file.size() <= 0 )
	{
		m_patchFile = QString();
		return;
	}

	// is current instrument-track-name equal to previous-filename??
	if( _rename &&
		( instrumentTrack()->name() ==
					QFileInfo( m_patchFile ).fileName() ||
				   	m_patchFile == "" ) )
	{
		// then set it to new one
		instrumentTrack()->setName( PathUtil::cleanName( _patch_file ) );
	}
	// else we don't touch the instrument-track-name, because the user
	// named it self

	m_patchFile = PathUtil::toShortestRelative( _patch_file );
	LoadErrors error = loadPatch( PathUtil::toAbsolute( _patch_file ) );
	if( error )
	{
		printf("Load error\n");
	}

	emit fileChanged();
}

patmanInstrument::LoadErrors patmanInstrument::loadPatch(
						const QString & _filename )
{
	unloadCurrentPatch();

	FILE * fd = fopen( _filename.toUtf8().constData() , "rb" );
	if( !fd )
	{
		perror( "fopen" );
		return( LoadOpen );
	}

	unsigned char header[239];

	if( fread( header, 1, 239, fd ) != 239 ||
			( memcmp( header, "GF1PATCH110\0ID#000002", 22 )
			&& memcmp( header, "GF1PATCH100\0ID#000002", 22 ) ) )
	{
		fclose( fd );
		return( LoadNotGUS );
	}

	if( header[82] != 1 && header[82] != 0 )
	{
		fclose( fd );
		return( LoadInstruments );
	}

	if( header[151] != 1 && header[151] != 0 )
	{
		fclose( fd );
		return( LoadLayers );
	}

	int sample_count = header[198];
	for( int i = 0; i < sample_count; ++i )
	{
		unsigned short tmpshort;

#define SKIP_BYTES( x ) \
		if ( fseek( fd, x, SEEK_CUR ) == -1 ) \
		{ \
			fclose( fd ); \
			return( LoadIO ); \
		}

#define READ_SHORT( x ) \
		if ( fread( &tmpshort, 2, 1, fd ) != 1 ) \
		{ \
			fclose( fd ); \
			return( LoadIO ); \
		} \
		x = (unsigned short)swap16IfBE( tmpshort );

#define READ_LONG( x ) \
		if ( fread( &x, 4, 1, fd ) != 1 ) \
		{ \
			fclose( fd ); \
			return( LoadIO ); \
		} \
		x = (unsigned)swap32IfBE( x );

		// skip wave name, fractions
		SKIP_BYTES( 7 + 1 );
		unsigned data_length;
		READ_LONG( data_length );
		unsigned loop_start;
		READ_LONG( loop_start );
		unsigned loop_end;
		READ_LONG( loop_end );
		unsigned sample_rate;
		READ_SHORT( sample_rate );
		// skip low_freq, high_freq
		SKIP_BYTES( 4 + 4 );
		unsigned root_freq;
		READ_LONG( root_freq );
		// skip tuning, panning, envelope, tremolo, vibrato
		SKIP_BYTES( 2 + 1 + 12 + 3 + 3 );
		unsigned char modes;
		if ( fread( &modes, 1, 1, fd ) != 1 )
		{
			fclose( fd );
			return( LoadIO );
		}
		// skip scale frequency, scale factor, reserved space
		SKIP_BYTES( 2 + 2 + 36 );

		f_cnt_t frames;
		sample_t * wave_samples;
		if( modes & MODES_16BIT )
		{
			frames = data_length >> 1;
			wave_samples = new sample_t[frames];
			for( f_cnt_t frame = 0; frame < frames; ++frame )
			{
				short sample;
				if ( fread( &sample, 2, 1, fd ) != 1 )
				{
					delete[] wave_samples;
					fclose( fd );
					return( LoadIO );
				}
				sample = swap16IfBE( sample );
				if( modes & MODES_UNSIGNED )
				{
					sample ^= 0x8000;
				}
				wave_samples[frame] = sample / 32767.0f;
			}

			loop_start >>= 1;
			loop_end >>= 1;
		}
		else
		{
			frames = data_length;
			wave_samples = new sample_t[frames];
			for( f_cnt_t frame = 0; frame < frames; ++frame )
			{
				char sample;
				if ( fread( &sample, 1, 1, fd ) != 1 )
				{
					delete[] wave_samples;
					fclose( fd );
					return( LoadIO );
				}
				if( modes & MODES_UNSIGNED )
				{
					sample ^= 0x80;
				}
				wave_samples[frame] = sample / 127.0f;
			}
		}

		sampleFrame * data = new sampleFrame[frames];

		for( f_cnt_t frame = 0; frame < frames; ++frame )
		{
			for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS;
									++chnl )
			{
				data[frame][chnl] = wave_samples[frame];
			}
		}

		SampleBuffer* psample = new SampleBuffer( data, frames );
		psample->setFrequency( root_freq / 1000.0f );
		psample->setSampleRate( sample_rate );

		if( modes & MODES_LOOPING )
		{
			psample->setLoopStartFrame( loop_start );
			psample->setLoopEndFrame( loop_end );
		}

		m_patchSamples.push_back( psample );

		delete[] wave_samples;
		delete[] data;
	}
	fclose( fd );
	return( LoadOK );
}

void patmanInstrument::unloadCurrentPatch( void )
{
	while( !m_patchSamples.empty() )
	{
		sharedObject::unref( m_patchSamples.back() );
		m_patchSamples.pop_back();
	}
}

void patmanInstrument::selectSample( NotePlayHandle * _n )
{
	const float freq = _n->frequency();

	float min_dist = HUGE_VALF;
	SampleBuffer* sample = NULL;

	for( QVector<SampleBuffer *>::iterator it = m_patchSamples.begin(); it != m_patchSamples.end(); ++it )
	{
		float patch_freq = ( *it )->frequency();
		float dist = freq >= patch_freq ? freq / patch_freq :
							patch_freq / freq;

		if( dist < min_dist )
		{
			min_dist = dist;
			sample = *it;
		}
	}

	handle_data * hdata = new handle_data;
	hdata->tuned = m_tunedModel.value();
	if( sample )
	{
		hdata->sample = sharedObject::ref( sample );
	}
	else
	{
		hdata->sample = new SampleBuffer( NULL, 0 );
	}
	hdata->state = new SampleBuffer::handleState( _n->hasDetuningInfo() );

	_n->m_pluginData = hdata;
}

PluginView * patmanInstrument::instantiateView( QWidget * _parent )
{
	return( new PatmanView( this, _parent ) );
}

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

	m_openFileButton = new PixmapButton( this, NULL );
	m_openFileButton->setObjectName( "openFileButton" );
	m_openFileButton->setCursor( QCursor( Qt::PointingHandCursor ) );
	m_openFileButton->move( 227, 86 );
	m_openFileButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
							"select_file_on" ) );
	m_openFileButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
							"select_file" ) );
	connect( m_openFileButton, SIGNAL( clicked() ),
				this, SLOT( openFile() ) );
	ToolTip::add( m_openFileButton, tr( "Open patch" ) );

	m_loopButton = new PixmapButton( this, tr( "Loop" ) );
	m_loopButton->setObjectName("loopButton");
	m_loopButton->setCheckable( true );
	m_loopButton->move( 195, 138 );
	m_loopButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
								"loop_on" ) );
	m_loopButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
								"loop_off" ) );
	ToolTip::add( m_loopButton, tr( "Loop mode" ) );

	m_tuneButton = new PixmapButton( this, tr( "Tune" ) );
	m_tuneButton->setObjectName("tuneButton");
	m_tuneButton->setCheckable( true );
	m_tuneButton->move( 223, 138 );
	m_tuneButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
								"tune_on" ) );
	m_tuneButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
								"tune_off" ) );
	ToolTip::add( m_tuneButton, tr( "Tune mode" ) );

	m_displayFilename = tr( "No file selected" );

	setAcceptDrops( true );
}

PatmanView::~PatmanView()
{
}

void PatmanView::openFile( void )
{
	FileDialog ofd( NULL, tr( "Open patch file" ) );
	ofd.setFileMode( FileDialog::ExistingFiles );

	QStringList types;
	types << tr( "Patch-Files (*.pat)" );
	ofd.setNameFilters( types );

	if( m_pi->m_patchFile == "" )
	{
		if( QDir( "/usr/share/midi/freepats" ).exists() )
		{
			ofd.setDirectory( "/usr/share/midi/freepats" );
		}
		else
		{
			ofd.setDirectory(
				ConfigManager::inst()->userSamplesDir() );
		}
	}
	else if( QFileInfo( m_pi->m_patchFile ).isRelative() )
	{
		QString f = ConfigManager::inst()->userSamplesDir()
							+ m_pi->m_patchFile;
		if( QFileInfo( f ).exists() == false )
		{
			f = ConfigManager::inst()->factorySamplesDir()
							+ m_pi->m_patchFile;
		}

		ofd.selectFile( f );
	}
	else
	{
		ofd.selectFile( m_pi->m_patchFile );
	}

	if( ofd.exec() == QDialog::Accepted && !ofd.selectedFiles().isEmpty() )
	{
		QString f = ofd.selectedFiles()[0];
		if( f != "" )
		{
			m_pi->setFile( f );
			Engine::getSong()->setModified();
		}
	}
}

void PatmanView::updateFilename( void )
{
 	m_displayFilename = "";
	int idx = m_pi->m_patchFile.length();

	QFontMetrics fm( pointSize<8>( font() ) );

	// simple algorithm for creating a text from the filename that
	// matches in the white rectangle
	while( idx > 0 && fm.size( Qt::TextSingleLine,
				m_displayFilename + "..." ).width() < 225 )
	{
		m_displayFilename = m_pi->m_patchFile[--idx] +
							m_displayFilename;
	}

	if( idx > 0 )
	{
		m_displayFilename = "..." + m_displayFilename;
	}

	update();
}

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

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

void PatmanView::dropEvent( QDropEvent * _de )
{
	QString type = StringPairDrag::decodeKey( _de );
	QString value = StringPairDrag::decodeValue( _de );
	if( type == "samplefile" )
	{
		m_pi->setFile( value );
		_de->accept();
		return;
	}

	_de->ignore();
}

void PatmanView::paintEvent( QPaintEvent * )
{
	QPainter p( this );

	p.setFont( pointSize<8>( font() ) );
	p.drawText( 8, 116, 235, 16,
			Qt::AlignLeft | Qt::TextSingleLine | Qt::AlignVCenter,
			m_displayFilename );
}

void PatmanView::modelChanged( void )
{
	m_pi = castModel<patmanInstrument>();
	m_loopButton->setModel( &m_pi->m_loopedModel );
	m_tuneButton->setModel( &m_pi->m_tunedModel );
	connect( m_pi, SIGNAL( fileChanged() ),
			this, SLOT( updateFilename() ) );
}


code:
/*
 * patman.h - header for a GUS-compatible patch instrument plugin
 *
 * Copyright (c) 2007-2008 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef PATMAN_H_
#define PATMAN_H_

#include "Instrument.h"
#include "InstrumentView.h"
#include "SampleBuffer.h"
#include "AutomatableModel.h"
#include "MemoryManager.h"

class PixmapButton;

#define MODES_16BIT	( 1 << 0 )
#define MODES_UNSIGNED	( 1 << 1 )
#define MODES_LOOPING	( 1 << 2 )
#define MODES_PINGPONG	( 1 << 3 )
#define MODES_REVERSE	( 1 << 4 )
#define MODES_SUSTAIN	( 1 << 5 )
#define MODES_ENVELOPE	( 1 << 6 )
#define MODES_CLAMPED	( 1 << 7 )

class patmanInstrument : public Instrument
{
	Q_OBJECT
public:
	patmanInstrument( InstrumentTrack * _track );
	virtual ~patmanInstrument();

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

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

	virtual void loadFile( const QString & _file );

	virtual QString nodeName( void ) const;

	virtual f_cnt_t desiredReleaseFrames( void ) const
	{
		return( 128 );
	}

	virtual PluginView * instantiateView( QWidget * _parent );

public slots:
	void setFile( const QString & _patch_file, bool _rename = true );

private:
	typedef struct
	{
		MM_OPERATORS
		SampleBuffer::handleState* state;
		bool tuned;
		SampleBuffer* sample;
	} handle_data;

	QString m_patchFile;
	QVector<SampleBuffer *> m_patchSamples;
	BoolModel m_loopedModel;
	BoolModel m_tunedModel;

	enum LoadErrors
	{
		LoadOK,
		LoadOpen,
		LoadNotGUS,
		LoadInstruments,
		LoadLayers,
		LoadIO
	} ;

	LoadErrors loadPatch( const QString & _filename );
	void unloadCurrentPatch( void );

	void selectSample( NotePlayHandle * _n );

	friend class PatmanView;

signals:
	void fileChanged( void );

} ;

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

public slots:
	void openFile( void );
	void updateFilename( void );

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

private:
	virtual void modelChanged( void );

	patmanInstrument * m_pi;
	QString m_displayFilename;

	PixmapButton * m_openFileButton;
	PixmapButton * m_loopButton;
	PixmapButton * m_tuneButton;

} ;

#endif


code:
INCLUDE(BuildPlugin)

BUILD_PLUGIN(peakcontrollereffect peak_controller_effect.cpp peak_controller_effect_controls.cpp peak_controller_effect_control_dialog.cpp peak_controller_effect.h peak_controller_effect_controls.h peak_controller_effect_control_dialog.h MOCFILES peak_controller_effect_controls.h peak_controller_effect_control_dialog.h EMBEDDED_RESOURCES artwork.png logo.png)


code:
/*
 * peak_controller_effect.cpp - PeakController effect plugin
 *
 * Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
 * 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 "Controller.h"
#include "Song.h"
#include "PresetPreviewPlayHandle.h"
#include "PeakController.h"
#include "peak_controller_effect.h"
#include "lmms_math.h"

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

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT peakcontrollereffect_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"Peak Controller",
	QT_TRANSLATE_NOOP( "PluginBrowser",
			"Plugin for controlling knobs with sound peaks" ),
	"Paul Giblock <drfaygo/at/gmail.com>",
	0x0100,
	Plugin::Effect,
	new PluginPixmapLoader("logo"),
	NULL,
	NULL
} ;

}

// We have to keep a list of all the PeakController effects so that we can save
// an peakEffect-ID to the project.  This ID is referenced in the PeakController
// settings and is used to set the PeakControllerEffect pointer upon load

//QVector<PeakControllerEffect *> PeakControllerEffect::s_effects;

PeakControllerEffect::PeakControllerEffect(
			Model * _parent,
			const Descriptor::SubPluginFeatures::Key * _key ) :
	Effect( &peakcontrollereffect_plugin_descriptor, _parent, _key ),
	m_effectId( rand() ),
	m_peakControls( this ),
	m_lastSample( 0 ),
	m_autoController( NULL )
{
	m_autoController = new PeakController( Engine::getSong(), this );
	if( !Engine::getSong()->isLoadingProject() && !PresetPreviewPlayHandle::isPreviewing() )
	{
		Engine::getSong()->addController( m_autoController );
	}
	PeakController::s_effects.append( this );
}

PeakControllerEffect::~PeakControllerEffect()
{
	int idx = PeakController::s_effects.indexOf( this );
	if( idx >= 0 )
	{
		PeakController::s_effects.remove( idx );
		Engine::getSong()->removeController( m_autoController );
	}
}

bool PeakControllerEffect::processAudioBuffer( sampleFrame * _buf,
							const fpp_t _frames )
{
	PeakControllerEffectControls & c = m_peakControls;

	// This appears to be used for determining whether or not to continue processing
	// audio with this effect
	if( !isEnabled() || !isRunning() )
	{
		return false;
	}

	// RMS:
	double sum = 0;

	if( c.m_absModel.value() )
	{
		for( int i = 0; i < _frames; ++i )
		{
			// absolute value is achieved because the squares are > 0
			sum += _buf[i][0]*_buf[i][0] + _buf[i][1]*_buf[i][1];
		}
	}
	else
	{
		for( int i = 0; i < _frames; ++i )
		{
			// the value is absolute because of squaring,
			// so we need to correct it
			sum += _buf[i][0] * _buf[i][0] * sign( _buf[i][0] )
				+ _buf[i][1] * _buf[i][1] * sign( _buf[i][1] );
		}
	}

	// TODO: flipping this might cause clipping
	// this will mute the output after the values were measured
	if( c.m_muteModel.value() )
	{
		for( int i = 0; i < _frames; ++i )
		{
			_buf[i][0] = _buf[i][1] = 0.0f;
		}
	}

	float curRMS = sqrt_neg( sum / _frames );
	const float tres = c.m_tresholdModel.value();
	const float amount = c.m_amountModel.value() * c.m_amountMultModel.value();
	curRMS = qAbs( curRMS ) < tres ? 0.0f : curRMS;
	m_lastSample = qBound( 0.0f, c.m_baseModel.value() + amount * curRMS, 1.0f );

	return isRunning();
}

extern "C"
{

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

}


code:
/*
 * peak_controller_Effect.h - PeakController effect plugin
 *
 * 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 PEAK_CONTROLLER_EFFECT_H
#define PEAK_CONTROLLER_EFFECT_H

#include "Effect.h"
#include "peak_controller_effect_controls.h"

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

	EffectControls * controls() override
	{
		return &m_peakControls;
	}

	float lastSample()
	{
		return m_lastSample;
	}

	PeakController * controller()
	{
		return m_autoController;
	}

	FloatModel * attackModel()
	{
		return &( m_peakControls.m_attackModel );
	}

	FloatModel * decayModel()
	{
		return &( m_peakControls.m_decayModel );
	}

	int m_effectId;

private:
	PeakControllerEffectControls m_peakControls;

	float m_lastSample;

	PeakController * m_autoController;

	friend class PeakControllerEffectControls;

} ;

#endif


code:
/*
 * peak_controller_effect_control_dialog.cpp - control dialog for
 *                                             PeakControllerEffect
 *
 * 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.
 *
 */

#include <QLayout>
#include <QLabel>

#include "peak_controller_effect_control_dialog.h"
#include "peak_controller_effect_controls.h"
#include "Knob.h"
#include "LedCheckbox.h"
#include "embed.h"

PeakControllerEffectControlDialog::PeakControllerEffectControlDialog(
				PeakControllerEffectControls * _controls ) :
	EffectControlDialog( _controls )
{
	setWindowIcon( embed::getIconPixmap( "controller" ) );
	setAutoFillBackground( true );
	QPalette pal;
	pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) );
	setPalette( pal );
	setFixedSize( 240, 80 );

	m_baseKnob = new Knob( knobBright_26, this );
	m_baseKnob->setLabel( tr( "BASE" ) );
	m_baseKnob->setModel( &_controls->m_baseModel );
	m_baseKnob->setHintText( tr( "Base:" ) , "" );

	m_amountKnob = new Knob( knobBright_26, this );
	m_amountKnob->setLabel( tr( "AMNT" ) );
	m_amountKnob->setModel( &_controls->m_amountModel );
	m_amountKnob->setHintText( tr( "Modulation amount:" ) , "" );

	m_amountMultKnob = new Knob( knobBright_26, this );
	m_amountMultKnob->setLabel( tr( "MULT" ) );
	m_amountMultKnob->setModel( &_controls->m_amountMultModel );
	m_amountMultKnob->setHintText( tr( "Amount multiplicator:" ) , "" );

	m_attackKnob = new Knob( knobBright_26, this );
	m_attackKnob->setLabel( tr( "ATCK" ) );
	m_attackKnob->setModel( &_controls->m_attackModel );
	m_attackKnob->setHintText( tr( "Attack:" ) , "" );

	m_decayKnob = new Knob( knobBright_26, this );
	m_decayKnob->setLabel( tr( "DCAY" ) );
	m_decayKnob->setModel( &_controls->m_decayModel );
	m_decayKnob->setHintText( tr( "Release:" ) , "" );

	m_tresholdKnob = new Knob( knobBright_26, this );
	m_tresholdKnob->setLabel( tr( "TRSH" ) );
	m_tresholdKnob->setModel( &_controls->m_tresholdModel );
	m_tresholdKnob->setHintText( tr( "Treshold:" ) , "" );

	m_muteLed = new LedCheckBox( tr( "Mute output" ), this );
	m_muteLed->setModel( &_controls->m_muteModel );

	m_absLed = new LedCheckBox( tr( "Absolute value" ), this );
	m_absLed->setModel( &_controls->m_absModel );

	QVBoxLayout * mainLayout = new QVBoxLayout();
	QHBoxLayout * knobLayout = new QHBoxLayout();
	QHBoxLayout * ledLayout = new QHBoxLayout();

	knobLayout->addWidget( m_baseKnob );
	knobLayout->addWidget( m_amountKnob );
	knobLayout->addWidget( m_amountMultKnob );
	knobLayout->addWidget( m_attackKnob );
	knobLayout->addWidget( m_decayKnob );
	knobLayout->addWidget( m_tresholdKnob );

	ledLayout->addWidget( m_muteLed );
	ledLayout->addWidget( m_absLed );

	mainLayout->setContentsMargins( 3, 10, 0, 0 );
	mainLayout->addLayout( knobLayout );
	mainLayout->addLayout( ledLayout );

	setLayout( mainLayout );
}


code:
/*
 * peak_controller_EffectControlDialog.h - control dialog for
 *                                           peakControllerEffect
 *
 * 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 _PEAK_CONTROLLER_EFFECT_CONTROL_DIALOG_H
#define _PEAK_CONTROLLER_EFFECT_CONTROL_DIALOG_H

#include "EffectControlDialog.h"

class PeakControllerEffectControls;
class Knob;
class LedCheckBox;

class PeakControllerEffectControlDialog : public EffectControlDialog
{
	Q_OBJECT
public:
	PeakControllerEffectControlDialog(
				PeakControllerEffectControls * _controls );
	virtual ~PeakControllerEffectControlDialog()
	{
	}

protected:
	Knob * m_baseKnob;
	Knob * m_amountKnob;
	Knob * m_attackKnob;
	Knob * m_decayKnob;
	Knob * m_tresholdKnob;
	LedCheckBox * m_muteLed;

	LedCheckBox * m_absLed;
	Knob * m_amountMultKnob;

} ;

#endif


code:
/*
 * peak_controller_effect_controls.cpp - controls for peakController effect
 *
 * Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
 * Copyright (c) 2009-2011 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 "PeakController.h"
#include "peak_controller_effect_controls.h"
#include "peak_controller_effect.h"
#include "Song.h"

PeakControllerEffectControls::
PeakControllerEffectControls( PeakControllerEffect * _eff ) :
	EffectControls( _eff ),
	m_effect( _eff ),
	m_baseModel( 0.5, 0.0, 1.0, 0.001, this, tr( "Base value" ) ),
	m_amountModel( 1.0, -1.0, 1.0, 0.005, this, tr( "Modulation amount" ) ),
	m_attackModel( 0, 0, 0.999, 0.001, this, tr( "Attack" ) ),
	m_decayModel( 0, 0, 0.999, 0.001, this, tr( "Release" ) ),
	m_tresholdModel( 0, 0, 1.0, 0.001, this, tr( "Treshold" ) ),
	m_muteModel( false, this, tr( "Mute output" ) ),
	m_absModel( true, this, tr("Absolute value") ),
	m_amountMultModel( 1.0, 0, 32, 0.2, this, tr("Amount multiplicator") )
{
}

void PeakControllerEffectControls::loadSettings( const QDomElement & _this )
{
	m_baseModel.loadSettings( _this, "base" );
	m_effect->m_lastSample = m_baseModel.value(); //Set initial Peak Controller output to Base
	m_amountModel.loadSettings( _this, "amount" );
	m_muteModel.loadSettings( _this, "mute" );

	m_attackModel.loadSettings( _this, "attack" );
	m_decayModel.loadSettings( _this, "decay" );

	m_absModel.loadSettings( _this, "abs" );
	m_amountMultModel.loadSettings( _this, "amountmult" );

	m_tresholdModel.loadSettings( _this, "treshold" );

	/*If the peak controller effect is NOT loaded from project,
	 * m_effectId stored is useless.
	 * Reason to assign a random number to it:
	 * If the user clone the instrument, and m_effectId is cloned, and
	 * m_effectId is copied, then there would be two instruments
	 * having the same id.
	 */
	if( Engine::getSong()->isLoadingProject() == true )
	{
		m_effect->m_effectId = _this.attribute( "effectId" ).toInt();
	}
	else
	{
		// TODO: Fix possible collision
		m_effect->m_effectId = rand();
	}
}

void PeakControllerEffectControls::saveSettings( QDomDocument & _doc,
							QDomElement & _this )
{
	_this.setAttribute( "effectId", m_effect->m_effectId );

	m_baseModel.saveSettings( _doc, _this, "base" );
	m_amountModel.saveSettings( _doc, _this, "amount" );
	m_muteModel.saveSettings( _doc, _this, "mute" );

	m_attackModel.saveSettings( _doc, _this, "attack" );
	m_decayModel.saveSettings( _doc, _this, "decay" );

	m_absModel.saveSettings( _doc,  _this, "abs" );
	m_amountMultModel.saveSettings( _doc, _this, "amountmult" );

	m_tresholdModel.saveSettings( _doc, _this, "treshold" );
}


code:
/*
 * peak_controller_EffectControls.h - controls for peakController effect
 *
 * Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
 * 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 _PEAK_CONTROLLER_EFFECT_CONTROLS_H
#define _PEAK_CONTROLLER_EFFECT_CONTROLS_H

#include "EffectControls.h"
#include "peak_controller_effect_control_dialog.h"
#include "Knob.h"

class PeakControllerEffect;

class PeakControllerEffectControls : public EffectControls
{
	Q_OBJECT
public:
	PeakControllerEffectControls( PeakControllerEffect * _eff );
	virtual ~PeakControllerEffectControls()
	{
	}

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

	int controlCount() override
	{
		return 1;
	}
	EffectControlDialog * createView() override
	{
		return new PeakControllerEffectControlDialog( this );
	}

private:
	PeakControllerEffect * m_effect;

	FloatModel m_baseModel;
	FloatModel m_amountModel;
	FloatModel m_attackModel;
	FloatModel m_decayModel;
	FloatModel m_tresholdModel;
	BoolModel m_muteModel;
	BoolModel m_absModel;
	FloatModel m_amountMultModel;

	friend class PeakControllerEffectControlDialog;
	friend class PeakControllerEffect;

} ;

#endif


code:
if(LMMS_HAVE_FLUIDSYNTH)
	INCLUDE(BuildPlugin)
	INCLUDE_DIRECTORIES(${FLUIDSYNTH_INCLUDE_DIRS} ${SAMPLERATE_INCLUDE_DIRS})
	LINK_DIRECTORIES(${FLUIDSYNTH_LIBRARY_DIRS} ${SAMPLERATE_LIBRARY_DIRS})
	LINK_LIBRARIES(${FLUIDSYNTH_LIBRARIES} ${SAMPLERATE_LIBRARIES})
	BUILD_PLUGIN(sf2player sf2_player.cpp sf2_player.h patches_dialog.cpp patches_dialog.h patches_dialog.ui MOCFILES sf2_player.h patches_dialog.h UICFILES patches_dialog.ui EMBEDDED_RESOURCES *.png)
endif(LMMS_HAVE_FLUIDSYNTH)


code:
/*
 * fluidsynthshims.h - a shim header for FluidSynth 2.0 API changes
 *
 * Copyright (c) 2018 Hyunjin Song <tteu.ingog@gmail.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 FLUIDSYNTHSHIMS_H
#define FLUIDSYNTHSHIMS_H

#include <fluidsynth.h>

#if FLUIDSYNTH_VERSION_MAJOR < 2

inline const char* fluid_preset_get_name(fluid_preset_t* preset)
{
	return preset->get_name(preset);
}

inline int fluid_preset_get_banknum(fluid_preset_t* preset)
{
	return preset->get_banknum(preset);
}

inline int fluid_preset_get_num(fluid_preset_t* preset)
{
	return preset->get_num(preset);
}

inline fluid_sfont_t* fluid_preset_get_sfont(fluid_preset_t* preset)
{
	return preset->sfont;
}

inline char* fluid_sfont_get_name(fluid_sfont_t* sfont)
{
	return sfont->get_name(sfont);
}

inline void fluid_sfont_iteration_start(fluid_sfont_t* sfont)
{
	sfont->iteration_start(sfont);
}

// Due to the API change, we can't simply shim the 'fluid_sfont_iteration_next' function
inline fluid_preset_t* fluid_sfont_iteration_next_wrapper(fluid_sfont_t* sfont, fluid_preset_t* preset)
{
	return sfont->iteration_next(sfont, preset) ? preset : nullptr;
}

#else // FLUIDSYNTH_VERSION_MAJOR < 2

#define FLUID_REVERB_DEFAULT_ROOMSIZE 0.2f
#define FLUID_REVERB_DEFAULT_DAMP 0.0f
#define FLUID_REVERB_DEFAULT_WIDTH 0.5f
#define FLUID_REVERB_DEFAULT_LEVEL 0.9f

#define FLUID_CHORUS_DEFAULT_N 3
#define FLUID_CHORUS_DEFAULT_LEVEL 2.0f
#define FLUID_CHORUS_DEFAULT_SPEED 0.3f
#define FLUID_CHORUS_DEFAULT_DEPTH 8.0f

inline fluid_preset_t* fluid_sfont_iteration_next_wrapper(fluid_sfont_t* sfont, fluid_preset_t*)
{
	return fluid_sfont_iteration_next(sfont);
}

#endif // FLUIDSYNTH_VERSION_MAJOR < 2

#endif // FLUIDSYNTHSHIMS_H