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

#ifndef NES_H
#define NES_H

#include <cmath>

#include "Instrument.h"
#include "InstrumentView.h"
#include "AutomatableModel.h"
#include "AutomatableButton.h"
#include "TempoSyncKnob.h"
#include "NotePlayHandle.h"
#include "PixmapButton.h"
#include "MemoryManager.h"


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

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

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

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

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

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

const int MIN_WLEN = 4;


class NesInstrument;

class NesObject
{
	MM_OPERATORS
public:
	NesObject( NesInstrument * nes, const sample_rate_t samplerate, NotePlayHandle * nph );
	virtual ~NesObject();
	
	void renderOutput( sampleFrame * buf, fpp_t frames );
	void updateVibrato( float * freq );
	void updatePitch();
	
	void updateLFSR( bool mode )
	{
		uint16_t LFSRx;
		if( mode )
		{
				LFSRx = m_LFSR & ( 1 << 8 ); // get bit 13
				LFSRx <<= 6; // shit to left so it overlaps with bit 14
		}
		else
		{
				LFSRx = m_LFSR & ( 1 << 13 ); // get bit 13
				LFSRx <<= 1; // shit to left so it overlaps with bit 14
		}
		m_LFSR ^= LFSRx; // xor bit 14 with bit 8/13 depending on mode
		m_LFSR <<= 1; // shift bit 14 to bit 15
		
		// cycle bit 14 to 0
		if( m_LFSR & ( 1 << 15 ) ) // if bit 15 is set
		{ 
			m_LFSR++; // set bit 0 - we know it to be 0 because of the left shift so we can just inc here
		}
	}
	
	inline bool LFSR() // returns true if bit 14 is set
	{
		if( m_LFSR & ( 1 << 14 ) ) 
		{
			return true; 
		}
		return false;
	}
	
	inline int wavelength( float freq )
	{
		return static_cast<int>( m_samplerate / freq );
	}
	
	inline float signedPow( float f, float e )
	{
		return f < 0 
			? powf( qAbs( f ), e ) * -1.0f
			: powf( f, e );
	}
	
	inline int nearestNoiseFreq( float f )
	{
		int n = 15;
		for( int i = 15; i >= 0; i-- )
		{
			if( f >= NOISE_FREQS[ i ] )
			{
				n = i;
			}
		}
		return n;
	}
	
private:
	NesInstrument * m_parent;
	const sample_rate_t m_samplerate;
	NotePlayHandle * m_nph;
	
	int m_pitchUpdateCounter;
	int m_pitchUpdateFreq;
	
	int m_ch1Counter;
	int m_ch2Counter;
	int m_ch3Counter;
	int m_ch4Counter;
	
	int m_ch1EnvCounter;
	int m_ch2EnvCounter;
	int m_ch4EnvCounter;
	
	int m_ch1EnvValue;
	int m_ch2EnvValue;
	int m_ch4EnvValue;
	
	int m_ch1SweepCounter;
	int m_ch2SweepCounter;
	int m_ch4SweepCounter;
	
	uint16_t m_LFSR;
	
	float m_12Last;
	float m_34Last;

	float m_lastNoteFreq;
	float m_lastNoiseFreq;
	
	int m_maxWlen;
	
	float m_nsf;

// wavelengths	
	int m_wlen1;
	int m_wlen2;
	int m_wlen3;
	int m_wlen4;
	
// vibrato
	int m_vibratoPhase;
};


class NesInstrument : public Instrument
{
	Q_OBJECT
public:
	NesInstrument( InstrumentTrack * instrumentTrack );
	virtual ~NesInstrument();
	
	virtual void playNote( NotePlayHandle * n,
						sampleFrame * workingBuffer );
	virtual void deleteNotePluginData( NotePlayHandle * n );


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

	virtual QString nodeName() const;

	virtual f_cnt_t desiredReleaseFrames() const
	{
		return( 8 );
	}
	
	virtual PluginView * instantiateView( QWidget * parent );
	
public slots:
	void updateFreq1();
	void updateFreq2();
	void updateFreq3();

protected:
	//freq  helpers
	float m_freq1;
	float m_freq2;
	float m_freq3;
	
private:
	// channel 1
	BoolModel	m_ch1Enabled;
	FloatModel	m_ch1Crs;
	FloatModel	m_ch1Volume;
	
	BoolModel	m_ch1EnvEnabled;
	BoolModel	m_ch1EnvLooped;
	FloatModel	m_ch1EnvLen;
	
	IntModel	m_ch1DutyCycle;
	
	BoolModel	m_ch1SweepEnabled;
	FloatModel	m_ch1SweepAmt;
	FloatModel	m_ch1SweepRate;
	
	// channel 2
	BoolModel	m_ch2Enabled;
	FloatModel	m_ch2Crs;
	FloatModel	m_ch2Volume;
	
	BoolModel	m_ch2EnvEnabled;
	BoolModel	m_ch2EnvLooped;
	FloatModel	m_ch2EnvLen;
	
	IntModel	m_ch2DutyCycle;
	
	BoolModel	m_ch2SweepEnabled;
	FloatModel	m_ch2SweepAmt;
	FloatModel	m_ch2SweepRate;	
	
	//channel 3
	BoolModel	m_ch3Enabled;
	FloatModel	m_ch3Crs;
	FloatModel	m_ch3Volume;

	//channel 4
	BoolModel	m_ch4Enabled;
	FloatModel	m_ch4Volume;
	
	BoolModel	m_ch4EnvEnabled;
	BoolModel	m_ch4EnvLooped;
	FloatModel	m_ch4EnvLen;
	
	BoolModel	m_ch4NoiseMode;
	BoolModel	m_ch4NoiseFreqMode;
	FloatModel	m_ch4NoiseFreq;
	
	FloatModel	m_ch4Sweep;
	BoolModel	m_ch4NoiseQuantize;
	
	//master
	FloatModel	m_masterVol;
	FloatModel	m_vibrato;
	
	
	friend class NesObject;
	friend class NesInstrumentView;
};


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

private:
	virtual void modelChanged();
	
	// channel 1
	PixmapButton * 	m_ch1EnabledBtn;
	Knob *			m_ch1CrsKnob;
	Knob *			m_ch1VolumeKnob;
	
	PixmapButton *	m_ch1EnvEnabledBtn;
	PixmapButton *	m_ch1EnvLoopedBtn;
	Knob *			m_ch1EnvLenKnob;
	
	automatableButtonGroup *	m_ch1DutyCycleGrp;
	
	PixmapButton *	m_ch1SweepEnabledBtn;
	Knob *			m_ch1SweepAmtKnob;
	Knob *			m_ch1SweepRateKnob;
	
	// channel 2
	PixmapButton * 	m_ch2EnabledBtn;
	Knob *			m_ch2CrsKnob;
	Knob *			m_ch2VolumeKnob;
	
	PixmapButton *	m_ch2EnvEnabledBtn;
	PixmapButton *	m_ch2EnvLoopedBtn;
	Knob *			m_ch2EnvLenKnob;
	
	automatableButtonGroup *	m_ch2DutyCycleGrp;
	
	PixmapButton *	m_ch2SweepEnabledBtn;
	Knob *			m_ch2SweepAmtKnob;
	Knob *			m_ch2SweepRateKnob;
	
	//channel 3
	PixmapButton * 	m_ch3EnabledBtn;
	Knob *			m_ch3CrsKnob;
	Knob *			m_ch3VolumeKnob;

	//channel 4
	PixmapButton * 	m_ch4EnabledBtn;
	Knob *			m_ch4VolumeKnob;
	
	PixmapButton * 	m_ch4EnvEnabledBtn;
	PixmapButton * 	m_ch4EnvLoopedBtn;
	Knob *			m_ch4EnvLenKnob;
	
	PixmapButton * 	m_ch4NoiseModeBtn;
	PixmapButton * 	m_ch4NoiseFreqModeBtn;
	Knob *			m_ch4NoiseFreqKnob;
	
	Knob *			m_ch4SweepKnob;
	PixmapButton *	m_ch4NoiseQuantizeBtn;
	
	//master
	Knob *			m_masterVolKnob;
	Knob *			m_vibratoKnob;	
	
	static QPixmap *	s_artwork;
};

#endif
