code:
/*
 * flangereffect.cpp - defination of FlangerEffect class.
 *
 * Copyright (c) 2014 David French <dave/dot/french3/at/googlemail/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 "FlangerEffect.h"
#include "Engine.h"

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

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT flanger_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"Flanger",
	QT_TRANSLATE_NOOP( "PluginBrowser", "A native flanger plugin" ),
	"Dave French <contact/dot/dave/dot/french3/at/googlemail/dot/com>",
	0x0100,
	Plugin::Effect,
	new PluginPixmapLoader("logo"),
	NULL,
	NULL
} ;

FlangerEffect::FlangerEffect( Model *parent, const Plugin::Descriptor::SubPluginFeatures::Key *key ) :
	Effect( &flanger_plugin_descriptor, parent, key ),
	m_flangerControls( this )
{
	m_lfo = new QuadratureLfo( Engine::mixer()->processingSampleRate() );
	m_lDelay = new MonoDelay( 1, Engine::mixer()->processingSampleRate() );
	m_rDelay = new MonoDelay( 1, Engine::mixer()->processingSampleRate() );
	m_noise = new Noise;
}

FlangerEffect::~FlangerEffect()
{
	if(m_lDelay )
	{
		delete m_lDelay;
	}
	if( m_rDelay )
	{
		delete m_rDelay;
	}
	if( m_lfo )
	{
		delete m_lfo;
	}
	if(m_noise)
	{
		delete m_noise;
	}
}

bool FlangerEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames )
{
	if( !isEnabled() || !isRunning () )
	{
		return( false );
	}
	double outSum = 0.0;
	const float d = dryLevel();
	const float w = wetLevel();
	const float length = m_flangerControls.m_delayTimeModel.value() * Engine::mixer()->processingSampleRate();
	const float noise = m_flangerControls.m_whiteNoiseAmountModel.value();
	float amplitude = m_flangerControls.m_lfoAmountModel.value() * Engine::mixer()->processingSampleRate();
	bool invertFeedback = m_flangerControls.m_invertFeedbackModel.value();
	m_lfo->setFrequency(  1.0/m_flangerControls.m_lfoFrequencyModel.value() );
	m_lDelay->setFeedback( m_flangerControls.m_feedbackModel.value() );
	m_rDelay->setFeedback( m_flangerControls.m_feedbackModel.value() );
	sample_t dryS[2];
	float leftLfo;
	float rightLfo;
	for( fpp_t f = 0; f < frames; ++f )
	{
		buf[f][0] += m_noise->tick() * noise;
		buf[f][1] += m_noise->tick() * noise;
		dryS[0] = buf[f][0];
		dryS[1] = buf[f][1];
		m_lfo->tick(&leftLfo, &rightLfo);
		m_lDelay->setLength( ( float )length + amplitude * (leftLfo+1.0)  );
		m_rDelay->setLength( ( float )length + amplitude * (rightLfo+1.0)  );
		if(invertFeedback)
		{
			m_lDelay->tick( &buf[f][1] );
			m_rDelay->tick(&buf[f][0] );
		} else
		{
			m_lDelay->tick( &buf[f][0] );
			m_rDelay->tick( &buf[f][1] );
		}

		buf[f][0] = ( d * dryS[0] ) + ( w * buf[f][0] );
		buf[f][1] = ( d * dryS[1] ) + ( w * buf[f][1] );
		outSum += buf[f][0]*buf[f][0] + buf[f][1]*buf[f][1];
	}
	checkGate( outSum / frames );
	return isRunning();
}

void FlangerEffect::changeSampleRate()
{
	m_lfo->setSampleRate( Engine::mixer()->processingSampleRate() );
	m_lDelay->setSampleRate( Engine::mixer()->processingSampleRate() );
	m_rDelay->setSampleRate( Engine::mixer()->processingSampleRate() );
}

void FlangerEffect::restartLFO()
{
	m_lfo->restart();
}

extern "C"
{

//needed for getting plugin out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main( Model* parent, void* data )
{
	return new FlangerEffect( parent , static_cast<const Plugin::Descriptor::SubPluginFeatures::Key *>( data ) );
}

}}


code:
/*
 * flangereffect.h - defination of FlangerEffect class.
 *
 * Copyright (c) 2014 David French <dave/dot/french3/at/googlemail/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 FLANGEREFFECT_H
#define FLANGEREFFECT_H

#include "Effect.h"
#include "FlangerControls.h"
#include "QuadratureLfo.h"
#include "MonoDelay.h"
#include "Noise.h"

class FlangerEffect : public Effect
{
public:
	FlangerEffect( Model* parent , const Descriptor::SubPluginFeatures::Key* key );
	virtual ~FlangerEffect();
	virtual bool processAudioBuffer( sampleFrame *buf, const fpp_t frames );
	virtual EffectControls* controls()
	{
		return &m_flangerControls;
	}
	void changeSampleRate();
	void restartLFO();

private:
	FlangerControls m_flangerControls;
	MonoDelay* m_lDelay;
	MonoDelay* m_rDelay;
	QuadratureLfo* m_lfo;
	Noise* m_noise;

};

#endif // FLANGEREFFECT_H

repo_name: CYBERDEViL/LMMS

license: mit


code:
/*
 * monodelay.cpp - defination of MonoDelay class.
 *
 * Copyright (c) 2014 David French <dave/dot/french3/at/googlemail/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 "MonoDelay.h"
#include "interpolation.h"
#include "lmms_math.h"
#include "string.h"

MonoDelay::MonoDelay( int maxTime , int sampleRate )
{
	m_buffer = 0;
	m_maxTime = maxTime;
	m_maxLength = maxTime * sampleRate;
	m_length = m_maxLength;

	m_writeIndex = 0;
	m_feedback = 0.0f;
	setSampleRate( sampleRate );
}

MonoDelay::~MonoDelay()
{
	if( m_buffer )
	{
		delete m_buffer;
	}
}

void MonoDelay::tick( sample_t* sample )
{
	m_writeIndex = ( m_writeIndex + 1 ) % ( int )m_maxLength;
	int readIndex = m_writeIndex - m_length;
	if (readIndex < 0 ) { readIndex += m_maxLength; }
	float out = m_buffer[ readIndex ];
	m_buffer[ m_writeIndex ] = *sample + ( out * m_feedback );
	*sample = out;
}

void MonoDelay::setSampleRate( int sampleRate )
{
	if( m_buffer )
	{
		delete m_buffer;
	}

	m_buffer = new sample_t[( int )( sampleRate * m_maxTime ) ];
	memset( m_buffer, 0, sizeof(float) * ( int )( sampleRate * m_maxTime ) );
}


code:
/*
 * monodelay.h - defination of MonoDelay class.
 *
 * Copyright (c) 2014 David French <dave/dot/french3/at/googlemail/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 MONODELAY_H
#define MONODELAY_H

#include "lmms_basics.h"

class MonoDelay
{
public:
	MonoDelay( int maxTime , int sampleRate );
	~MonoDelay();
	inline void setLength( float length )
	{
		if( length <= m_maxLength && length >= 0 )
		{
			m_length = length;
		}
	}

	inline void setFeedback( float feedback )
	{
		m_feedback = feedback;
	}

	void tick( sample_t* sample );
	void setSampleRate( int sampleRate );

private:
	sample_t* m_buffer;
	int m_maxLength;
	float m_length;
	int m_writeIndex;
	float m_feedback;
	float m_maxTime;
};

#endif // MONODELAY_H


code:
/*
 * noise.cpp - defination of Noise class.
 *
 * Copyright (c) 2014 David French <dave/dot/french3/at/googlemail/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 "Noise.h"
#include "lmms_math.h"

Noise::Noise()
{
	inv_randmax = 1.0/FAST_RAND_MAX; /* for range of 0 - 1.0 */
}

float Noise::tick()
{
	return (float) ((2.0 * fast_rand() * inv_randmax) - 1.0);
}


code:
/*
 * noise.h - defination of Noise class.
 *
 * Copyright (c) 2014 David French <dave/dot/french3/at/googlemail/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 NOISE_H
#define NOISE_H

class Noise
{
public:
	Noise();
	float tick();
private:
	double inv_randmax;
};

#endif // NOISE_H


code:
/*
 * quadraturelfo.cpp - defination of QuadratureLfo class.
 *
 * Copyright (c) 2014 David French <dave/dot/french3/at/googlemail/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 "QuadratureLfo.h"

QuadratureLfo::QuadratureLfo( int sampleRate )
{
	setSampleRate(sampleRate);
}

void QuadratureLfo::tick( float *s, float *c )
{
	*s = sinf( m_phase );
	*c = cosf( m_phase );
	m_phase += m_increment;

}


code:
/*
 * quadraturelfo.h - defination of QuadratureLfo class.
 *
 * Copyright (c) 2014 David French <dave/dot/french3/at/googlemail/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 QUADRATURELFO_H
#define QUADRATURELFO_H

#include "lmms_math.h"

class QuadratureLfo
{
public:
	QuadratureLfo( int sampleRate );
	~QuadratureLfo()
	{
	}

	inline void setFrequency( double frequency )
	{
		if( frequency < 0 || frequency > ( m_samplerate / 2.0 ) || frequency == m_frequency )
		{
			return;
		}
		m_frequency = frequency;
		m_increment = m_frequency * m_twoPiOverSr;

		if( m_phase >= F_2PI )
		{
			m_phase -= F_2PI;
		}
	}

	inline void restart()
	{
		m_phase = 0;
	}

	inline void setSampleRate ( int samplerate )
	{
		m_samplerate = samplerate;
		m_twoPiOverSr = F_2PI / samplerate;
		m_increment = m_frequency * m_twoPiOverSr;
	}

	void tick( float *s, float *c );

private:
	double m_frequency;
	double m_phase;
	double m_increment;
	double m_twoPiOverSr;
	int m_samplerate;

};

#endif // QUADRATURELFO_H


code:
INCLUDE(BuildPlugin)
INCLUDE_DIRECTORIES(game-music-emu/gme)

BUILD_PLUGIN(freeboy
	FreeBoy.cpp
	FreeBoy.h
	Gb_Apu_Buffer.cpp
	Gb_Apu_Buffer.h
	game-music-emu/gme/Gb_Apu.cpp
	game-music-emu/gme/Gb_Apu.h
	game-music-emu/gme/Gb_Oscs.cpp
	game-music-emu/gme/Blip_Buffer.cpp
	game-music-emu/gme/Gb_Oscs.h
	game-music-emu/gme/blargg_common.h
	game-music-emu/gme/Blip_Buffer.h
	game-music-emu/gme/Multi_Buffer.cpp
	game-music-emu/gme/blargg_source.h
	game-music-emu/gme/Multi_Buffer.h
	MOCFILES FreeBoy.h
	EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png"
)


code:
/*
 * FreeBoy.cpp - GameBoy papu based instrument
 *
 * Copyright (c) 2008 Attila Herman <attila589/at/gmail.com>
 *				Csaba Hruska <csaba.hruska/at/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.
 *
 */

#include <cmath>

#include <QPainter>
#include <QDomElement>
#include "FreeBoy.h"
#include "Gb_Apu_Buffer.h"
#include "Multi_Buffer.h"
#include "base64.h"
#include "InstrumentTrack.h"
#include "Knob.h"
#include "Mixer.h"
#include "NotePlayHandle.h"
#include "PixmapButton.h"
#include "ToolTip.h"
#include "Engine.h"
#include "Graph.h"

#include "embed.h"

#include "plugin_export.h"

const blip_time_t FRAME_LENGTH = 70224;
const long CLOCK_RATE = 4194304;

extern "C"
{
Plugin::Descriptor PLUGIN_EXPORT freeboy_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"FreeBoy",
	QT_TRANSLATE_NOOP( "PluginBrowser", "Emulation of GameBoy (TM) APU" ),

	"Attila Herman <attila589/at/gmail.com>"
	"Csaba Hruska <csaba.hruska/at/gmail.com>",
	0x0100,
	Plugin::Instrument,
	new PluginPixmapLoader( "logo" ),
	NULL
} ;

}

FreeBoyInstrument::FreeBoyInstrument( InstrumentTrack * _instrument_track ) :
	Instrument( _instrument_track, &freeboy_plugin_descriptor ),

	m_ch1SweepTimeModel( 4.0f, 0.0f, 7.0f, 1.0f, this, tr( "Sweep time" ) ),
	m_ch1SweepDirModel( false, this, tr( "Sweep direction" ) ),
	m_ch1SweepRtShiftModel( 4.0f, 0.0f, 7.0f, 1.0f, this,
						tr( "Sweep rate shift amount" ) ),
	m_ch1WavePatternDutyModel( 2.0f, 0.0f, 3.0f, 1.0f, this,
						tr( "Wave pattern duty cycle" ) ),
	m_ch1VolumeModel( 15.0f, 0.0f, 15.0f, 1.0f, this,
						tr( "Channel 1 volume" ) ),
	m_ch1VolSweepDirModel( false, this,
						tr( "Volume sweep direction" ) ),
	m_ch1SweepStepLengthModel( 0.0f, 0.0f, 7.0f, 1.0f, this,
						tr( "Length of each step in sweep" ) ),

	m_ch2WavePatternDutyModel( 2.0f, 0.0f, 3.0f, 1.0f, this,
						tr( "Wave pattern duty cycle" ) ),
	m_ch2VolumeModel( 15.0f, 0.0f, 15.0f, 1.0f, this,
						tr( "Channel 2 volume" ) ),
	m_ch2VolSweepDirModel( false, this,
						tr( "Volume sweep direction" ) ),
	m_ch2SweepStepLengthModel( 0.0f, 0.0f, 7.0f, 1.0f, this,
						tr( "Length of each step in sweep" ) ),

	//m_ch3OnModel( true, this, tr( "Channel 3 Master on/off" ) ),
	m_ch3VolumeModel( 3.0f, 0.0f, 3.0f, 1.0f, this,
						tr( "Channel 3 volume" ) ),

	m_ch4VolumeModel( 15.0f, 0.0f, 15.0f, 1.0f, this,
						tr( "Channel 4 volume" ) ),
	m_ch4VolSweepDirModel( false, this,
						tr( "Volume sweep direction" ) ),
	m_ch4SweepStepLengthModel( 0.0f, 0.0f, 7.0f, 1.0f, this,
						tr( "Length of each step in sweep" ) ),
	m_ch4ShiftRegWidthModel( false, this,
						tr( "Shift Register width" ) ),

	m_so1VolumeModel( 7.0f, 0.0f, 7.0f, 1.0f, this, tr( "Right output level") ),
	m_so2VolumeModel( 7.0f, 0.0f, 7.0f, 1.0f, this, tr( "Left output level" ) ),
	m_ch1So1Model( true, this, tr( "Channel 1 to SO2 (Left)" ) ),
	m_ch2So1Model( true, this, tr( "Channel 2 to SO2 (Left)" ) ),
	m_ch3So1Model( true, this, tr( "Channel 3 to SO2 (Left)" ) ),
	m_ch4So1Model( false, this, tr( "Channel 4 to SO2 (Left)" ) ),
	m_ch1So2Model( true, this, tr( "Channel 1 to SO1 (Right)" ) ),
	m_ch2So2Model( true, this, tr( "Channel 2 to SO1 (Right)" ) ),
	m_ch3So2Model( true, this, tr( "Channel 3 to SO1 (Right)" ) ),
	m_ch4So2Model( false, this, tr( "Channel 4 to SO1 (Right)" ) ),
	m_trebleModel( -20.0f, -100.0f, 200.0f, 1.0f, this, tr( "Treble" ) ),
	m_bassModel( 461.0f, -1.0f, 600.0f, 1.0f, this, tr( "Bass" ) ),

	m_graphModel( 0, 15, 32, this, false, 1 ),

	m_time(0)
{
}

FreeBoyInstrument::~FreeBoyInstrument()
{
}

void FreeBoyInstrument::saveSettings( QDomDocument & _doc,
							QDomElement & _this )
{
	m_ch1SweepTimeModel.saveSettings( _doc, _this, "st" );
	m_ch1SweepDirModel.saveSettings( _doc, _this, "sd" );
	m_ch1SweepRtShiftModel.saveSettings( _doc, _this, "srs" );
	m_ch1WavePatternDutyModel.saveSettings( _doc, _this, "ch1wpd" );
	m_ch1VolumeModel.saveSettings( _doc, _this, "ch1vol" );
	m_ch1VolSweepDirModel.saveSettings( _doc, _this, "ch1vsd" );
	m_ch1SweepStepLengthModel.saveSettings( _doc, _this, "ch1ssl" );

	m_ch2WavePatternDutyModel.saveSettings( _doc, _this, "ch2wpd" );
	m_ch2VolumeModel.saveSettings( _doc, _this, "ch2vol" );
	m_ch2VolSweepDirModel.saveSettings( _doc, _this, "ch2vsd" );
	m_ch2SweepStepLengthModel.saveSettings( _doc, _this, "ch2ssl" );

	//m_ch3OnModel.saveSettings( _doc, _this, "ch3on" );
	m_ch3VolumeModel.saveSettings( _doc, _this, "ch3vol" );

	m_ch4VolumeModel.saveSettings( _doc, _this, "ch4vol" );
	m_ch4VolSweepDirModel.saveSettings( _doc, _this, "ch4vsd" );
	m_ch4SweepStepLengthModel.saveSettings( _doc, _this, "ch4ssl" );
	m_ch4ShiftRegWidthModel.saveSettings( _doc, _this, "srw" );

	m_so1VolumeModel.saveSettings( _doc, _this, "so1vol" );
	m_so2VolumeModel.saveSettings( _doc, _this, "so2vol" );
	m_ch1So1Model.saveSettings( _doc, _this, "ch1so2" );
	m_ch2So1Model.saveSettings( _doc, _this, "ch2so2" );
	m_ch3So1Model.saveSettings( _doc, _this, "ch3so2" );
	m_ch4So1Model.saveSettings( _doc, _this, "ch4so2" );
	m_ch1So2Model.saveSettings( _doc, _this, "ch1so1" );
	m_ch2So2Model.saveSettings( _doc, _this, "ch2so1" );
	m_ch3So2Model.saveSettings( _doc, _this, "ch3so1" );
	m_ch4So2Model.saveSettings( _doc, _this, "ch4so1" );
	m_trebleModel.saveSettings( _doc, _this, "Treble" );
	m_bassModel.saveSettings( _doc, _this, "Bass" );

	QString sampleString;
	base64::encode( (const char *)m_graphModel.samples(),
		m_graphModel.length() * sizeof(float), sampleString );
	_this.setAttribute( "sampleShape", sampleString );
}

void FreeBoyInstrument::loadSettings( const QDomElement & _this )
{
	m_ch1SweepTimeModel.loadSettings( _this, "st" );
	m_ch1SweepDirModel.loadSettings( _this, "sd" );
	m_ch1SweepRtShiftModel.loadSettings( _this, "srs" );
	m_ch1WavePatternDutyModel.loadSettings( _this, "ch1wpd" );
	m_ch1VolumeModel.loadSettings( _this, "ch1vol" );
	m_ch1VolSweepDirModel.loadSettings( _this, "ch1vsd" );
	m_ch1SweepStepLengthModel.loadSettings( _this, "ch1ssl" );

	m_ch2WavePatternDutyModel.loadSettings( _this, "ch2wpd" );
	m_ch2VolumeModel.loadSettings( _this, "ch2vol" );
	m_ch2VolSweepDirModel.loadSettings( _this, "ch2vsd" );
	m_ch2SweepStepLengthModel.loadSettings( _this, "ch2ssl" );

	//m_ch3OnModel.loadSettings( _this, "ch3on" );
	m_ch3VolumeModel.loadSettings( _this, "ch3vol" );

	m_ch4VolumeModel.loadSettings( _this, "ch4vol" );
	m_ch4VolSweepDirModel.loadSettings( _this, "ch4vsd" );
	m_ch4SweepStepLengthModel.loadSettings( _this, "ch4ssl" );
	m_ch4ShiftRegWidthModel.loadSettings( _this, "srw" );

	m_so1VolumeModel.loadSettings( _this, "so1vol" );
	m_so2VolumeModel.loadSettings( _this, "so2vol" );
	m_ch1So1Model.loadSettings( _this, "ch1so2" );
	m_ch2So1Model.loadSettings( _this, "ch2so2" );
	m_ch3So1Model.loadSettings( _this, "ch3so2" );
	m_ch4So1Model.loadSettings( _this, "ch4so2" );
	m_ch1So2Model.loadSettings( _this, "ch1so1" );
	m_ch2So2Model.loadSettings( _this, "ch2so1" );
	m_ch3So2Model.loadSettings( _this, "ch3so1" );
	m_ch4So2Model.loadSettings( _this, "ch4so1" );
	m_trebleModel.loadSettings( _this, "Treble" );
	m_bassModel.loadSettings( _this, "Bass" );

	int size = 0;
	char * dst = 0;
	base64::decode( _this.attribute( "sampleShape"), &dst, &size );
	m_graphModel.setSamples( (float*) dst );
}

QString FreeBoyInstrument::nodeName() const
{
	return( freeboy_plugin_descriptor.name );
}

/*f_cnt_t FreeBoyInstrument::desiredReleaseFrames() const
{
	const float samplerate = Engine::mixer()->processingSampleRate();
	int maxrel = 0;
	for( int i = 0 ; i < 3 ; ++i )
	{
		if( maxrel < m_voice[i]->m_releaseModel.value() )
			maxrel = m_voice[i]->m_releaseModel.value();
	}

	return f_cnt_t( float(relTime[maxrel])*samplerate/1000.0 );
}*/

f_cnt_t FreeBoyInstrument::desiredReleaseFrames() const
{
	return f_cnt_t( 1000 );
}

void FreeBoyInstrument::playNote( NotePlayHandle * _n,
						sampleFrame * _working_buffer )
{
	const f_cnt_t tfp = _n->totalFramesPlayed();
	const int samplerate = Engine::mixer()->processingSampleRate();
	const fpp_t frames = _n->framesLeftForCurrentPeriod();
	const f_cnt_t offset = _n->noteOffset();

	int data = 0;
	int freq = _n->frequency();

	if ( tfp == 0 )
	{
		Gb_Apu_Buffer *papu = new Gb_Apu_Buffer();
		papu->set_sample_rate( samplerate, CLOCK_RATE );

		// Master sound circuitry power control
		papu->write_register( fakeClock(),  0xff26, 0x80 );

		data = m_ch1VolumeModel.value();
		data = data<<1;
		data += m_ch1VolSweepDirModel.value();
		data = data<<3;
		data += m_ch1SweepStepLengthModel.value();
		papu->write_register( fakeClock(),  0xff12, data );

		data = m_ch2VolumeModel.value();
		data = data<<1;
		data += m_ch2VolSweepDirModel.value();
		data = data<<3;
		data += m_ch2SweepStepLengthModel.value();
		papu->write_register( fakeClock(),  0xff17, data );

		//channel 4 - noise
		data = m_ch4VolumeModel.value();
		data = data<<1;
		data += m_ch4VolSweepDirModel.value();
		data = data<<3;
		data += m_ch4SweepStepLengthModel.value();
		papu->write_register( fakeClock(),  0xff21, data );

		_n->m_pluginData = papu;
	}

	Gb_Apu_Buffer *papu = static_cast<Gb_Apu_Buffer *>( _n->m_pluginData );

	papu->treble_eq( m_trebleModel.value() );
	papu->bass_freq( m_bassModel.value() );

	//channel 1 - square
	data = m_ch1SweepTimeModel.value();
	data = data<<1;
	data += m_ch1SweepDirModel.value();
	data = data << 3;
	data += m_ch1SweepRtShiftModel.value();
	papu->write_register( fakeClock(),  0xff10, data );

	data = m_ch1WavePatternDutyModel.value();
	data = data<<6;
	papu->write_register( fakeClock(),  0xff11, data );

	//channel 2 - square
	data = m_ch2WavePatternDutyModel.value();
	data = data<<6;
	papu->write_register( fakeClock(),  0xff16, data );

	//channel 3 - wave
	//data = m_ch3OnModel.value()?128:0;
	data = 128;
	papu->write_register( fakeClock(),  0xff1a, data );

	int ch3voldata[4] = { 0, 3, 2, 1 };
	data = ch3voldata[(int)m_ch3VolumeModel.value()];
	data = data<<5;
	papu->write_register( fakeClock(),  0xff1c, data );

	//controls
	data = m_so1VolumeModel.value();
	data = data<<4;
	data += m_so2VolumeModel.value();
	papu->write_register( fakeClock(),  0xff24, data );

	data = m_ch4So2Model.value()?128:0;
	data += m_ch3So2Model.value()?64:0;
	data += m_ch2So2Model.value()?32:0;
	data += m_ch1So2Model.value()?16:0;
	data += m_ch4So1Model.value()?8:0;
	data += m_ch3So1Model.value()?4:0;
	data += m_ch2So1Model.value()?2:0;
	data += m_ch1So1Model.value()?1:0;
	papu->write_register( fakeClock(),  0xff25, data );

	const float * wpm = m_graphModel.samples();

	for( char i=0; i<16; i++ )
	{
		data = (int)floor(wpm[i*2]) << 4;
		data += (int)floor(wpm[i*2+1]);
		papu->write_register( fakeClock(),  0xff30 + i, data );
	}

	if( ( freq >= 65 ) && ( freq <=4000 ) )
	{
		int initflag = (tfp==0)?128:0;
		// Hz = 4194304 / ( ( 2048 - ( 11-bit-freq ) ) << 5 )
		data = 2048 - ( ( 4194304 / freq )>>5 );
		if( tfp==0 )
		{
			papu->write_register( fakeClock(),  0xff13, data & 0xff );
			papu->write_register( fakeClock(),  0xff14, (data>>8) | initflag );
		}
		papu->write_register( fakeClock(),  0xff18, data & 0xff );
		papu->write_register( fakeClock(),  0xff19, (data>>8) | initflag );
		papu->write_register( fakeClock(),  0xff1d, data & 0xff );
		papu->write_register( fakeClock(),  0xff1e, (data>>8) | initflag );
	}

	if( tfp == 0 )
	{
		//PRNG Frequency = (1048576 Hz / (ratio + 1)) / 2 ^ (shiftclockfreq + 1)
		char sopt=0;
		char ropt=1;
		float fopt = 524288.0 / ( ropt * pow( 2.0, sopt + 1.0 ) );
		float f;
		for ( char s=0; s<16; s++ )
		for ( char r=0; r<8; r++ ) {
			f = 524288.0 / ( r * pow( 2.0, s + 1.0 ) );
			if( fabs( freq-fopt ) > fabs( freq-f ) ) {
				fopt = f;
				ropt = r;
				sopt = s;
			}
		}
		data = sopt;
		data = data << 1;
		data += m_ch4ShiftRegWidthModel.value();
		data = data << 3;
		data += ropt;
		papu->write_register( fakeClock(),  0xff22, data );

		//channel 4 init
		papu->write_register( fakeClock(),  0xff23, 128 );
	}

	int const buf_size = 2048;
	int framesleft = frames;
	int datalen = 0;
	blip_sample_t buf [buf_size*2];
	while( framesleft > 0 )
	{
		int avail = papu->samples_avail();
		if( avail <= 0 )
		{
			m_time = 0;
			papu->end_frame(FRAME_LENGTH);
			avail = papu->samples_avail();
		}
		datalen = framesleft>avail?avail:framesleft;
		datalen = datalen>buf_size?buf_size:datalen;

		long count = papu->read_samples( buf, datalen*2)/2;

		for( fpp_t frame = 0; frame < count; ++frame )
		{
			for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
			{
				sample_t s = float(buf[frame*2+ch])/32768.0;
				_working_buffer[frames-framesleft+frame+offset][ch] = s;
			}
		}
		framesleft -= count;
	}
	instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n );
}

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

PluginView * FreeBoyInstrument::instantiateView( QWidget * _parent )
{
	return( new FreeBoyInstrumentView( this, _parent ) );
}

class FreeBoyKnob : public Knob
{
public:
	FreeBoyKnob( QWidget * _parent ) :
			Knob( knobStyled, _parent )
	{
		setFixedSize( 30, 30 );
		setCenterPointX( 15.0 );
		setCenterPointY( 15.0 );
		setInnerRadius( 8 );
		setOuterRadius( 13 );
		setTotalAngle( 270.0 );
		setLineWidth( 1 );
		setOuterColor( QColor( 0xF1, 0xFF, 0x93 ) );
	}
};

FreeBoyInstrumentView::FreeBoyInstrumentView( Instrument * _instrument,
							QWidget * _parent ) :
	InstrumentViewFixedSize( _instrument, _parent )
{

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

	m_ch1SweepTimeKnob = new FreeBoyKnob( this );
	m_ch1SweepTimeKnob->setHintText( tr( "Sweep time:" ), "" );
	m_ch1SweepTimeKnob->move( 5 + 4*32, 106 );
	ToolTip::add( m_ch1SweepTimeKnob, tr( "Sweep time" ) );

	m_ch1SweepRtShiftKnob = new FreeBoyKnob( this );
	m_ch1SweepRtShiftKnob->setHintText( tr( "Sweep rate shift amount:" )
										, "" );
	m_ch1SweepRtShiftKnob->move( 5 + 3*32, 106 );
	ToolTip::add( m_ch1SweepRtShiftKnob, tr( "Sweep rate shift amount" ) );

	m_ch1WavePatternDutyKnob = new FreeBoyKnob( this );
	m_ch1WavePatternDutyKnob->setHintText( tr( "Wave pattern duty cycle:" )
									, "" );
	m_ch1WavePatternDutyKnob->move( 5 + 2*32, 106 );
	ToolTip::add( m_ch1WavePatternDutyKnob, tr( "Wave pattern duty cycle" ) );

	m_ch1VolumeKnob = new FreeBoyKnob( this );
	m_ch1VolumeKnob->setHintText( tr( "Square channel 1 volume:" )
								, "" );
	m_ch1VolumeKnob->move( 5, 106 );
	ToolTip::add( m_ch1VolumeKnob, tr( "Square channel 1 volume" ) );

	m_ch1SweepStepLengthKnob = new FreeBoyKnob( this );
	m_ch1SweepStepLengthKnob->setHintText( tr( "Length of each step in sweep:" )
									, "" );
	m_ch1SweepStepLengthKnob->move( 5 + 32, 106 );
	ToolTip::add( m_ch1SweepStepLengthKnob, tr( "Length of each step in sweep" ) );

	m_ch2WavePatternDutyKnob = new FreeBoyKnob( this );
	m_ch2WavePatternDutyKnob->setHintText( tr( "Wave pattern duty cycle:" )
									, "" );
	m_ch2WavePatternDutyKnob->move( 5 + 2*32, 155 );
	ToolTip::add( m_ch2WavePatternDutyKnob, tr( "Wave pattern duty cycle" ) );

	m_ch2VolumeKnob = new FreeBoyKnob( this );
	m_ch2VolumeKnob->setHintText( tr( "Square channel 2 volume:" )
							, "" );
	m_ch2VolumeKnob->move( 5, 155 );
	ToolTip::add( m_ch2VolumeKnob, tr( "Square channel 2 volume" ) );

	m_ch2SweepStepLengthKnob = new FreeBoyKnob( this );
	m_ch2SweepStepLengthKnob->setHintText( tr( "Length of each step in sweep:" )
									, "" );
	m_ch2SweepStepLengthKnob->move( 5 + 32, 155 );
	ToolTip::add( m_ch2SweepStepLengthKnob, tr( "Length of each step in sweep" ) );

	m_ch3VolumeKnob = new FreeBoyKnob( this );
	m_ch3VolumeKnob->setHintText( tr( "Wave pattern channel volume:" ), "" );
	m_ch3VolumeKnob->move( 5, 204 );
	ToolTip::add( m_ch3VolumeKnob, tr( "Wave pattern channel volume" ) );

	m_ch4VolumeKnob = new FreeBoyKnob( this );
	m_ch4VolumeKnob->setHintText( tr( "Noise channel volume:" ), "" );
	m_ch4VolumeKnob->move( 144, 155 );
	ToolTip::add( m_ch4VolumeKnob, tr( "Noise channel volume" ) );

	m_ch4SweepStepLengthKnob = new FreeBoyKnob( this );
	m_ch4SweepStepLengthKnob->setHintText( tr( "Length of each step in sweep:" )
									, "" );
	m_ch4SweepStepLengthKnob->move( 144 + 32, 155 );
	ToolTip::add( m_ch4SweepStepLengthKnob, tr( "Length of each step in sweep" ) );

	m_so1VolumeKnob = new FreeBoyKnob( this );
	m_so1VolumeKnob->setHintText( tr( "SO1 volume (Right):" ), "" );
	m_so1VolumeKnob->move( 5, 58 );
	ToolTip::add( m_so1VolumeKnob, tr( "SO1 volume (Right)" ) );

	m_so2VolumeKnob = new FreeBoyKnob( this );
	m_so2VolumeKnob->setHintText( tr( "SO2 volume (Left):" ), "" );
	m_so2VolumeKnob->move( 5 + 32, 58 );
	ToolTip::add( m_so2VolumeKnob, tr( "SO2 volume (Left)" ) );

	m_trebleKnob = new FreeBoyKnob( this );
	m_trebleKnob->setHintText( tr( "Treble:" ), "" );
	m_trebleKnob->move( 5 + 2*32, 58 );
	ToolTip::add( m_trebleKnob, tr( "Treble" ) );

	m_bassKnob = new FreeBoyKnob( this );
	m_bassKnob->setHintText( tr( "Bass:" ), "" );
	m_bassKnob->move( 5 + 3*32, 58 );
	ToolTip::add( m_bassKnob, tr( "Bass" ) );

	m_ch1SweepDirButton = new PixmapButton( this, NULL );
	m_ch1SweepDirButton->setCheckable( true );
	m_ch1SweepDirButton->move( 167, 108 );
	m_ch1SweepDirButton->setActiveGraphic(
							PLUGIN_NAME::getIconPixmap( "btn_down" ) );
	m_ch1SweepDirButton->setInactiveGraphic(
							PLUGIN_NAME::getIconPixmap( "btn_up" ) );
	ToolTip::add( m_ch1SweepDirButton, tr( "Sweep direction" ) );

	m_ch1VolSweepDirButton = new PixmapButton( this, NULL );
	m_ch1VolSweepDirButton->setCheckable( true );
	m_ch1VolSweepDirButton->move( 207, 108 );
	m_ch1VolSweepDirButton->setActiveGraphic(
								PLUGIN_NAME::getIconPixmap( "btn_up" ) );
	m_ch1VolSweepDirButton->setInactiveGraphic(
								PLUGIN_NAME::getIconPixmap( "btn_down" ) );
	ToolTip::add( m_ch1VolSweepDirButton, tr( "Volume sweep direction" ) );

	m_ch2VolSweepDirButton = new PixmapButton( this,
										tr( "Volume sweep direction" ) );
	m_ch2VolSweepDirButton->setCheckable( true );
	m_ch2VolSweepDirButton->move( 102, 156 );
	m_ch2VolSweepDirButton->setActiveGraphic(
								PLUGIN_NAME::getIconPixmap( "btn_up" ) );
	m_ch2VolSweepDirButton->setInactiveGraphic(
								PLUGIN_NAME::getIconPixmap( "btn_down" ) );
	ToolTip::add( m_ch2VolSweepDirButton, tr( "Volume sweep direction" ) );

	//m_ch3OnButton = new PixmapButton( this, NULL );
	//m_ch3OnButton->move( 176, 53 );

	m_ch4VolSweepDirButton = new PixmapButton( this,
										tr( "Volume sweep direction" ) );
	m_ch4VolSweepDirButton->setCheckable( true );
	m_ch4VolSweepDirButton->move( 207, 157 );
	m_ch4VolSweepDirButton->setActiveGraphic(
								PLUGIN_NAME::getIconPixmap( "btn_up" ) );
	m_ch4VolSweepDirButton->setInactiveGraphic(
								PLUGIN_NAME::getIconPixmap( "btn_down" ) );
	ToolTip::add( m_ch4VolSweepDirButton, tr( "Volume sweep direction" ) );

	m_ch4ShiftRegWidthButton = new PixmapButton( this, NULL );
	m_ch4ShiftRegWidthButton->setCheckable( true );
	m_ch4ShiftRegWidthButton->move( 207, 171 );
	m_ch4ShiftRegWidthButton->setActiveGraphic(
									PLUGIN_NAME::getIconPixmap( "btn_7" ) );
	m_ch4ShiftRegWidthButton->setInactiveGraphic(
									PLUGIN_NAME::getIconPixmap( "btn_15" ) );
	ToolTip::add( m_ch4ShiftRegWidthButton, tr( "Shift register width" ) );

	m_ch1So1Button = new PixmapButton( this, NULL );
	m_ch1So1Button->setCheckable( true );
	m_ch1So1Button->move( 208, 51 );
	m_ch1So1Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) );
	m_ch1So1Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") );
	ToolTip::add( m_ch1So1Button, tr( "Channel 1 to SO1 (Right)" ) );

	m_ch2So1Button = new PixmapButton( this, NULL );
	m_ch2So1Button->setCheckable( true );
	m_ch2So1Button->move( 208, 51 + 12 );
	m_ch2So1Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) );
	m_ch2So1Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") );
	ToolTip::add( m_ch2So1Button, tr( "Channel 2 to SO1 (Right)" ) );

	m_ch3So1Button = new PixmapButton( this, NULL );
	m_ch3So1Button->setCheckable( true );
	m_ch3So1Button->move( 208, 51 + 2*12 );
	m_ch3So1Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) );
	m_ch3So1Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") );
	ToolTip::add( m_ch3So1Button, tr( "Channel 3 to SO1 (Right)" ) );

	m_ch4So1Button = new PixmapButton( this, NULL );
	m_ch4So1Button->setCheckable( true );
	m_ch4So1Button->setChecked( false );
	m_ch4So1Button->move( 208, 51 + 3*12 );
	m_ch4So1Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) );
	m_ch4So1Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") );
	ToolTip::add( m_ch4So1Button, tr( "Channel 4 to SO1 (Right)" ) );

	m_ch1So2Button = new PixmapButton( this, NULL );
	m_ch1So2Button->setCheckable( true );
	m_ch1So2Button->move( 148, 51 );
	m_ch1So2Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) );
	m_ch1So2Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") );
	ToolTip::add( m_ch1So2Button, tr( "Channel 1 to SO2 (Left)" ) );

	m_ch2So2Button = new PixmapButton( this, NULL );
	m_ch2So2Button->setCheckable( true );
	m_ch2So2Button->move( 148, 51 + 12 );
	m_ch2So2Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) );
	m_ch2So2Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") );
	ToolTip::add( m_ch2So2Button, tr( "Channel 2 to SO2 (Left)" ) );

	m_ch3So2Button = new PixmapButton( this, NULL );
	m_ch3So2Button->setCheckable( true );
	m_ch3So2Button->move( 148, 51 + 2*12 );
	m_ch3So2Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) );
	m_ch3So2Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") );
	ToolTip::add( m_ch3So2Button, tr( "Channel 3 to SO2 (Left)" ) );

	m_ch4So2Button = new PixmapButton( this, NULL );
	m_ch4So2Button->setCheckable( true );
	m_ch4So2Button->setChecked( false );
	m_ch4So2Button->move( 148, 51 + 3*12 );
	m_ch4So2Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) );
	m_ch4So2Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") );
	ToolTip::add( m_ch4So2Button, tr( "Channel 4 to SO2 (Left)" ) );

	m_graph = new Graph( this );
	m_graph->setGraphStyle( Graph::NearestStyle );
	m_graph->setGraphColor( QColor(0x4E, 0x83, 0x2B) );
	m_graph->move( 37, 199 );
	m_graph->resize(208, 47);
	ToolTip::add( m_graph, tr( "Wave pattern graph" ) );
}

FreeBoyInstrumentView::~FreeBoyInstrumentView()
{
}

void FreeBoyInstrumentView::modelChanged()
{
	FreeBoyInstrument * p = castModel<FreeBoyInstrument>();

	m_ch1SweepTimeKnob->setModel( &p->m_ch1SweepTimeModel );
	m_ch1SweepDirButton->setModel( &p->m_ch1SweepDirModel );
	m_ch1SweepRtShiftKnob->setModel( &p->m_ch1SweepRtShiftModel );
	m_ch1WavePatternDutyKnob->setModel( &p->m_ch1WavePatternDutyModel );
	m_ch1VolumeKnob->setModel( &p->m_ch1VolumeModel );
	m_ch1VolSweepDirButton->setModel( &p->m_ch1VolSweepDirModel );
	m_ch1SweepStepLengthKnob->setModel( &p->m_ch1SweepStepLengthModel );

	m_ch2WavePatternDutyKnob->setModel( &p->m_ch2WavePatternDutyModel );
	m_ch2VolumeKnob->setModel( &p->m_ch2VolumeModel );
	m_ch2VolSweepDirButton->setModel( &p->m_ch2VolSweepDirModel );
	m_ch2SweepStepLengthKnob->setModel( &p->m_ch2SweepStepLengthModel );

	//m_ch3OnButton->setModel( &p->m_ch3OnModel );
	m_ch3VolumeKnob->setModel( &p->m_ch3VolumeModel );

	m_ch4VolumeKnob->setModel( &p->m_ch4VolumeModel );
	m_ch4VolSweepDirButton->setModel( &p->m_ch4VolSweepDirModel );
	m_ch4SweepStepLengthKnob->setModel( &p->m_ch4SweepStepLengthModel );
	m_ch4ShiftRegWidthButton->setModel( &p->m_ch4ShiftRegWidthModel );

	m_so1VolumeKnob->setModel( &p->m_so1VolumeModel );
	m_so2VolumeKnob->setModel( &p->m_so2VolumeModel );
	m_ch1So1Button->setModel( &p->m_ch1So1Model );
	m_ch2So1Button->setModel( &p->m_ch2So1Model );
	m_ch3So1Button->setModel( &p->m_ch3So1Model );
	m_ch4So1Button->setModel( &p->m_ch4So1Model );
	m_ch1So2Button->setModel( &p->m_ch1So2Model );
	m_ch2So2Button->setModel( &p->m_ch2So2Model );
	m_ch3So2Button->setModel( &p->m_ch3So2Model );
	m_ch4So2Button->setModel( &p->m_ch4So2Model );
	m_trebleKnob->setModel( &p->m_trebleModel );
	m_bassKnob->setModel( &p->m_bassModel );
	m_graph->setModel( &p->m_graphModel );
}

extern "C"
{

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

}


code:
/*
 * FreeBoyInstrument.h - GameBoy papu based instrument
 *
 * Copyright (c) 2008 <Attila Herman <attila589/at/gmail.com>
 *				Csaba Hruska <csaba.hruska/at/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 FREEBOY_H
#define FREEBOY_H

#include <QObject>
#include "Instrument.h"
#include "InstrumentView.h"
#include "Knob.h"
#include "Graph.h"
#include "Gb_Apu.h"

class FreeBoyInstrumentView;
class NotePlayHandle;
class PixmapButton;

class FreeBoyInstrument : public Instrument
{
	Q_OBJECT
public:

	FreeBoyInstrument( InstrumentTrack * _instrument_track );
	virtual ~FreeBoyInstrument();

	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;

	virtual f_cnt_t desiredReleaseFrames() const;

	virtual PluginView * instantiateView( QWidget * _parent );

/*public slots:
	void updateKnobHint();
	void updateKnobToolTip();*/

private:
	FloatModel m_ch1SweepTimeModel;
	BoolModel m_ch1SweepDirModel;
	FloatModel m_ch1SweepRtShiftModel;
	FloatModel m_ch1WavePatternDutyModel;
	FloatModel m_ch1VolumeModel;
	BoolModel m_ch1VolSweepDirModel;
	FloatModel m_ch1SweepStepLengthModel;

	FloatModel m_ch2WavePatternDutyModel;
	FloatModel m_ch2VolumeModel;
	BoolModel m_ch2VolSweepDirModel;
	FloatModel m_ch2SweepStepLengthModel;

	BoolModel m_ch3OnModel;
	FloatModel m_ch3VolumeModel;

	FloatModel m_ch4VolumeModel;
	BoolModel m_ch4VolSweepDirModel;
	FloatModel m_ch4SweepStepLengthModel;
	FloatModel m_ch4ShiftClockFreqModel;
	BoolModel m_ch4ShiftRegWidthModel;
	FloatModel m_ch4FreqDivRatioModel;

	FloatModel m_so1VolumeModel;
	FloatModel m_so2VolumeModel;
	BoolModel m_ch1So1Model;
	BoolModel m_ch2So1Model;
	BoolModel m_ch3So1Model;
	BoolModel m_ch4So1Model;
	BoolModel m_ch1So2Model;
	BoolModel m_ch2So2Model;
	BoolModel m_ch3So2Model;
	BoolModel m_ch4So2Model;
	FloatModel m_trebleModel;
	FloatModel m_bassModel;

	graphModel  m_graphModel;

	// Fake CPU timing
	blip_time_t m_time;
	blip_time_t fakeClock() { return m_time += 4; }

	friend class FreeBoyInstrumentView;
} ;

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

private:
	virtual void modelChanged();

	Knob * m_ch1SweepTimeKnob;
	PixmapButton * m_ch1SweepDirButton;
	Knob * m_ch1SweepRtShiftKnob;
	Knob * m_ch1WavePatternDutyKnob;
	Knob * m_ch1VolumeKnob;
	PixmapButton * m_ch1VolSweepDirButton;
	Knob * m_ch1SweepStepLengthKnob;

	Knob * m_ch2WavePatternDutyKnob;
	Knob * m_ch2VolumeKnob;
	PixmapButton * m_ch2VolSweepDirButton;
	Knob * m_ch2SweepStepLengthKnob;

	Knob * m_ch3VolumeKnob;

	Knob * m_ch4VolumeKnob;
	PixmapButton * m_ch4VolSweepDirButton;
	Knob * m_ch4SweepStepLengthKnob;
	PixmapButton * m_ch4ShiftRegWidthButton;

	Knob * m_so1VolumeKnob;
	Knob * m_so2VolumeKnob;
	PixmapButton * m_ch1So1Button;
	PixmapButton * m_ch2So1Button;
	PixmapButton * m_ch3So1Button;
	PixmapButton * m_ch4So1Button;
	PixmapButton * m_ch1So2Button;
	PixmapButton * m_ch2So2Button;
	PixmapButton * m_ch3So2Button;
	PixmapButton * m_ch4So2Button;
	Knob * m_trebleKnob;
	Knob * m_bassKnob;

	Graph * m_graph;

/*protected slots:
	void updateKnobHint();
	void updateKnobToolTip();*/
} ;

#endif


code:
/*
 * Gb_Apu_Buffer.cpp - Gb_Apu subclass which allows direct buffer access
 * Copyright (c) 2017 Tres Finocchiaro <tres.finocchiaro/at/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.
 *
 */
#include "Gb_Apu_Buffer.h"

Gb_Apu_Buffer::Gb_Apu_Buffer() {}
Gb_Apu_Buffer::~Gb_Apu_Buffer() {}

void Gb_Apu_Buffer::end_frame(blip_time_t end_time) {
	Gb_Apu::end_frame(end_time);
	m_buf.end_frame(end_time);
}

// Sets specified sample rate and clock rate in Multi_Buffer
blargg_err_t Gb_Apu_Buffer::set_sample_rate(long sample_rate, long clock_rate) {
	Gb_Apu_Buffer::output(m_buf.center(), m_buf.left(), m_buf.right());
	m_buf.clock_rate(clock_rate);
	return m_buf.set_sample_rate(sample_rate);
}

// Wrap Multi_Buffer::samples_avail()
long Gb_Apu_Buffer::samples_avail() const {
	return m_buf.samples_avail();
}

// Wrap Multi_Buffer::read_samples(...)
long Gb_Apu_Buffer::read_samples(sample_t* out, long count) {
	return m_buf.read_samples(out, count);
}

void Gb_Apu_Buffer::bass_freq(int freq) {
	m_buf.bass_freq(freq);
}


code:
/*
 * Gb_Apu_Buffer.cpp - Gb_Apu subclass which allows direct buffer access
 * Copyright (c) 2017 Tres Finocchiaro <tres.finocchiaro/at/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 GB_APU_BUFFER_H
#define GB_APU_BUFFER_H

#include "Gb_Apu.h"
#include "Multi_Buffer.h"
#include "MemoryManager.h"

class Gb_Apu_Buffer : public Gb_Apu {
	MM_OPERATORS
public:
	Gb_Apu_Buffer();
	~Gb_Apu_Buffer();

	void end_frame(blip_time_t);

	blargg_err_t set_sample_rate(long sample_rate, long clock_rate);
	long samples_avail() const;
	typedef blip_sample_t sample_t;
	long read_samples(sample_t* out, long count);
	void bass_freq(int freq);
private:
	Stereo_Buffer m_buf;
};

#endif


code:
if(LMMS_HAVE_GIG)
	INCLUDE(BuildPlugin)
	INCLUDE_DIRECTORIES(${GIG_INCLUDE_DIRS})

	# Disable C++11 on Clang until gig.h is patched
	IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
		SET(CMAKE_CXX_STANDARD 98)
	ENDIF()

	# Required for not crashing loading files with libgig
	SET(GCC_COVERAGE_COMPILE_FLAGS "-fexceptions")
	add_definitions(${GCC_COVERAGE_COMPILE_FLAGS})

	# disable deprecated check for mingw-x-libgig
	if(LMMS_BUILD_WIN32)
		SET(GCC_GIG_COMPILE_FLAGS "-Wno-deprecated")
		add_definitions(${GCC_GIG_COMPILE_FLAGS})
	endif(LMMS_BUILD_WIN32)

	LINK_DIRECTORIES(${GIG_LIBRARY_DIRS} ${SAMPLERATE_LIBRARY_DIRS})
	LINK_LIBRARIES(${GIG_LIBRARIES} ${SAMPLERATE_LIBRARIES})
	BUILD_PLUGIN(gigplayer GigPlayer.cpp GigPlayer.h PatchesDialog.cpp PatchesDialog.h PatchesDialog.ui MOCFILES GigPlayer.h PatchesDialog.h UICFILES PatchesDialog.ui EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png")
endif(LMMS_HAVE_GIG)


code:
/*
 * GigPlayer.cpp - a GIG player using libgig (based on Sf2 player plugin)
 *
 * Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
 * Copyright (c) 2009-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * A few lines of code taken from LinuxSampler (also GPLv2) where noted:
 * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck
 * Copyright (C) 2005-2008 Christian Schoenebeck
 * Copyright (C) 2009-2010 Christian Schoenebeck and Grigor Iliev
 *
 * 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 "GigPlayer.h"

#include <cstring>
#include <QDebug>
#include <QLayout>
#include <QLabel>
#include <QDomDocument>

#include "ConfigManager.h"
#include "endian_handling.h"
#include "Engine.h"
#include "FileDialog.h"
#include "InstrumentTrack.h"
#include "InstrumentPlayHandle.h"
#include "Knob.h"
#include "Mixer.h"
#include "NotePlayHandle.h"
#include "PathUtil.h"
#include "SampleBuffer.h"
#include "Song.h"

#include "PatchesDialog.h"
#include "ToolTip.h"
#include "LcdSpinBox.h"

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

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT gigplayer_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"GIG Player",
	QT_TRANSLATE_NOOP( "PluginBrowser", "Player for GIG files" ),
	"Garrett Wilson <g/at/floft/dot/net>",
	0x0100,
	Plugin::Instrument,
	new PluginPixmapLoader( "logo" ),
	"gig",
	NULL
} ;

}

GigInstrument::GigInstrument( InstrumentTrack * _instrument_track ) :
	Instrument( _instrument_track, &gigplayer_plugin_descriptor ),
	m_instance( NULL ),
	m_instrument( NULL ),
	m_filename( "" ),
	m_bankNum( 0, 0, 999, this, tr( "Bank" ) ),
	m_patchNum( 0, 0, 127, this, tr( "Patch" ) ),
	m_gain( 1.0f, 0.0f, 5.0f, 0.01f, this, tr( "Gain" ) ),
	m_interpolation( SRC_LINEAR ),
	m_RandomSeed( 0 ),
	m_currentKeyDimension( 0 )
{
	InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrument_track );
	Engine::mixer()->addPlayHandle( iph );

	updateSampleRate();

	connect( &m_bankNum, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) );
	connect( &m_patchNum, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) );
	connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) );
}

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

void GigInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
	_this.setAttribute( "src", m_filename );
	m_patchNum.saveSettings( _doc, _this, "patch" );
	m_bankNum.saveSettings( _doc, _this, "bank" );

	m_gain.saveSettings( _doc, _this, "gain" );
}

void GigInstrument::loadSettings( const QDomElement & _this )
{
	openFile( _this.attribute( "src" ), false );
	m_patchNum.loadSettings( _this, "patch" );
	m_bankNum.loadSettings( _this, "bank" );

	m_gain.loadSettings( _this, "gain" );

	updatePatch();
}

void GigInstrument::loadFile( const QString & _file )
{
	if( !_file.isEmpty() && QFileInfo( _file ).exists() )
	{
		openFile( _file, false );
		updatePatch();
		updateSampleRate();
	}
}

AutomatableModel * GigInstrument::childModel( const QString & _modelName )
{
	if( _modelName == "bank" )
	{
		return &m_bankNum;
	}
	else if( _modelName == "patch" )
	{
		return &m_patchNum;
	}

	qCritical() << "requested unknown model " << _modelName;

	return NULL;
}

QString GigInstrument::nodeName() const
{
	return gigplayer_plugin_descriptor.name;
}

void GigInstrument::freeInstance()
{
	QMutexLocker synthLock( &m_synthMutex );
	QMutexLocker notesLock( &m_notesMutex );

	if( m_instance != NULL )
	{
		delete m_instance;
		m_instance = NULL;

		// If we're changing instruments, we got to make sure that we
		// remove all pointers to the old samples and don't try accessing
		// that instrument again
		m_instrument = NULL;
		m_notes.clear();
	}
}

void GigInstrument::openFile( const QString & _gigFile, bool updateTrackName )
{
	emit fileLoading();

	// Remove the current instrument if one is selected
	freeInstance();

	{
		QMutexLocker locker( &m_synthMutex );

		try
		{
			m_instance = new GigInstance( PathUtil::toAbsolute( _gigFile ) );
			m_filename = PathUtil::toShortestRelative( _gigFile );
		}
		catch( ... )
		{
			m_instance = NULL;
			m_filename = "";
		}
	}

	emit fileChanged();

	if( updateTrackName == true )
	{
		instrumentTrack()->setName(PathUtil::cleanName( _gigFile ) );
		updatePatch();
	}
}

void GigInstrument::updatePatch()
{
	if( m_bankNum.value() >= 0 && m_patchNum.value() >= 0 )
	{
		getInstrument();
	}
}

QString GigInstrument::getCurrentPatchName()
{
	QMutexLocker locker( &m_synthMutex );

	if( m_instance == NULL )
	{
		return "";
	}

	int iBankSelected = m_bankNum.value();
	int iProgSelected = m_patchNum.value();

	gig::Instrument * pInstrument = m_instance->gig.GetFirstInstrument();

	while( pInstrument != NULL )
	{
		int iBank = pInstrument->MIDIBank;
		int iProg = pInstrument->MIDIProgram;

		if( iBank == iBankSelected && iProg == iProgSelected )
		{
			QString name = QString::fromStdString( pInstrument->pInfo->Name );

			if( name == "" )
			{
				name = "<no name>";
			}

			return name;
		}

		pInstrument = m_instance->gig.GetNextInstrument();
	}

	return "";
}

// A key has been pressed
void GigInstrument::playNote( NotePlayHandle * _n, sampleFrame * )
{
	const float LOG440 = 2.643452676f;

	const f_cnt_t tfp = _n->totalFramesPlayed();

	int midiNote = (int) floor( 12.0 * ( log2( _n->unpitchedFrequency() ) - LOG440 ) - 4.0 );

	// out of range?
	if( midiNote <= 0 || midiNote >= 128 )
	{
		return;
	}

	if( tfp == 0 )
	{
		GIGPluginData * pluginData = new GIGPluginData;
		pluginData->midiNote = midiNote;
		_n->m_pluginData = pluginData;

		const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity();
		const uint velocity = _n->midiVelocity( baseVelocity );

		QMutexLocker locker( &m_notesMutex );
		m_notes.push_back( GigNote( midiNote, velocity, _n->unpitchedFrequency(), pluginData ) );
	}
}

// Process the notes and output a certain number of frames (e.g. 256, set in
// the preferences)
void GigInstrument::play( sampleFrame * _working_buffer )
{
	const fpp_t frames = Engine::mixer()->framesPerPeriod();
	const int rate = Engine::mixer()->processingSampleRate();

	// Initialize to zeros
	std::memset( &_working_buffer[0][0], 0, DEFAULT_CHANNELS * frames * sizeof( float ) );

	m_synthMutex.lock();
	m_notesMutex.lock();

	if( m_instance == NULL || m_instrument == NULL )
	{
		m_synthMutex.unlock();
		m_notesMutex.unlock();
		return;
	}

	for( QList<GigNote>::iterator it = m_notes.begin(); it != m_notes.end(); ++it )
	{
		// Process notes in the KeyUp state, adding release samples if desired
		if( it->state == KeyUp )
		{
			// If there are no samples, we're done
			if( it->samples.empty() )
			{
				it->state = Completed;
			}
			else
			{
				it->state = PlayingKeyUp;

				// Notify each sample that the key has been released
				for( QList<GigSample>::iterator sample = it->samples.begin();
						sample != it->samples.end(); ++sample )
				{
					sample->adsr.keyup();
				}

				// Add release samples if available
				if( it->release == true )
				{
					addSamples( *it, true );
				}
			}
		}
		// Process notes in the KeyDown state, adding samples for the notes
		else if( it->state == KeyDown )
		{
			it->state = PlayingKeyDown;
			addSamples( *it, false );
		}

		// Delete ended samples
		for( QList<GigSample>::iterator sample = it->samples.begin();
				sample != it->samples.end(); ++sample )
		{
			// Delete if the ADSR for a sample is complete for normal
			// notes, or if a release sample, then if we've reached
			// the end of the sample
			if( sample->sample == NULL || sample->adsr.done() ||
				( it->isRelease == true &&
				  sample->pos >= sample->sample->SamplesTotal - 1 ) )
			{
				sample = it->samples.erase( sample );

				if( sample == it->samples.end() )
				{
					break;
				}
			}
		}

		// Delete ended notes (either in the completed state or all the samples ended)
		if( it->state == Completed || it->samples.empty() )
		{
			it = m_notes.erase( it );

			if( it == m_notes.end() )
			{
				break;
			}
		}
	}

	// Fill buffer with portions of the note samples
	for( QList<GigNote>::iterator it = m_notes.begin(); it != m_notes.end(); ++it )
	{
		// Only process the notes if we're in a playing state
		if( !( it->state == PlayingKeyDown ||
				it->state == PlayingKeyUp ) )
		{
			continue;
		}

		for( QList<GigSample>::iterator sample = it->samples.begin();
				sample != it->samples.end(); ++sample )
		{
			if( sample->sample == NULL || sample->region == NULL )
			{
				continue;
			}

			// Will change if resampling
			bool resample = false;
			f_cnt_t samples = frames; // How many to grab
			f_cnt_t used = frames; // How many we used
			float freq_factor = 1.0; // How to resample

			// Resample to be the correct pitch when the sample provided isn't
			// solely for this one note (e.g. one or two samples per octave) or
			// we are processing at a different sample rate
			if( sample->region->PitchTrack == true || rate != sample->sample->SamplesPerSecond )
			{
				resample = true;

				// Factor just for resampling
				freq_factor = 1.0 * rate / sample->sample->SamplesPerSecond;

				// Factor for pitch shifting as well as resampling
				if( sample->region->PitchTrack == true )
				{
					freq_factor *= sample->freqFactor;
				}

				// We need a bit of margin so we don't get glitching
				samples = frames / freq_factor + MARGIN[m_interpolation];
			}

			// Load this note's data
			sampleFrame sampleData[samples];
			loadSample( *sample, sampleData, samples );

			// Apply ADSR using a copy so if we don't use these samples when
			// resampling, the ADSR doesn't get messed up
			ADSR copy = sample->adsr;

			for( f_cnt_t i = 0; i < samples; ++i )
			{
				float amplitude = copy.value();
				sampleData[i][0] *= amplitude;
				sampleData[i][1] *= amplitude;
			}

			// Output the data resampling if needed
			if( resample == true )
			{
				sampleFrame convertBuf[frames];

				// Only output if resampling is successful (note that "used" is output)
				if( sample->convertSampleRate( *sampleData, *convertBuf, samples, frames,
							freq_factor, used ) )
				{
					for( f_cnt_t i = 0; i < frames; ++i )
					{
						_working_buffer[i][0] += convertBuf[i][0];
						_working_buffer[i][1] += convertBuf[i][1];
					}
				}
			}
			else
			{
				for( f_cnt_t i = 0; i < frames; ++i )
				{
					_working_buffer[i][0] += sampleData[i][0];
					_working_buffer[i][1] += sampleData[i][1];
				}
			}

			// Update note position with how many samples we actually used
			sample->pos += used;
			sample->adsr.inc( used );
		}
	}

	m_notesMutex.unlock();
	m_synthMutex.unlock();

	// Set gain properly based on volume control
	for( f_cnt_t i = 0; i < frames; ++i )
	{
		_working_buffer[i][0] *= m_gain.value();
		_working_buffer[i][1] *= m_gain.value();
	}

	instrumentTrack()->processAudioBuffer( _working_buffer, frames, NULL );
}

void GigInstrument::loadSample( GigSample& sample, sampleFrame* sampleData, f_cnt_t samples )
{
	if( sampleData == NULL || samples < 1 )
	{
		return;
	}

	// Determine if we need to loop part of this sample
	bool loop = false;
	gig::loop_type_t loopType = gig::loop_type_normal;
	f_cnt_t loopStart = 0;
	f_cnt_t loopLength = 0;

	if( sample.region->pSampleLoops != NULL )
	{
		for( uint32_t i = 0; i < sample.region->SampleLoops; ++i )
		{
			loop = true;
			loopType = static_cast<gig::loop_type_t>( sample.region->pSampleLoops[i].LoopType );
			loopStart = sample.region->pSampleLoops[i].LoopStart;
			loopLength = sample.region->pSampleLoops[i].LoopLength;

			// Currently only support at max one loop
			break;
		}
	}

	unsigned long allocationsize = samples * sample.sample->FrameSize;
	int8_t buffer[allocationsize];

	// Load the sample in different ways depending on if we're looping or not
	if( loop == true && ( sample.pos >= loopStart || sample.pos + samples > loopStart ) )
	{
		// Calculate the new position based on the type of loop
		if( loopType == gig::loop_type_bidirectional )
		{
			sample.pos = getPingPongIndex( sample.pos, loopStart, loopStart + loopLength );
		}
		else
		{
			sample.pos = getLoopedIndex( sample.pos, loopStart, loopStart + loopLength );
			// TODO: also implement loop_type_backward support
		}

		sample.sample->SetPos( sample.pos );

		// Load the samples (based on gig::Sample::ReadAndLoop) even around the end
		// of a loop boundary wrapping to the beginning of the loop region
		long samplestoread = samples;
		long samplestoloopend = 0;
		long readsamples = 0;
		long totalreadsamples = 0;
		long loopEnd = loopStart + loopLength;

		do
		{
			samplestoloopend = loopEnd - sample.sample->GetPos();
			readsamples = sample.sample->Read( &buffer[totalreadsamples * sample.sample->FrameSize],
					min( samplestoread, samplestoloopend ) );
			samplestoread -= readsamples;
			totalreadsamples += readsamples;

			if( readsamples >= samplestoloopend )
			{
				sample.sample->SetPos( loopStart );
			}
		}
		while( samplestoread > 0 && readsamples > 0 );
	}
	else
	{
		sample.sample->SetPos( sample.pos );

		unsigned long size = sample.sample->Read( &buffer, samples ) * sample.sample->FrameSize;
		std::memset( (int8_t*) &buffer + size, 0, allocationsize - size );
	}

	// Convert from 16 or 24 bit into 32-bit float
	if( sample.sample->BitDepth == 24 ) // 24 bit
	{
		uint8_t * pInt = reinterpret_cast<uint8_t*>( &buffer );

		for( f_cnt_t i = 0; i < samples; ++i )
		{
			// libgig gives 24-bit data as little endian, so we must
			// convert if on a big endian system
			int32_t valueLeft = swap32IfBE(
						( pInt[ 3 * sample.sample->Channels * i ] << 8 ) |
						( pInt[ 3 * sample.sample->Channels * i + 1 ] << 16 ) |
						( pInt[ 3 * sample.sample->Channels * i + 2 ] << 24 ) );

			// Store the notes to this buffer before saving to output
			// so we can fade them out as needed
			sampleData[i][0] = 1.0 / 0x100000000 * sample.attenuation * valueLeft;

			if( sample.sample->Channels == 1 )
			{
				sampleData[i][1] = sampleData[i][0];
			}
			else
			{
				int32_t valueRight = swap32IfBE(
							( pInt[ 3 * sample.sample->Channels * i + 3 ] << 8 ) |
							( pInt[ 3 * sample.sample->Channels * i + 4 ] << 16 ) |
							( pInt[ 3 * sample.sample->Channels * i + 5 ] << 24 ) );

				sampleData[i][1] = 1.0 / 0x100000000 * sample.attenuation * valueRight;
			}
		}
	}
	else // 16 bit
	{
		int16_t * pInt = reinterpret_cast<int16_t*>( &buffer );

		for( f_cnt_t i = 0; i < samples; ++i )
		{
			sampleData[i][0] = 1.0 / 0x10000 *
				pInt[ sample.sample->Channels * i ] * sample.attenuation;

			if( sample.sample->Channels == 1 )
			{
				sampleData[i][1] = sampleData[i][0];
			}
			else
			{
				sampleData[i][1] = 1.0 / 0x10000 *
					pInt[ sample.sample->Channels * i + 1 ] * sample.attenuation;
			}
		}
	}
}

// These two loop index functions taken from SampleBuffer.cpp
f_cnt_t GigInstrument::getLoopedIndex( f_cnt_t index, f_cnt_t startf, f_cnt_t endf ) const
{
	if( index < endf )
	{
		return index;
	}

	return startf + ( index - startf )
				% ( endf - startf );
}

f_cnt_t GigInstrument::getPingPongIndex( f_cnt_t index, f_cnt_t startf, f_cnt_t endf ) const
{
	if( index < endf )
	{
		return index;
	}

	const f_cnt_t looplen = endf - startf;
	const f_cnt_t looppos = ( index - endf ) % ( looplen * 2 );

	return ( looppos < looplen )
		? endf - looppos
		: startf + ( looppos - looplen );
}

// A key has been released
void GigInstrument::deleteNotePluginData( NotePlayHandle * _n )
{
	GIGPluginData * pluginData = static_cast<GIGPluginData *>( _n->m_pluginData );
	QMutexLocker locker( &m_notesMutex );

	// Mark the note as being released, but only if it was playing or was just
	// pressed (i.e., not if the key was already released)
	for( QList<GigNote>::iterator i = m_notes.begin(); i != m_notes.end(); ++i )
	{
		// Find the note by matching pointers to the plugin data
		if( i->handle == pluginData &&
				( i->state == KeyDown || i->state == PlayingKeyDown ) )
		{
			i->state = KeyUp;
		}
	}

	// TODO: not sample exact? What about in the middle of us writing out the sample?

	delete pluginData;
}

PluginView * GigInstrument::instantiateView( QWidget * _parent )
{
	return new GigInstrumentView( this, _parent );
}

// Add the desired samples (either the normal samples or the release samples)
// to the GigNote
//
// Note: not thread safe since libgig stores current region position data in
// the instrument object
void GigInstrument::addSamples( GigNote & gignote, bool wantReleaseSample )
{
	// Change key dimension, e.g. change samples based on what key is pressed
	// in a certain range. From LinuxSampler
	if( wantReleaseSample == true &&
			gignote.midiNote >= m_instrument->DimensionKeyRange.low &&
			gignote.midiNote <= m_instrument->DimensionKeyRange.high )
	{
		m_currentKeyDimension = float( gignote.midiNote -
				m_instrument->DimensionKeyRange.low ) / (
					m_instrument->DimensionKeyRange.high -
					m_instrument->DimensionKeyRange.low + 1 );
	}

	gig::Region* pRegion = m_instrument->GetFirstRegion();

	while( pRegion != NULL )
	{
		Dimension dim = getDimensions( pRegion, gignote.velocity, wantReleaseSample );
		gig::DimensionRegion * pDimRegion = pRegion->GetDimensionRegionByValue( dim.DimValues );
		gig::Sample * pSample = pDimRegion->pSample;

		// If this is a release sample, the note won't ever be
		// released, so we handle it differently
		gignote.isRelease = wantReleaseSample;

		// Does this note have release samples? Set this only on the original
		// notes and not when we get the release samples.
		if( wantReleaseSample != true )
		{
			gignote.release = dim.release;
		}

		if( pSample != NULL && pSample->SamplesTotal != 0 )
		{
			int keyLow = pRegion->KeyRange.low;
			int keyHigh = pRegion->KeyRange.high;

			if( gignote.midiNote >= keyLow && gignote.midiNote <= keyHigh )
			{
				float attenuation = pDimRegion->GetVelocityAttenuation( gignote.velocity );
				float length = (float) pSample->SamplesTotal / Engine::mixer()->processingSampleRate();

				// TODO: sample panning? crossfade different layers?

				if( wantReleaseSample == true )
				{
					// From LinuxSampler, not sure how it was created
					attenuation *= 1 - 0.01053 * ( 256 >> pDimRegion->ReleaseTriggerDecay ) * length;
				}
				else
				{
					attenuation *= pDimRegion->SampleAttenuation;
				}

				gignote.samples.push_back( GigSample( pSample, pDimRegion,
							attenuation, m_interpolation, gignote.frequency ) );
			}
		}

		pRegion = m_instrument->GetNextRegion();
	}
}

// Based on our input parameters, generate a "dimension" that specifies which
// note we wish to select from the GIG file with libgig. libgig will use this
// information to select the sample.
Dimension GigInstrument::getDimensions( gig::Region * pRegion, int velocity, bool release )
{
	Dimension dim;

	if( pRegion == NULL )
	{
		return dim;
	}

	for( int i = pRegion->Dimensions - 1; i >= 0; --i )
	{
		switch( pRegion->pDimensionDefinitions[i].dimension )
		{
			case gig::dimension_layer:
				// TODO: implement this
				dim.DimValues[i] = 0;
				break;
			case gig::dimension_velocity:
				dim.DimValues[i] = velocity;
				break;
			case gig::dimension_releasetrigger:
				dim.release = true;
				dim.DimValues[i] = (uint) release;
				break;
			case gig::dimension_keyboard:
				dim.DimValues[i] = (uint) ( m_currentKeyDimension * pRegion->pDimensionDefinitions[i].zones );
				break;
			case gig::dimension_roundrobin:
			case gig::dimension_roundrobinkeyboard:
				// TODO: implement this
				dim.DimValues[i] = 0;
				break;
			case gig::dimension_random:
				// From LinuxSampler, untested
				m_RandomSeed = m_RandomSeed * 1103515245 + 12345;
				dim.DimValues[i] = uint(
					m_RandomSeed / 4294967296.0f * pRegion->pDimensionDefinitions[i].bits );
				break;
			case gig::dimension_samplechannel:
			case gig::dimension_channelaftertouch:
			case gig::dimension_modwheel:
			case gig::dimension_breath:
			case gig::dimension_foot:
			case gig::dimension_portamentotime:
			case gig::dimension_effect1:
			case gig::dimension_effect2:
			case gig::dimension_genpurpose1:
			case gig::dimension_genpurpose2:
			case gig::dimension_genpurpose3:
			case gig::dimension_genpurpose4:
			case gig::dimension_sustainpedal:
			case gig::dimension_portamento:
			case gig::dimension_sostenutopedal:
			case gig::dimension_softpedal:
			case gig::dimension_genpurpose5:
			case gig::dimension_genpurpose6:
			case gig::dimension_genpurpose7:
			case gig::dimension_genpurpose8:
			case gig::dimension_effect1depth:
			case gig::dimension_effect2depth:
			case gig::dimension_effect3depth:
			case gig::dimension_effect4depth:
			case gig::dimension_effect5depth:
			case gig::dimension_none:
			default:
				dim.DimValues[i] = 0;
				break;
		}
	}

	return dim;
}

// Get the selected instrument from the GIG file we opened if we haven't gotten
// it already. This is based on the bank and patch numbers.
void GigInstrument::getInstrument()
{
	// Find instrument
	int iBankSelected = m_bankNum.value();
	int iProgSelected = m_patchNum.value();

	QMutexLocker locker( &m_synthMutex );

	if( m_instance != NULL )
	{
		gig::Instrument * pInstrument = m_instance->gig.GetFirstInstrument();

		while( pInstrument != NULL )
		{
			int iBank = pInstrument->MIDIBank;
			int iProg = pInstrument->MIDIProgram;

			if( iBank == iBankSelected && iProg == iProgSelected )
			{
				break;
			}

			pInstrument = m_instance->gig.GetNextInstrument();
		}

		m_instrument = pInstrument;
	}
}

// Since the sample rate changes when we start an export, clear all the
// currently-playing notes when we get this signal. Then, the export won't
// include leftover notes that were playing in the program.
void GigInstrument::updateSampleRate()
{
	QMutexLocker locker( &m_notesMutex );
	m_notes.clear();
}

class gigKnob : public Knob
{
public:
	gigKnob( QWidget * _parent ) :
			Knob( knobBright_26, _parent )
	{
		setFixedSize( 31, 38 );
	}
} ;

GigInstrumentView::GigInstrumentView( Instrument * _instrument, QWidget * _parent ) :
        InstrumentViewFixedSize( _instrument, _parent )
{
	GigInstrument * k = castModel<GigInstrument>();

	connect( &k->m_bankNum, SIGNAL( dataChanged() ), this, SLOT( updatePatchName() ) );
	connect( &k->m_patchNum, SIGNAL( dataChanged() ), this, SLOT( updatePatchName() ) );

	// File Button
	m_fileDialogButton = new PixmapButton( this );
	m_fileDialogButton->setCursor( QCursor( Qt::PointingHandCursor ) );
	m_fileDialogButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "fileselect_on" ) );
	m_fileDialogButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "fileselect_off" ) );
	m_fileDialogButton->move( 223, 68 );

	connect( m_fileDialogButton, SIGNAL( clicked() ), this, SLOT( showFileDialog() ) );

	ToolTip::add( m_fileDialogButton, tr( "Open GIG file" ) );

	// Patch Button
	m_patchDialogButton = new PixmapButton( this );
	m_patchDialogButton->setCursor( QCursor( Qt::PointingHandCursor ) );
	m_patchDialogButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "patches_on" ) );
	m_patchDialogButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "patches_off" ) );
	m_patchDialogButton->setEnabled( false );
	m_patchDialogButton->move( 223, 94 );

	connect( m_patchDialogButton, SIGNAL( clicked() ), this, SLOT( showPatchDialog() ) );

	ToolTip::add( m_patchDialogButton, tr( "Choose patch" ) );

	// LCDs
	m_bankNumLcd = new LcdSpinBox( 3, "21pink", this );
	m_bankNumLcd->move( 111, 150 );

	m_patchNumLcd = new LcdSpinBox( 3, "21pink", this );
	m_patchNumLcd->move( 161, 150 );

	// Next row
	m_filenameLabel = new QLabel( this );
	m_filenameLabel->setGeometry( 61, 70, 156, 14 );
	m_patchLabel = new QLabel( this );
	m_patchLabel->setGeometry( 61, 94, 156, 14 );

	// Gain
	m_gainKnob = new gigKnob( this );
	m_gainKnob->setHintText( tr( "Gain:" ) + " ", "" );
	m_gainKnob->move( 32, 140 );

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

	updateFilename();
}

GigInstrumentView::~GigInstrumentView()
{
}

void GigInstrumentView::modelChanged()
{
	GigInstrument * k = castModel<GigInstrument>();
	m_bankNumLcd->setModel( &k->m_bankNum );
	m_patchNumLcd->setModel( &k->m_patchNum );

	m_gainKnob->setModel( &k->m_gain );

	connect( k, SIGNAL( fileChanged() ), this, SLOT( updateFilename() ) );
	connect( k, SIGNAL( fileLoading() ), this, SLOT( invalidateFile() ) );

	updateFilename();
}

void GigInstrumentView::updateFilename()
{
	GigInstrument * i = castModel<GigInstrument>();
	QFontMetrics fm( m_filenameLabel->font() );
	QString file = i->m_filename.endsWith( ".gig", Qt::CaseInsensitive ) ?
			i->m_filename.left( i->m_filename.length() - 4 ) :
			i->m_filename;
	m_filenameLabel->setText( fm.elidedText( file, Qt::ElideLeft, m_filenameLabel->width() ) );

	m_patchDialogButton->setEnabled( !i->m_filename.isEmpty() );

	updatePatchName();
	update();
}

void GigInstrumentView::updatePatchName()
{
	GigInstrument * i = castModel<GigInstrument>();
	QFontMetrics fm( font() );
	QString patch = i->getCurrentPatchName();
	m_patchLabel->setText( fm.elidedText( patch, Qt::ElideLeft, m_patchLabel->width() ) );

	update();
}

void GigInstrumentView::invalidateFile()
{
	m_patchDialogButton->setEnabled( false );
}

void GigInstrumentView::showFileDialog()
{
	GigInstrument * k = castModel<GigInstrument>();

	FileDialog ofd( NULL, tr( "Open GIG file" ) );
	ofd.setFileMode( FileDialog::ExistingFiles );

	QStringList types;
	types << tr( "GIG Files (*.gig)" );
	ofd.setNameFilters( types );

	if( k->m_filename != "" )
	{
		QString f = PathUtil::toAbsolute( k->m_filename );
		ofd.setDirectory( QFileInfo( f ).absolutePath() );
		ofd.selectFile( QFileInfo( f ).fileName() );
	}
	else
	{
		ofd.setDirectory( ConfigManager::inst()->gigDir() );
	}

	m_fileDialogButton->setEnabled( false );

	if( ofd.exec() == QDialog::Accepted && !ofd.selectedFiles().isEmpty() )
	{
		QString f = ofd.selectedFiles()[0];

		if( f != "" )
		{
			k->openFile( f );
			Engine::getSong()->setModified();
		}
	}

	m_fileDialogButton->setEnabled( true );
}

void GigInstrumentView::showPatchDialog()
{
	GigInstrument * k = castModel<GigInstrument>();
	PatchesDialog pd( this );
	pd.setup( k->m_instance, 1, k->instrumentTrack()->name(), &k->m_bankNum, &k->m_patchNum, m_patchLabel );
	pd.exec();
}

// Store information related to playing a sample from the GIG file
GigSample::GigSample( gig::Sample * pSample, gig::DimensionRegion * pDimRegion,
		float attenuation, int interpolation, float desiredFreq )
	: sample( pSample ), region( pDimRegion ), attenuation( attenuation ),
	  pos( 0 ), interpolation( interpolation ), srcState( NULL ),
	  sampleFreq( 0 ), freqFactor( 1 )
{
	if( sample != NULL && region != NULL )
	{
		// Note: we don't create the libsamplerate object here since we always
		// also call the copy constructor when appending to the end of the
		// QList. We'll create it only in the copy constructor so we only have
		// to create it once.

		// Calculate note pitch and frequency factor only if we're actually
		// going to be changing the pitch of the notes
		if( region->PitchTrack == true )
		{
			// Calculate what frequency the provided sample is
			sampleFreq = 440.0 * powf( 2, 1.0 / 12 * (
						1.0 * region->UnityNote - 69 -
						0.01 * region->FineTune ) );
			freqFactor = sampleFreq / desiredFreq;
		}

		// The sample rate we pass in is affected by how we are going to be
		// resampling the note so that a 1.5 second release ends up being 1.5
		// seconds after resampling
		adsr = ADSR( region, sample->SamplesPerSecond / freqFactor );
	}
}

GigSample::~GigSample()
{
	if( srcState != NULL )
	{
		src_delete( srcState );
	}
}

GigSample::GigSample( const GigSample& g )
	: sample( g.sample ), region( g.region ), attenuation( g.attenuation ),
	  adsr( g.adsr ), pos( g.pos ), interpolation( g.interpolation ),
	  srcState( NULL ), sampleFreq( g.sampleFreq ), freqFactor( g.freqFactor )
{
	// On the copy, we want to create the object
	updateSampleRate();
}

GigSample& GigSample::operator=( const GigSample& g )
{
	sample = g.sample;
	region= g.region;
	attenuation = g.attenuation;
	adsr = g.adsr;
	pos = g.pos;
	interpolation = g.interpolation;
	srcState = NULL;
	sampleFreq = g.sampleFreq;
	freqFactor = g.freqFactor;

	if( g.srcState != NULL )
	{
		updateSampleRate();
	}

	return *this;
}

void GigSample::updateSampleRate()
{
	if( srcState != NULL )
	{
		src_delete( srcState );
	}

	int error = 0;
	srcState = src_new( interpolation, DEFAULT_CHANNELS, &error );

	if( srcState == NULL || error != 0 )
	{
		qCritical( "error while creating libsamplerate data structure in GigSample" );
	}
}

bool GigSample::convertSampleRate( sampleFrame & oldBuf, sampleFrame & newBuf,
		f_cnt_t oldSize, f_cnt_t newSize, float freq_factor, f_cnt_t& used )
{
	if( srcState == NULL )
	{
		return false;
	}

	SRC_DATA src_data;
	src_data.data_in = &oldBuf[0];
	src_data.data_out = &newBuf[0];
	src_data.input_frames = oldSize;
	src_data.output_frames = newSize;
	src_data.src_ratio = freq_factor;
	src_data.end_of_input = 0;

	// We don't need to lock this assuming that we're only outputting the
	// samples in one thread
	int error = src_process( srcState, &src_data );

	used = src_data.input_frames_used;

	if( error != 0 )
	{
		qCritical( "GigInstrument: error while resampling: %s", src_strerror( error ) );
		return false;
	}

	if( oldSize != 0 && src_data.output_frames_gen == 0 )
	{
		qCritical( "GigInstrument: could not resample, no frames generated" );
		return false;
	}

	if( src_data.output_frames_gen > 0 && src_data.output_frames_gen < newSize )
	{
		qCritical() << "GigInstrument: not enough frames, wanted"
			<< newSize << "generated" << src_data.output_frames_gen;
		return false;
	}

	return true;
}

ADSR::ADSR()
	: preattack( 0 ), attack( 0 ), decay1( 0 ), decay2( 0 ), infiniteSustain( false ),
	  sustain( 0 ), release( 0 ),
	  amplitude( 0 ), isAttack( true ), isRelease( false ), isDone( false ),
	  attackPosition( 0 ), attackLength( 0 ), decayLength( 0 ),
	  releasePosition( 0 ), releaseLength( 0 )
{
}

// Create the ADSR envelope from the settings in the GIG file
ADSR::ADSR( gig::DimensionRegion * region, int sampleRate )
	: preattack( 0 ), attack( 0 ), decay1( 0 ), decay2( 0 ), infiniteSustain( false ),
	  sustain( 0 ), release( 0 ),
	  amplitude( 0 ), isAttack( true ), isRelease( false ), isDone( false ),
	  attackPosition( 0 ), attackLength( 0 ), decayLength( 0 ),
	  releasePosition( 0 ), releaseLength( 0 )
{
	if( region != NULL )
	{
		// Parameters from GIG file
		preattack = 1.0 * region->EG1PreAttack / 1000; // EG1PreAttack is 0-1000 permille
		attack = region->EG1Attack;
		decay1 = region->EG1Decay1;
		decay2 = region->EG1Decay2;
		infiniteSustain = region->EG1InfiniteSustain;
		sustain = 1.0 * region->EG1Sustain / 1000; // EG1Sustain is 0-1000 permille
		release = region->EG1Release;

		// Simple ADSR using positions in sample
		amplitude = preattack;
		attackLength = attack * sampleRate;
		decayLength = decay1 * sampleRate; // TODO: ignoring decay2 for now
		releaseLength = release * sampleRate;

		// If there is no attack or decay, start at the sustain amplitude
		if( attackLength == 0 && decayLength == 0 )
		{
			amplitude = sustain;
		}
		// If there is no attack, start at the full amplitude
		else if( attackLength == 0 )
		{
			amplitude = 1.0;
		}
	}
}

// Next time we get the amplitude, we'll be releasing the note
void ADSR::keyup()
{
	isRelease = true;
}

// Can we delete the sample now?
bool ADSR::done()
{
	return isDone;
}

// Return the current amplitude and increment internal positions
float ADSR::value()
{
	float currentAmplitude = amplitude;

	// If we're done, don't output any signal
	if( isDone == true )
	{
		return 0;
	}
	// If we're still in the attack phase, release from the current volume
	// instead of jumping to the sustain volume and fading out
	else if( isAttack == true && isRelease == true )
	{
		sustain = amplitude;
		isAttack = false;
	}

	// If we're in the attack phase, start at the preattack amplitude and
	// increase to the full before decreasing to sustain
	if( isAttack == true )
	{
		if( attackPosition < attackLength )
		{
			amplitude = preattack + ( 1.0 - preattack ) / attackLength * attackPosition;
		}
		else if( attackPosition < attackLength + decayLength )
		{
			amplitude = 1.0 - ( 1.0 - sustain ) / decayLength * ( attackPosition - attackLength );
		}
		else
		{
			isAttack = false;
		}

		++attackPosition;
	}
	// If we're in the sustain phase, decrease from sustain to zero
	else if( isRelease == true )
	{
		// Maybe not the best way of doing this, but it appears to be about right
		// Satisfies f(0) = sustain and f(releaseLength) = very small
		amplitude = ( sustain + 1e-3 ) * expf( -5.0 / releaseLength * releasePosition ) - 1e-3;

		// Don't have an infinite exponential decay
		if( amplitude <= 0 || releasePosition >= releaseLength )
		{
			amplitude = 0;
			isDone = true;
		}

		++releasePosition;
	}

	return currentAmplitude;
}

// Increment internal positions a certain number of times
void ADSR::inc( f_cnt_t num )
{
	for( f_cnt_t i = 0; i < num; ++i )
	{
		value();
	}
}

extern "C"
{

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

}


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

#ifndef GIG_PLAYER_H
#define GIG_PLAYER_H

#include <QList>
#include <QMutex>
#include <QMutexLocker>
#include <samplerate.h>

#include "Instrument.h"
#include "PixmapButton.h"
#include "InstrumentView.h"
#include "Knob.h"
#include "LcdSpinBox.h"
#include "LedCheckbox.h"
#include "MemoryManager.h"
#include "gig.h"

class GigInstrumentView;
class NotePlayHandle;

class PatchesDialog;
class QLabel;

struct GIGPluginData
{
	int midiNote;
} ;

// Load a GIG file using libgig
class GigInstance
{
public:
	GigInstance( QString filename ) :
		riff( filename.toUtf8().constData() ),
		gig( &riff )
	{}

private:
	RIFF::File riff;

public:
	gig::File gig;
} ;

// Stores options for the notes, e.g. velocity and release time
struct Dimension
{
	Dimension() :
		release( false )
	{
		for( int i = 0; i < 8; ++i )
		{
			DimValues[i] = 0;
		}
	}

	uint DimValues[8];
	bool release;
} ;

// Takes information from the GIG file for a certain note and provides the
// amplitude (0-1) to multiply the signal by (internally incrementing the
// position in the envelope when asking for the amplitude).
class ADSR
{
	// From the file
	float preattack; // initial amplitude (0-1)
	float attack; // 0-60s
	float decay1; // 0-60s
	float decay2; // 0-60s
	bool infiniteSustain; // i.e., no decay2
	float sustain; // sustain amplitude (0-1)
	float release; // 0-60s

	// Used to calculate current amplitude
	float amplitude;
	bool isAttack;
	bool isRelease;
	bool isDone;
	f_cnt_t attackPosition;
	f_cnt_t attackLength;
	f_cnt_t decayLength;
	f_cnt_t releasePosition;
	f_cnt_t releaseLength;

public:
	ADSR();
	ADSR( gig::DimensionRegion * region, int sampleRate );
	void keyup(); // We will begin releasing starting now
	bool done(); // Is this sample done playing?
	float value(); // What's the current amplitude
	void inc( f_cnt_t num ); // Increment internal positions by num
} ;

// The sample from the GIG file with our current position in both the sample
// and the envelope
class GigSample
{
public:
	GigSample( gig::Sample * pSample, gig::DimensionRegion * pDimRegion,
			float attenuation, int interpolation, float desiredFreq );
	~GigSample();

	// Needed when initially creating in QList
	GigSample( const GigSample& g );
	GigSample& operator=( const GigSample& g );

	// Needed since libsamplerate stores data internally between calls
	void updateSampleRate();
	bool convertSampleRate( sampleFrame & oldBuf, sampleFrame & newBuf,
		f_cnt_t oldSize, f_cnt_t newSize, float freq_factor, f_cnt_t& used );

	gig::Sample * sample;
	gig::DimensionRegion * region;
	float attenuation;
	ADSR adsr;

	// The position in sample
	f_cnt_t pos;

	// Whether to change the pitch of the samples, e.g. if there's only one
	// sample per octave and you want that sample pitch shifted for the rest of
	// the notes in the octave, this will be true
	bool pitchtrack;

	// Used to convert sample rates
	int interpolation;
	SRC_STATE * srcState;

	// Used changing the pitch of the note if desired
	float sampleFreq;
	float freqFactor;
} ;

// What portion of a note are we in?
enum GigState
{
	// We just pressed the key
	KeyDown,
	// The note is currently playing
	PlayingKeyDown,
	// We just released the key
	KeyUp,
	// The note is being released, e.g. a release sample is playing
	PlayingKeyUp,
	// The note is done playing, you can delete this note now
	Completed
} ;

// Corresponds to a certain midi note pressed, but may contain multiple samples
class GigNote
{
public:
	int midiNote;
	int velocity;
	bool release; // Whether to trigger a release sample on key up
	bool isRelease; // Whether this is a release sample, changes when we delete it
	GigState state;
	float frequency;
	QList<GigSample> samples;

	// Used to determine which note should be released on key up
	//
	// Note: if accessing the data, be careful not to access it after the key
	// has been released since that's when it is deleted
	GIGPluginData * handle;

	GigNote( int midiNote, int velocity, float frequency, GIGPluginData * handle )
		: midiNote( midiNote ), velocity( velocity ),
		  release( false ), isRelease( false ), state( KeyDown ),
		  frequency( frequency ), handle( handle )
	{
	}
} ;

class GigInstrument : public Instrument
{
	Q_OBJECT
	MM_OPERATORS

	mapPropertyFromModel( int, getBank, setBank, m_bankNum );
	mapPropertyFromModel( int, getPatch, setPatch, m_patchNum );

public:
	GigInstrument( InstrumentTrack * _instrument_track );
	virtual ~GigInstrument();

	virtual void play( sampleFrame * _working_buffer );

	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 AutomatableModel * childModel( const QString & _modelName );

	virtual QString nodeName() const;

	virtual f_cnt_t desiredReleaseFrames() const
	{
		return 0;
	}

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

	virtual PluginView * instantiateView( QWidget * _parent );

	QString getCurrentPatchName();

	void setParameter( const QString & _param, const QString & _value );

public slots:
	void openFile( const QString & _gigFile, bool updateTrackName = true );
	void updatePatch();
	void updateSampleRate();

private:
	// The GIG file and instrument we're using
	GigInstance * m_instance;
	gig::Instrument * m_instrument;

	// Part of the UI
	QString m_filename;

	LcdSpinBoxModel m_bankNum;
	LcdSpinBoxModel m_patchNum;

	FloatModel m_gain;

	// Locking for the data
	QMutex m_synthMutex;
	QMutex m_notesMutex;

	// Used for resampling
	int m_interpolation;

	// List of all the currently playing notes
	QList<GigNote> m_notes;

	// Used when determining which samples to use
	uint32_t m_RandomSeed;
	float m_currentKeyDimension;

private:
	// Delete the current GIG instance if one is open
	void freeInstance();

	// Open the instrument in the currently-open GIG file
	void getInstrument();

	// Create "dimension" to select desired samples from GIG file based on
	// parameters such as velocity
	Dimension getDimensions( gig::Region * pRegion, int velocity, bool release );

	// Load sample data from the Gig file, looping the sample where needed
	void loadSample( GigSample& sample, sampleFrame* sampleData, f_cnt_t samples );
	f_cnt_t getLoopedIndex( f_cnt_t index, f_cnt_t startf, f_cnt_t endf ) const;
	f_cnt_t getPingPongIndex( f_cnt_t index, f_cnt_t startf, f_cnt_t endf ) const;

	// Add the desired samples to the note, either normal samples or release
	// samples
	void addSamples( GigNote & gignote, bool wantReleaseSample );

	friend class GigInstrumentView;

signals:
	void fileLoading();
	void fileChanged();
	void patchChanged();

} ;

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

private:
	virtual void modelChanged();

	PixmapButton * m_fileDialogButton;
	PixmapButton * m_patchDialogButton;

	LcdSpinBox * m_bankNumLcd;
	LcdSpinBox * m_patchNumLcd;

	QLabel * m_filenameLabel;
	QLabel * m_patchLabel;

	Knob * m_gainKnob;

	static PatchesDialog * s_patchDialog;

protected slots:
	void invalidateFile();
	void showFileDialog();
	void showPatchDialog();
	void updateFilename();
	void updatePatchName();
} ;

#endif


code:
/*
 * PatchesDialog.cpp - display GIG patches (based on Sf2 patches_dialog.cpp)
 *
 * 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 "PatchesDialog.h"

#include <QHeaderView>

// Custom list-view item (as for numerical sort purposes...)
class PatchItem : public QTreeWidgetItem
{
public:

	// Constructor.
	PatchItem( QTreeWidget *pListView,
		QTreeWidgetItem *pItemAfter )
		: QTreeWidgetItem( pListView, pItemAfter ) {}

	// Sort/compare overriden method.
	bool operator< ( const QTreeWidgetItem& other ) const
	{
		int iColumn = QTreeWidgetItem::treeWidget()->sortColumn();
		const QString& s1 = text( iColumn );
		const QString& s2 = other.text( iColumn );

		if( iColumn == 0 || iColumn == 2 )
		{
			return s1.toInt() < s2.toInt();
		}
		else
		{
			return s1 < s2;
		}
	}
};

// Constructor.
PatchesDialog::PatchesDialog( QWidget * pParent, Qt::WindowFlags wflags )
	: QDialog( pParent, wflags )
{
	// Setup UI struct...
	setupUi( this );

	m_pSynth = NULL;
	m_iChan  = 0;
	m_iBank  = 0;
	m_iProg  = 0;

	// Soundfonts list view...
	QHeaderView * pHeader = m_progListView->header();
	pHeader->setDefaultAlignment( Qt::AlignLeft );
	pHeader->setSectionsMovable( false );
	pHeader->setStretchLastSection( true );

	m_progListView->resizeColumnToContents( 0 );	// Prog.

	// Initial sort order...
	m_bankListView->sortItems( 0, Qt::AscendingOrder );
	m_progListView->sortItems( 0, Qt::AscendingOrder );

	// UI connections...
	QObject::connect( m_bankListView,
		SIGNAL( currentItemChanged( QTreeWidgetItem *,QTreeWidgetItem * ) ),
		SLOT( bankChanged() ) );
	QObject::connect( m_progListView,
		SIGNAL( currentItemChanged( QTreeWidgetItem *, QTreeWidgetItem * ) ),
		SLOT( progChanged( QTreeWidgetItem *, QTreeWidgetItem * ) ) );
	QObject::connect( m_progListView,
		SIGNAL( itemActivated( QTreeWidgetItem *, int ) ),
		SLOT( accept() ) );
	QObject::connect( m_okButton,
		SIGNAL( clicked() ),
		SLOT( accept() ) );
	QObject::connect( m_cancelButton,
		SIGNAL( clicked() ),
		SLOT( reject() ) );
}

// Destructor.
PatchesDialog::~PatchesDialog()
{
}

// Dialog setup loader.
void PatchesDialog::setup( GigInstance * pSynth, int iChan,
						const QString & chanName,
						LcdSpinBoxModel * bankModel,
						LcdSpinBoxModel * progModel,
						QLabel * patchLabel )
{

	// We'll going to changes the whole thing...
	m_dirty = 0;
	m_bankModel = bankModel;
	m_progModel = progModel;
	m_patchLabel =  patchLabel;

	// Set the proper caption...
	setWindowTitle( chanName + " - GIG patches" );

	// set m_pSynth to NULL so we don't trigger any progChanged events
	m_pSynth = NULL;

	// Load bank list from actual synth stack...
	m_bankListView->setSortingEnabled( false );
	m_bankListView->clear();

	// now it should be safe to set internal stuff
	m_pSynth = pSynth;
	m_iChan  = iChan;

	//fluid_preset_t preset;
	QTreeWidgetItem * pBankItem = NULL;

	// Currently just use zero as the only bank
	int iBankDefault = -1;
	int iProgDefault = -1;

	gig::Instrument * pInstrument = m_pSynth->gig.GetFirstInstrument();

	while( pInstrument )
	{
		int iBank = pInstrument->MIDIBank;
		int iProg = pInstrument->MIDIProgram;

		if ( !findBankItem( iBank ) )
		{
			pBankItem = new PatchItem( m_bankListView, pBankItem );

			if( pBankItem )
			{
				pBankItem->setText( 0, QString::number( iBank ) );

				if( iBankDefault == -1 )
				{
					iBankDefault = iBank;
					iProgDefault = iProg;
				}
			}
		}

		pInstrument = m_pSynth->gig.GetNextInstrument();
	}

	m_bankListView->setSortingEnabled( true );

	// Set the selected bank.
	if( iBankDefault != -1 )
	{
		m_iBank = iBankDefault;
	}

	pBankItem = findBankItem( m_iBank );
	m_bankListView->setCurrentItem( pBankItem );
	m_bankListView->scrollToItem( pBankItem );
	bankChanged();

	// Set the selected program.
	if( iProgDefault != -1 )
	{
		m_iProg = iProgDefault;
	}

	QTreeWidgetItem * pProgItem = findProgItem( m_iProg );
	m_progListView->setCurrentItem( pProgItem );
	m_progListView->scrollToItem( pProgItem );
}

// Stabilize current state form.
void PatchesDialog::stabilizeForm()
{
	m_okButton->setEnabled( validateForm() );
}

// Validate form fields.
bool PatchesDialog::validateForm()
{
	bool bValid = true;

	bValid = bValid && ( m_bankListView->currentItem() != NULL );
	bValid = bValid && ( m_progListView->currentItem() != NULL );

	return bValid;
}

// Realize a bank-program selection preset.
void PatchesDialog::setBankProg( int iBank, int iProg )
{
	if( m_pSynth == NULL )
	{
		return;
	}
}

// Validate form fields and accept it valid.
void PatchesDialog::accept()
{
	if( validateForm() )
	{
		// Unload from current selected dialog items.
		int iBank = ( m_bankListView->currentItem() )->text( 0 ).toInt();
		int iProg = ( m_progListView->currentItem() )->text( 0 ).toInt();

		// And set it right away...
		setBankProg( iBank, iProg );

		if( m_dirty > 0 )
		{
			m_bankModel->setValue( iBank );
			m_progModel->setValue( iProg );
			m_patchLabel->setText( m_progListView->
						currentItem()->text( 1 ) );
		}

		// We got it.
		QDialog::accept();
	}
}

// Reject settings (Cancel button slot).
void PatchesDialog::reject()
{
	// Reset selection to initial selection, if applicable...
	if( m_dirty > 0 )
	{
		setBankProg( m_bankModel->value(), m_progModel->value() );
	}

	// Done (hopefully nothing).
	QDialog::reject();
}

// Find the bank item of given bank number id.
QTreeWidgetItem * PatchesDialog::findBankItem( int iBank )
{
	QList<QTreeWidgetItem *> banks
		= m_bankListView->findItems(
			QString::number( iBank ), Qt::MatchExactly, 0 );

	QListIterator<QTreeWidgetItem *> iter( banks );

	if( iter.hasNext() )
	{
		return iter.next();
	}
	else
	{
		return NULL;
	}
}

// Find the program item of given program number id.
QTreeWidgetItem *PatchesDialog::findProgItem( int iProg )
{
	QList<QTreeWidgetItem *> progs
		= m_progListView->findItems(
			QString::number( iProg ), Qt::MatchExactly, 0 );

	QListIterator<QTreeWidgetItem *> iter( progs );

	if( iter.hasNext() )
	{
		return iter.next();
	}
	else
	{
		return NULL;
	}
}

// Bank change slot.
void PatchesDialog::bankChanged()
{
	if( m_pSynth == NULL )
	{
		return;
	}

	QTreeWidgetItem * pBankItem = m_bankListView->currentItem();

	if( pBankItem == NULL )
	{
		return;
	}

	int iBankSelected = pBankItem->text( 0 ).toInt();

	// Clear up the program listview.
	m_progListView->setSortingEnabled( false );
	m_progListView->clear();
	QTreeWidgetItem * pProgItem = NULL;

	gig::Instrument * pInstrument = m_pSynth->gig.GetFirstInstrument();

	while( pInstrument )
	{
		QString name = QString::fromStdString( pInstrument->pInfo->Name );

		if( name == "" )
		{
			name = "<no name>";
		}

		int iBank = pInstrument->MIDIBank;
		int iProg = pInstrument->MIDIProgram;

		if( iBank == iBankSelected && !findProgItem( iProg ) )
		{
			pProgItem = new PatchItem( m_progListView, pProgItem );

			if( pProgItem )
			{
				pProgItem->setText( 0, QString::number( iProg ) );
				pProgItem->setText( 1, name );
			}
		}

		pInstrument = m_pSynth->gig.GetNextInstrument();
	}

	m_progListView->setSortingEnabled( true );

	// Stabilize the form.
	stabilizeForm();
}

// Program change slot.
void PatchesDialog::progChanged( QTreeWidgetItem * curr, QTreeWidgetItem * prev )
{
	if( m_pSynth == NULL || curr == NULL )
	{
		return;
	}

	// Which preview state...
	if( validateForm() )
	{
		// Set current selection.
		int iBank = ( m_bankListView->currentItem() )->text( 0 ).toInt();
		int iProg = curr->text( 0 ).toInt();

		// And set it right away...
		setBankProg( iBank, iProg );

		// Now we're dirty nuff.
		m_dirty++;
	}

	// Stabilize the form.
	stabilizeForm();
}