code:
/*
 * OpulenZ.h - AdLib OPL2 FM synth based instrument
 *
 * Copyright (c) 2013 Raine M. Ekman <raine/at/iki/fi>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef OPULENZ_H
#define OPULENZ_H

#include "Instrument.h"
#include "InstrumentView.h"
#include "opl.h"

#include "LcdSpinBox.h"
#include "Knob.h"
#include "PixmapButton.h"

// This one is a flag, MIDI notes take 7 low bits
#define OPL2_VOICE_FREE 128
#define OPL2_NO_VOICE 255
#define OPL2_VOICES 9

// The "normal" range for LMMS pitchbends
#define DEFAULT_BEND_CENTS 100

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

	virtual QString nodeName() const;
	virtual PluginView * instantiateView( QWidget * _parent );

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

	virtual bool handleMidiEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset = 0 );
	virtual void play( sampleFrame * _working_buffer );

	void saveSettings( QDomDocument & _doc, QDomElement & _this );
	void loadSettings( const QDomElement & _this );
	void loadPatch(const unsigned char inst[14]);
	void tuneEqual(int center, float Hz);
	virtual void loadFile( const QString& file );

	IntModel m_patchModel;

	FloatModel op1_a_mdl;
	FloatModel op1_d_mdl;
	FloatModel op1_s_mdl;
	FloatModel op1_r_mdl;
	FloatModel op1_lvl_mdl;
	FloatModel op1_scale_mdl;
	FloatModel op1_mul_mdl;
	FloatModel feedback_mdl;
	BoolModel op1_ksr_mdl;
	BoolModel op1_perc_mdl;
	BoolModel op1_trem_mdl;
	BoolModel op1_vib_mdl;
	BoolModel op1_w0_mdl;
	BoolModel op1_w1_mdl;
	BoolModel op1_w2_mdl;
	BoolModel op1_w3_mdl;
	IntModel op1_waveform_mdl;

	FloatModel op2_a_mdl;
	FloatModel op2_d_mdl;
	FloatModel op2_s_mdl;
	FloatModel op2_r_mdl;
	FloatModel op2_lvl_mdl;
	FloatModel op2_scale_mdl;
	FloatModel op2_mul_mdl;
	BoolModel op2_ksr_mdl;
	BoolModel op2_perc_mdl;
	BoolModel op2_trem_mdl;
	BoolModel op2_vib_mdl;
	BoolModel op2_w0_mdl;
	BoolModel op2_w1_mdl;
	BoolModel op2_w2_mdl;
	BoolModel op2_w3_mdl;
	IntModel op2_waveform_mdl;

	BoolModel fm_mdl;
	BoolModel vib_depth_mdl;
	BoolModel trem_depth_mdl;

private slots:
        void updatePatch();
	void reloadEmulator();
	void loadGMPatch();

private:
	Copl *theEmulator;
	QString storedname;
	fpp_t frameCount;
	short *renderbuffer;
	int voiceNote[OPL2_VOICES];
	// Least recently used voices
	int voiceLRU[OPL2_VOICES];
	// 0 - no note, >0 - note on velocity
	int velocities[128];
	// These include both octave and Fnumber
	int fnums[128];
	// in cents, range defaults to +/-100 cents (should this be changeable?)
	int pitchbend;
	int pitchBendRange;

	int popVoice();
	int pushVoice(int v);

	int Hz2fnum(float Hz);
	static QMutex emulatorMutex;
	void setVoiceVelocity(int voice, int vel);

	// Pitch bend range comes through RPNs.
	int RPNcoarse, RPNfine;
};

class OpulenzInstrumentView : public InstrumentViewFixedSize
{
	Q_OBJECT
public:
	OpulenzInstrumentView( Instrument * _instrument, QWidget * _parent );
	virtual ~OpulenzInstrumentView();
	LcdSpinBox *m_patch;
	void modelChanged();

	Knob *op1_a_kn;
	Knob *op1_d_kn;
	Knob *op1_s_kn;
	Knob *op1_r_kn;
	Knob *op1_lvl_kn;
	Knob *op1_scale_kn;
	Knob *op1_mul_kn;
	Knob *feedback_kn;
	PixmapButton *op1_ksr_btn;
	PixmapButton *op1_perc_btn;
	PixmapButton *op1_trem_btn;
	PixmapButton *op1_vib_btn;
	PixmapButton *op1_w0_btn;
	PixmapButton *op1_w1_btn;
	PixmapButton *op1_w2_btn;
	PixmapButton *op1_w3_btn;
	automatableButtonGroup *op1_waveform;

	Knob *op2_a_kn;
	Knob *op2_d_kn;
	Knob *op2_s_kn;
	Knob *op2_r_kn;
	Knob *op2_lvl_kn;
	Knob *op2_scale_kn;
	Knob *op2_mul_kn;
	PixmapButton *op2_ksr_btn;
	PixmapButton *op2_perc_btn;
	PixmapButton *op2_trem_btn;
	PixmapButton *op2_vib_btn;
	PixmapButton *op2_w0_btn;
	PixmapButton *op2_w1_btn;
	PixmapButton *op2_w2_btn;
	PixmapButton *op2_w3_btn;
	automatableButtonGroup *op2_waveform;

	PixmapButton *fm_btn;
	PixmapButton *vib_depth_btn;
	PixmapButton *trem_depth_btn;

	private slots:
	void updateKnobHints();

 private:
	QString knobHintHelper(float n);

};

#endif


code:
INCLUDE(BuildPlugin)

BUILD_PLUGIN(
	reverbsc
	ReverbSC.cpp
	ReverbSCControls.cpp
	ReverbSCControlDialog.cpp
	base.c
	revsc.c
	base.h
	revsc.h
	dcblock.h
	dcblock.c
	ReverbSC.h
	MOCFILES
	ReverbSCControls.h
	ReverbSCControlDialog.h
	EMBEDDED_RESOURCES artwork.png logo.png
)

repo_name: CYBERDEViL/LMMS

license: mit


code:
/*
 * ReverbSC.cpp - A native reverb based on an algorithm by Sean Costello
 *
 * 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 <math.h>
#include "ReverbSC.h"

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

#define DB2LIN(X) pow(10, X / 20.0f);

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT reverbsc_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"ReverbSC",
	QT_TRANSLATE_NOOP( "PluginBrowser", "Reverb algorithm by Sean Costello" ),
	"Paul Batchelor",
	0x0123,
	Plugin::Effect,
	new PluginPixmapLoader( "logo" ),
	NULL,
	NULL
} ;

}

ReverbSCEffect::ReverbSCEffect( Model* parent, const Descriptor::SubPluginFeatures::Key* key ) :
	Effect( &reverbsc_plugin_descriptor, parent, key ),
	m_reverbSCControls( this )
{
	sp_create(&sp);
	sp->sr = Engine::mixer()->processingSampleRate();

	sp_revsc_create(&revsc);
	sp_revsc_init(sp, revsc);

	sp_dcblock_create(&dcblk[0]);
	sp_dcblock_create(&dcblk[1]);

	sp_dcblock_init(sp, dcblk[0], Engine::mixer()->currentQualitySettings().sampleRateMultiplier() );
	sp_dcblock_init(sp, dcblk[1], Engine::mixer()->currentQualitySettings().sampleRateMultiplier() );
}

ReverbSCEffect::~ReverbSCEffect()
{
	sp_revsc_destroy(&revsc);
	sp_dcblock_destroy(&dcblk[0]);
	sp_dcblock_destroy(&dcblk[1]);
	sp_destroy(&sp);
}

bool ReverbSCEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames )
{
	if( !isEnabled() || !isRunning () )
	{
		return( false );
	}

	double outSum = 0.0;
	const float d = dryLevel();
	const float w = wetLevel();

	SPFLOAT tmpL, tmpR;
	SPFLOAT dcblkL, dcblkR;

	ValueBuffer * inGainBuf = m_reverbSCControls.m_inputGainModel.valueBuffer();
	ValueBuffer * sizeBuf = m_reverbSCControls.m_sizeModel.valueBuffer();
	ValueBuffer * colorBuf = m_reverbSCControls.m_colorModel.valueBuffer();
	ValueBuffer * outGainBuf = m_reverbSCControls.m_outputGainModel.valueBuffer();

	for( fpp_t f = 0; f < frames; ++f )
	{
		sample_t s[2] = { buf[f][0], buf[f][1] };

		const SPFLOAT inGain = (SPFLOAT)DB2LIN((inGainBuf ?
			inGainBuf->values()[f]
			: m_reverbSCControls.m_inputGainModel.value()));
		const SPFLOAT outGain = (SPFLOAT)DB2LIN((outGainBuf ?
			outGainBuf->values()[f]
			: m_reverbSCControls.m_outputGainModel.value()));

		s[0] *= inGain;
		s[1] *= inGain;
		revsc->feedback = (SPFLOAT)(sizeBuf ?
			sizeBuf->values()[f]
			: m_reverbSCControls.m_sizeModel.value());

		revsc->lpfreq = (SPFLOAT)(colorBuf ?
			colorBuf->values()[f]
			: m_reverbSCControls.m_colorModel.value());

		sp_revsc_compute(sp, revsc, &s[0], &s[1], &tmpL, &tmpR);
		sp_dcblock_compute(sp, dcblk[0], &tmpL, &dcblkL);
		sp_dcblock_compute(sp, dcblk[1], &tmpR, &dcblkR);
		buf[f][0] = d * buf[f][0] + w * dcblkL * outGain;
		buf[f][1] = d * buf[f][1] + w * dcblkR * outGain;

		outSum += buf[f][0]*buf[f][0] + buf[f][1]*buf[f][1];
	}

	checkGate( outSum / frames );

	return isRunning();
}

void ReverbSCEffect::changeSampleRate()
{
	// Change sr variable in Soundpipe. does not need to be destroyed
	sp->sr = Engine::mixer()->processingSampleRate();

	mutex.lock();
	sp_revsc_destroy(&revsc);
	sp_dcblock_destroy(&dcblk[0]);
	sp_dcblock_destroy(&dcblk[1]);

	sp_revsc_create(&revsc);
	sp_revsc_init(sp, revsc);

	sp_dcblock_create(&dcblk[0]);
	sp_dcblock_create(&dcblk[1]);

	sp_dcblock_init(sp, dcblk[0], Engine::mixer()->currentQualitySettings().sampleRateMultiplier() );
	sp_dcblock_init(sp, dcblk[1], Engine::mixer()->currentQualitySettings().sampleRateMultiplier() );
	mutex.unlock();
}

extern "C"
{

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

}


code:
/*
 * RerverbSC.h - Reverb algorithm by Sean Costello
 *
 * Copyright (c) 2017 Paul Batchelor
 *
 * 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 REVERBSC_H
#define REVERBSC_H

#include "Effect.h"
#include "ReverbSCControls.h"
#include "ValueBuffer.h"

extern "C" {
    #include "base.h"
    #include "revsc.h"
    #include "dcblock.h"
}

class ReverbSCEffect : public Effect
{
public:
	ReverbSCEffect( Model* parent, const Descriptor::SubPluginFeatures::Key* key );
	virtual ~ReverbSCEffect();
	virtual bool processAudioBuffer( sampleFrame* buf, const fpp_t frames );

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

	void changeSampleRate();

private:
	ReverbSCControls m_reverbSCControls;
	sp_data *sp;
	sp_revsc *revsc;
	sp_dcblock *dcblk[2];
	QMutex mutex;
	friend class ReverbSCControls;
} ;

#endif


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

#include <QLayout>

#include "ReverbSCControlDialog.h"
#include "ReverbSCControls.h"
#include "embed.h"

ReverbSCControlDialog::ReverbSCControlDialog( ReverbSCControls* controls ) :
	EffectControlDialog( controls )
{
	setAutoFillBackground( true );
	QPalette pal;
	pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) );
	setPalette( pal );
	setFixedSize( 185, 55 );

	Knob * inputGainKnob = new Knob( knobBright_26, this);
	inputGainKnob -> move( 16, 10 );
	inputGainKnob->setModel( &controls->m_inputGainModel );
	inputGainKnob->setLabel( tr( "Input" ) );
	inputGainKnob->setHintText( tr( "Input gain:" ) , "dB" );

	Knob * sizeKnob = new Knob( knobBright_26, this);
	sizeKnob -> move( 57, 10 );
	sizeKnob->setModel( &controls->m_sizeModel );
	sizeKnob->setLabel( tr( "Size" ) );
	sizeKnob->setHintText( tr( "Size:" ) , "" );

	Knob * colorKnob = new Knob( knobBright_26, this);
	colorKnob -> move( 98, 10 );
	colorKnob->setModel( &controls->m_colorModel );
	colorKnob->setLabel( tr( "Color" ) );
	colorKnob->setHintText( tr( "Color:" ) , "" );

	Knob * outputGainKnob = new Knob( knobBright_26, this);
	outputGainKnob -> move( 139, 10 );
	outputGainKnob->setModel( &controls->m_outputGainModel );
	outputGainKnob->setLabel( tr( "Output" ) );
	outputGainKnob->setHintText( tr( "Output gain:" ) , "dB" );
}


code:
/*
 * ReverbSCControlDialog.h - control dialog for ReverbSC
 *
 * Copyright (c) 2017 Paul Batchelor
 *
 * 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 REVERBSC_CONTROL_DIALOG_H
#define REVERBSC_CONTROL_DIALOG_H

#include "EffectControlDialog.h"

class ReverbSCControls;

class ReverbSCControlDialog : public EffectControlDialog
{
	Q_OBJECT
public:
	ReverbSCControlDialog( ReverbSCControls* controls );
	virtual ~ReverbSCControlDialog()
	{
	}

} ;

#endif


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

#include <QDomElement>

#include "ReverbSCControls.h"
#include "ReverbSC.h"
#include "Engine.h"
#include "Song.h"

ReverbSCControls::ReverbSCControls( ReverbSCEffect* effect ) :
	EffectControls( effect ),
	m_effect( effect ),
	m_inputGainModel( 0.0f, -60.0f, 15, 0.1f, this, tr( "Input gain" ) ),
	m_sizeModel( 0.89f, 0.0f, 1.0f, 0.01f, this, tr( "Size" ) ),
	m_colorModel( 10000.0f, 100.0f, 15000.0f, 0.1f, this, tr( "Color" ) ),
	m_outputGainModel( 0.0f, -60.0f, 15, 0.1f, this, tr( "Output gain" ) )
{
	connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( changeSampleRate() ));
}

void ReverbSCControls::changeControl()
{
}

void ReverbSCControls::loadSettings( const QDomElement& _this )
{
	m_inputGainModel.loadSettings( _this, "input_gain" );
	m_sizeModel.loadSettings( _this, "size" );
	m_colorModel.loadSettings( _this, "color" );
	m_outputGainModel.loadSettings( _this, "output_gain" );
}

void ReverbSCControls::saveSettings( QDomDocument& doc, QDomElement& _this )
{
	m_inputGainModel.saveSettings( doc, _this, "input_gain" );
	m_sizeModel.saveSettings( doc, _this, "size" );
	m_colorModel.saveSettings( doc, _this, "color" );
	m_outputGainModel.saveSettings( doc, _this, "output_gain" );
}

void ReverbSCControls::changeSampleRate()
{
	m_effect->changeSampleRate();
}


code:
/*
 * ReverbSCControls.h
 *
 * Copyright (c) 2017 Paul Batchelor
 *
 * 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 REVERBSC_CONTROLS_H
#define REVERBSC_CONTROLS_H

#include "EffectControls.h"
#include "ReverbSCControlDialog.h"
#include "Knob.h"

class ReverbSCEffect;

class ReverbSCControls : public EffectControls
{
	Q_OBJECT
public:
	ReverbSCControls( ReverbSCEffect* effect );
	virtual ~ReverbSCControls()
	{
	}

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

	virtual int controlCount()
	{
		return 4;
	}

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

private slots:
	void changeControl();
	void changeSampleRate();

private:
	ReverbSCEffect* m_effect;
	FloatModel m_inputGainModel;
	FloatModel m_sizeModel;
	FloatModel m_colorModel;
	FloatModel m_outputGainModel;

	friend class ReverbSCControlDialog;
	friend class ReverbSCEffect;

} ;

#endif


code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "base.h"

int sp_create(sp_data **spp)
{
    *spp = (sp_data *) malloc(sizeof(sp_data));
    sp_data *sp = *spp;
    sprintf(sp->filename, "test.wav");
    sp->nchan = 1;
    SPFLOAT *out = malloc(sizeof(SPFLOAT) * sp->nchan);
    *out = 0;
    sp->out = out;
    sp->sr = 44100;
    sp->len = 5 * sp->sr;
    sp->pos = 0;
    sp->rand = 0;
    return 0;
}

int sp_createn(sp_data **spp, int nchan)
{
    *spp = (sp_data *) malloc(sizeof(sp_data));
    sp_data *sp = *spp;
    sprintf(sp->filename, "test.wav");
    sp->nchan = nchan;
    SPFLOAT *out = malloc(sizeof(SPFLOAT) * sp->nchan);
    *out = 0;
    sp->out = out;
    sp->sr = 44100;
    sp->len = 5 * sp->sr;
    sp->pos = 0;
    sp->rand = 0;
    return 0;
}

int sp_destroy(sp_data **spp)
{
    sp_data *sp = *spp;
    free(sp->out);
    free(*spp);
    return 0;
}

#ifndef NO_LIBSNDFILE

int sp_process(sp_data *sp, void *ud, void (*callback)(sp_data *, void *))
{
    SNDFILE *sf[sp->nchan];
    char tmp[140];
    SF_INFO info;
    memset(&info, 0, sizeof(SF_INFO));
    SPFLOAT buf[sp->nchan][SP_BUFSIZE];
    info.samplerate = sp->sr;
    info.channels = 1;
    info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24;
    int numsamps, i, chan;
    if(sp->nchan == 1) {
        sf[0] = sf_open(sp->filename, SFM_WRITE, &info);
    } else {
        for(chan = 0; chan < sp->nchan; chan++) {
            sprintf(tmp, "%02d_%s", chan, sp->filename);
            sf[chan] = sf_open(tmp, SFM_WRITE, &info);
        }
    }

    while(sp->len > 0){
        if(sp->len < SP_BUFSIZE) {
            numsamps = sp->len;
        }else{
            numsamps = SP_BUFSIZE;
        }
        for(i = 0; i < numsamps; i++){
            callback(sp, ud);
            for(chan = 0; chan < sp->nchan; chan++) {
                buf[chan][i] = sp->out[chan];
            }
            sp->pos++;
        }
        for(chan = 0; chan < sp->nchan; chan++) {
#ifdef USE_DOUBLE
            sf_write_double(sf[chan], buf[chan], numsamps);
#else
            sf_write_float(sf[chan], buf[chan], numsamps);
#endif
        }
        sp->len -= numsamps;
    }
    for(i = 0; i < sp->nchan; i++) {
        sf_close(sf[i]);
    }
    return 0;
}

#endif

int sp_process_raw(sp_data *sp, void *ud, void (*callback)(sp_data *, void *))
{
    int chan;
    if(sp->len == 0) {
        while(1) {
            callback(sp, ud);
            for (chan = 0; chan < sp->nchan; chan++) {
                fwrite(&sp->out[chan], sizeof(SPFLOAT), 1, stdout);
            }
            sp->len--;
        }
    } else {
        while(sp->len > 0) {
            callback(sp, ud);
            for (chan = 0; chan < sp->nchan; chan++) {
                fwrite(&sp->out[chan], sizeof(SPFLOAT), 1, stdout);
            }
            sp->len--;
            sp->pos++;
        }
    }
    return SP_OK;
}

#ifdef USE_SPA
int sp_process_spa(sp_data *sp, void *ud, void (*callback)(sp_data *, void *))
{
    sp_audio spa;
    if(spa_open(sp, &spa, sp->filename, SPA_WRITE) == SP_NOT_OK) {
        fprintf(stderr, "Error: could not open file %s.\n", sp->filename);
    }
    while(sp->len > 0) {
        callback(sp, ud);
        spa_write_buf(sp, &spa, sp->out, sp->nchan);
        sp->len--;
        sp->pos++;
    }
    spa_close(&spa);
    return SP_OK;
}
#endif

int sp_process_plot(sp_data *sp, void *ud, void (*callback)(sp_data *, void *))
{
    int chan;
    fprintf(stdout, "sp_out =  [ ... \n");
    while(sp->len > 0) {
        callback(sp, ud);
        for (chan = 0; chan < sp->nchan; chan++) {
            /* fwrite(&sp->out[chan], sizeof(SPFLOAT), 1, stdout); */
            fprintf(stdout, "%g ", sp->out[chan]);
        }
        fprintf(stdout, "; ...\n");
        sp->len--;
        sp->pos++;
    }
    fprintf(stdout, "];\n");

    fprintf(stdout, "plot(sp_out);\n");
    fprintf(stdout, "title('Plot generated by Soundpipe');\n");
    fprintf(stdout, "xlabel('Time (samples)');\n");
    fprintf(stdout, "ylabel('Amplitude');\n");
    return SP_OK;
}

int sp_auxdata_alloc(sp_auxdata *aux, size_t size)
{
    aux->ptr = malloc(size);
    aux->size = size;
    memset(aux->ptr, 0, size);
    return SP_OK;
}

int sp_auxdata_free(sp_auxdata *aux)
{
    free(aux->ptr);
    return SP_OK;
}

SPFLOAT sp_midi2cps(SPFLOAT nn)
{
    return pow(2, (nn - 69.0) / 12.0) * 440.0;
}

int sp_set(sp_param *p, SPFLOAT val) {
    p->state = 1;
    p->val = val;
    return SP_OK;
}

int sp_out(sp_data *sp, uint32_t chan, SPFLOAT val)
{
    if(chan > sp->nchan - 1) {
        fprintf(stderr, "sp_out: Invalid channel\n");
        return SP_NOT_OK;
    }
    sp->out[chan] = val;
    return SP_OK;
}

/*
uint32_t sp_rand(sp_data *sp)
{
    uint32_t val = (1103515245 * sp->rand + 12345) % SP_RANDMAX;
    sp->rand = val;
    return val;
}
*/

void sp_srand(sp_data *sp, uint32_t val)
{
    sp->rand = val;
}


code:
#include <stdint.h>
#include <stdio.h>

#define NO_LIBSNDFILE

#ifndef NO_LIBSNDFILE
#include "sndfile.h"
#endif

#define SP_BUFSIZE 4096
#ifndef SPFLOAT
#define SPFLOAT float
#endif
#define SP_OK 1
#define SP_NOT_OK 0

#define SP_RANDMAX 2147483648

typedef unsigned long sp_frame;

typedef struct sp_auxdata {
    size_t size;
    void *ptr;
} sp_auxdata;

typedef struct sp_data {
    SPFLOAT *out;
    uint32_t sr;
    int nchan;
    unsigned long len;
    unsigned long pos;
    char filename[200];
    uint32_t rand;
} sp_data;

typedef struct {
    char state;
    SPFLOAT val;
} sp_param;

int sp_auxdata_alloc(sp_auxdata *aux, size_t size);
int sp_auxdata_free(sp_auxdata *aux);

int sp_create(sp_data **spp);
int sp_createn(sp_data **spp, int nchan);

int sp_destroy(sp_data **spp);
int sp_process(sp_data *sp, void *ud, void (*callback)(sp_data *, void *));
int sp_process_raw(sp_data *sp, void *ud, void (*callback)(sp_data *, void *));
int sp_process_plot(sp_data *sp, void *ud, void (*callback)(sp_data *, void *));
int sp_process_spa(sp_data *sp, void *ud, void (*callback)(sp_data *, void *));

SPFLOAT sp_midi2cps(SPFLOAT nn);

int sp_set(sp_param *p, SPFLOAT val);

int sp_out(sp_data *sp, uint32_t chan, SPFLOAT val);

uint32_t sp_rand(sp_data *sp);
void sp_srand(sp_data *sp, uint32_t val);

typedef struct {
    SPFLOAT *utbl;
    int16_t *BRLow;
    int16_t *BRLowCpx;
} sp_fft;

void sp_fft_create(sp_fft **fft);
void sp_fft_init(sp_fft *fft, int M);
void sp_fftr(sp_fft *fft, SPFLOAT *buf, int FFTsize);
void sp_fft_cpx(sp_fft *fft, SPFLOAT *buf, int FFTsize);
void sp_ifftr(sp_fft *fft, SPFLOAT *buf, int FFTsize);
void sp_fft_destroy(sp_fft *fft);
#ifndef kiss_fft_scalar
#define kiss_fft_scalar SPFLOAT
#endif
typedef struct {
    kiss_fft_scalar r;
    kiss_fft_scalar i;
}kiss_fft_cpx;

typedef struct kiss_fft_state* kiss_fft_cfg;
typedef struct kiss_fftr_state* kiss_fftr_cfg;


code:
/*
 * DCblock
 *
 * This code has been extracted from the Csound opcode "dcblock".
 * It has been modified to work as a Soundpipe module.
 *
 * Original Author(s): Perry R. Cook
 * Year: 1995
 * Location: Opcodes/biquad.c
 *
 */

#include <math.h>
#include <stdlib.h>
#include "base.h"
#include "dcblock.h"

int sp_dcblock_create(sp_dcblock **p)
{
    *p = malloc(sizeof(sp_dcblock));
    return SP_OK;
}

int sp_dcblock_destroy(sp_dcblock **p)
{
    free(*p);
    return SP_OK;
}

int sp_dcblock_init(sp_data *sp, sp_dcblock *p, int oversampling )
{
    p->outputs = 0.0;
    p->inputs = 0.0;
    p->gain = pow( 0.99, 1.0f / oversampling );
    if (p->gain == 0.0 || p->gain>=1.0 || p->gain<=-1.0)
      p->gain = 0.99;
    return SP_OK;
}

int sp_dcblock_compute(sp_data *sp, sp_dcblock *p, SPFLOAT *in, SPFLOAT *out)
{
    SPFLOAT gain = p->gain;
    SPFLOAT outputs = p->outputs;
    SPFLOAT inputs = p->inputs;

    SPFLOAT sample = *in;
    outputs = sample - inputs + (gain * outputs);
    inputs = sample;
    *out = outputs;
    p->outputs = outputs;
    p->inputs = inputs;
    return SP_OK;
}


code:
typedef struct {
    SPFLOAT gg;
    SPFLOAT outputs;
    SPFLOAT inputs;
    SPFLOAT gain;
} sp_dcblock;

int sp_dcblock_create(sp_dcblock **p);
int sp_dcblock_destroy(sp_dcblock **p);
int sp_dcblock_init(sp_data *sp, sp_dcblock *p, int oversampling );
int sp_dcblock_compute(sp_data *sp, sp_dcblock *p, SPFLOAT *in, SPFLOAT *out);


code:
/*
 * RevSC
 *
 * This code has been extracted from the Csound opcode "reverbsc".
 * It has been modified to work as a Soundpipe module.
 *
 * Original Author(s): Sean Costello, Istvan Varga
 * Year: 1999, 2005
 * Location: Opcodes/reverbsc.c
 *
 */
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "base.h"
#include "revsc.h"

#define DEFAULT_SRATE   44100.0
#define MIN_SRATE       5000.0
#define MAX_SRATE       1000000.0
#define MAX_PITCHMOD    20.0
#define DELAYPOS_SHIFT  28
#define DELAYPOS_SCALE  0x10000000
#define DELAYPOS_MASK   0x0FFFFFFF

#ifndef M_PI
#define M_PI		3.14159265358979323846	/* pi */
#endif

/* reverbParams[n][0] = delay time (in seconds)                     */
/* reverbParams[n][1] = random variation in delay time (in seconds) */
/* reverbParams[n][2] = random variation frequency (in 1/sec)       */
/* reverbParams[n][3] = random seed (0 - 32767)                     */

static const SPFLOAT reverbParams[8][4] = {
    { (2473.0 / DEFAULT_SRATE), 0.0010, 3.100,  1966.0 },
    { (2767.0 / DEFAULT_SRATE), 0.0011, 3.500, 29491.0 },
    { (3217.0 / DEFAULT_SRATE), 0.0017, 1.110, 22937.0 },
    { (3557.0 / DEFAULT_SRATE), 0.0006, 3.973,  9830.0 },
    { (3907.0 / DEFAULT_SRATE), 0.0010, 2.341, 20643.0 },
    { (4127.0 / DEFAULT_SRATE), 0.0011, 1.897, 22937.0 },
    { (2143.0 / DEFAULT_SRATE), 0.0017, 0.891, 29491.0 },
    { (1933.0 / DEFAULT_SRATE), 0.0006, 3.221, 14417.0 }
};

static int delay_line_max_samples(SPFLOAT sr, SPFLOAT iPitchMod, int n);
static int init_delay_line(sp_revsc *p, sp_revsc_dl *lp, int n);
static int delay_line_bytes_alloc(SPFLOAT sr, SPFLOAT iPitchMod, int n);
static const SPFLOAT outputGain  = 0.35;
static const SPFLOAT jpScale     = 0.25;
int sp_revsc_create(sp_revsc **p){
    *p = malloc(sizeof(sp_revsc));
    return SP_OK;
}

int sp_revsc_init(sp_data *sp, sp_revsc *p)
{
    p->iSampleRate = sp->sr;
    p->sampleRate = sp->sr;
    p->feedback = 0.97;
    p->lpfreq = 10000;
    p->iPitchMod = 1;
    p->iSkipInit = 0;
    p->dampFact = 1.0;
    p->prv_LPFreq = 0.0;
    p->initDone = 1;
    int i, nBytes = 0;
    for(i = 0; i < 8; i++){
        nBytes += delay_line_bytes_alloc(sp->sr, 1, i);
    }
    sp_auxdata_alloc(&p->aux, nBytes);
    nBytes = 0;
    for (i = 0; i < 8; i++) {
        p->delayLines[i].buf = (SPFLOAT*) (((char*) p->aux.ptr) + nBytes);
        init_delay_line(p, &p->delayLines[i], i);
        nBytes += delay_line_bytes_alloc(sp->sr, 1, i);
    }

    return SP_OK;
}

int sp_revsc_destroy(sp_revsc **p)
{
    sp_revsc *pp = *p;
    sp_auxdata_free(&pp->aux);
    free(*p);
    return SP_OK;
}

static int delay_line_max_samples(SPFLOAT sr, SPFLOAT iPitchMod, int n)
{
    SPFLOAT maxDel;

    maxDel = reverbParams[n][0];
    maxDel += (reverbParams[n][1] * (SPFLOAT) iPitchMod * 1.125);
    return (int) (maxDel * sr + 16.5);
}

static int delay_line_bytes_alloc(SPFLOAT sr, SPFLOAT iPitchMod, int n)
{
    int nBytes = 0;

    nBytes += (delay_line_max_samples(sr, iPitchMod, n) * (int) sizeof(SPFLOAT));
    return nBytes;
}

static void next_random_lineseg(sp_revsc *p, sp_revsc_dl *lp, int n)
{
    SPFLOAT prvDel, nxtDel, phs_incVal;

    /* update random seed */
    if (lp->seedVal < 0)
      lp->seedVal += 0x10000;
    lp->seedVal = (lp->seedVal * 15625 + 1) & 0xFFFF;
    if (lp->seedVal >= 0x8000)
      lp->seedVal -= 0x10000;
    /* length of next segment in samples */
    lp->randLine_cnt = (int) ((p->sampleRate / reverbParams[n][2]) + 0.5);
    prvDel = (SPFLOAT) lp->writePos;
    prvDel -= ((SPFLOAT) lp->readPos
               + ((SPFLOAT) lp->readPosFrac / (SPFLOAT) DELAYPOS_SCALE));
    while (prvDel < 0.0)
      prvDel += lp->bufferSize;
    prvDel = prvDel / p->sampleRate;    /* previous delay time in seconds */
    nxtDel = (SPFLOAT) lp->seedVal * reverbParams[n][1] / 32768.0;
    /* next delay time in seconds */
    nxtDel = reverbParams[n][0] + (nxtDel * (SPFLOAT) p->iPitchMod);
    /* calculate phase increment per sample */
    phs_incVal = (prvDel - nxtDel) / (SPFLOAT) lp->randLine_cnt;
    phs_incVal = phs_incVal * p->sampleRate + 1.0;
    lp->readPosFrac_inc = (int) (phs_incVal * DELAYPOS_SCALE + 0.5);
}

static int init_delay_line(sp_revsc *p, sp_revsc_dl *lp, int n)
{
    SPFLOAT readPos;
    /* int     i; */

    /* calculate length of delay line */
    lp->bufferSize = delay_line_max_samples(p->sampleRate, 1, n);
    lp->dummy = 0;
    lp->writePos = 0;
    /* set random seed */
    lp->seedVal = (int) (reverbParams[n][3] + 0.5);
    /* set initial delay time */
    readPos = (SPFLOAT) lp->seedVal * reverbParams[n][1] / 32768;
    readPos = reverbParams[n][0] + (readPos * (SPFLOAT) p->iPitchMod);
    readPos = (SPFLOAT) lp->bufferSize - (readPos * p->sampleRate);
    lp->readPos = (int) readPos;
    readPos = (readPos - (SPFLOAT) lp->readPos) * (SPFLOAT) DELAYPOS_SCALE;
    lp->readPosFrac = (int) (readPos + 0.5);
    /* initialise first random line segment */
    next_random_lineseg(p, lp, n);
    /* clear delay line to zero */
    lp->filterState = 0.0;
    memset(lp->buf, 0, sizeof(SPFLOAT) * lp->bufferSize);
    return SP_OK;
}

int sp_revsc_compute(sp_data *sp, sp_revsc *p, SPFLOAT *in1, SPFLOAT *in2, SPFLOAT *out1, SPFLOAT *out2)
{
    SPFLOAT ainL, ainR, aoutL, aoutR;
    SPFLOAT vm1, v0, v1, v2, am1, a0, a1, a2, frac;
    sp_revsc_dl *lp;
    int readPos;
    uint32_t n;
    int bufferSize; /* Local copy */
    SPFLOAT dampFact = p->dampFact;

    if (p->initDone <= 0) return SP_NOT_OK;

    /* calculate tone filter coefficient if frequency changed */

    if (p->lpfreq != p->prv_LPFreq) {
        p->prv_LPFreq = p->lpfreq;
        dampFact = 2.0 - cos(p->prv_LPFreq * (2 * M_PI) / p->sampleRate);
        dampFact = p->dampFact = dampFact - sqrt(dampFact * dampFact - 1.0);
    }

    /* calculate "resultant junction pressure" and mix to input signals */

    ainL = aoutL = aoutR = 0.0;
    for (n = 0; n < 8; n++) {
        ainL += p->delayLines[n].filterState;
    }
    ainL *= jpScale;
    ainR = ainL + *in2;
    ainL = ainL + *in1;

    /* loop through all delay lines */

    for (n = 0; n < 8; n++) {
        lp = &p->delayLines[n];
        bufferSize = lp->bufferSize;

        /* send input signal and feedback to delay line */

        lp->buf[lp->writePos] = (SPFLOAT) ((n & 1 ? ainR : ainL)
                                 - lp->filterState);
        if (++lp->writePos >= bufferSize) {
            lp->writePos -= bufferSize;
        }

        /* read from delay line with cubic interpolation */

        if (lp->readPosFrac >= DELAYPOS_SCALE) {
            lp->readPos += (lp->readPosFrac >> DELAYPOS_SHIFT);
            lp->readPosFrac &= DELAYPOS_MASK;
        }
        if (lp->readPos >= bufferSize)
        lp->readPos -= bufferSize;
        readPos = lp->readPos;
        frac = (SPFLOAT) lp->readPosFrac * (1.0 / (SPFLOAT) DELAYPOS_SCALE);

        /* calculate interpolation coefficients */

        a2 = frac * frac; a2 -= 1.0; a2 *= (1.0 / 6.0);
        a1 = frac; a1 += 1.0; a1 *= 0.5; am1 = a1 - 1.0;
        a0 = 3.0 * a2; a1 -= a0; am1 -= a2; a0 -= frac;

        /* read four samples for interpolation */

        if (readPos > 0 && readPos < (bufferSize - 2)) {
            vm1 = (SPFLOAT) (lp->buf[readPos - 1]);
            v0  = (SPFLOAT) (lp->buf[readPos]);
            v1  = (SPFLOAT) (lp->buf[readPos + 1]);
            v2  = (SPFLOAT) (lp->buf[readPos + 2]);
        }
        else {

        /* at buffer wrap-around, need to check index */

        if (--readPos < 0) readPos += bufferSize;
            vm1 = (SPFLOAT) lp->buf[readPos];
        if (++readPos >= bufferSize) readPos -= bufferSize;
            v0 = (SPFLOAT) lp->buf[readPos];
        if (++readPos >= bufferSize) readPos -= bufferSize;
            v1 = (SPFLOAT) lp->buf[readPos];
        if (++readPos >= bufferSize) readPos -= bufferSize;
            v2 = (SPFLOAT) lp->buf[readPos];
        }
        v0 = (am1 * vm1 + a0 * v0 + a1 * v1 + a2 * v2) * frac + v0;

        /* update buffer read position */

        lp->readPosFrac += lp->readPosFrac_inc;

        /* apply feedback gain and lowpass filter */

        v0 *= (SPFLOAT) p->feedback;
        v0 = (lp->filterState - v0) * dampFact + v0;
        lp->filterState = v0;

        /* mix to output */

        if (n & 1) {
            aoutR += v0;
        }else{
            aoutL += v0;
        }

        /* start next random line segment if current one has reached endpoint */

        if (--(lp->randLine_cnt) <= 0) {
            next_random_lineseg(p, lp, n);
        }
    }
    /* someday, use aoutR for multimono out */

    *out1  = aoutL * outputGain;
    *out2 = aoutR * outputGain;
    return SP_OK;
}


code:
typedef struct {
    int writePos;
    int bufferSize;
    int readPos;
    int readPosFrac;
    int readPosFrac_inc;
    int dummy;
    int seedVal;
    int randLine_cnt;
    SPFLOAT filterState;
    SPFLOAT *buf;
} sp_revsc_dl;

typedef struct  {
    SPFLOAT feedback, lpfreq;
    SPFLOAT iSampleRate, iPitchMod, iSkipInit;
    SPFLOAT sampleRate;
    SPFLOAT dampFact;
    SPFLOAT prv_LPFreq;
    int initDone;
    sp_revsc_dl delayLines[8];
    sp_auxdata aux;
} sp_revsc;

int sp_revsc_create(sp_revsc **p);
int sp_revsc_destroy(sp_revsc **p);
int sp_revsc_init(sp_data *sp, sp_revsc *p);
int sp_revsc_compute(sp_data *sp, sp_revsc *p, SPFLOAT *in1, SPFLOAT *in2, SPFLOAT *out1, SPFLOAT *out2);


code:
INCLUDE(BuildPlugin)

INCLUDE_DIRECTORIES(resid)

BUILD_PLUGIN(sid
	SidInstrument.cpp
	SidInstrument.h
	resid/envelope.h
	resid/extfilt.h
	resid/filter.h
	resid/pot.h
	resid/siddefs.h
	resid/sid.h
	resid/spline.h
	resid/voice.h
	resid/wave.h
	resid/envelope.cc
	resid/extfilt.cc
	resid/filter.cc
	resid/pot.cc
	resid/sid.cc
	resid/version.cc
	resid/voice.cc
	resid/wave6581_PS_.cc
	resid/wave6581_PST.cc
	resid/wave6581_P_T.cc
	resid/wave6581__ST.cc
	resid/wave8580_PS_.cc
	resid/wave8580_PST.cc
	resid/wave8580_P_T.cc
	resid/wave8580__ST.cc
	resid/wave.cc
	MOCFILES SidInstrument.h
	EMBEDDED_RESOURCES *.png)

# Parse VERSION
FILE(READ "resid/CMakeLists.txt" lines)
STRING(REGEX MATCH "set\\(MAJOR_VER [A-Za-z0-9_]*\\)" MAJOR_RAW ${lines})
STRING(REGEX MATCH "set\\(MINOR_VER [A-Za-z0-9_]*\\)" MINOR_RAW ${lines})
STRING(REGEX MATCH "set\\(PATCH_VER [A-Za-z0-9_]*\\)" PATCH_RAW ${lines})
SEPARATE_ARGUMENTS(MAJOR_RAW)
SEPARATE_ARGUMENTS(MINOR_RAW)
SEPARATE_ARGUMENTS(PATCH_RAW)
LIST(GET MAJOR_RAW 1 MAJOR_RAW)
LIST(GET MINOR_RAW 1 MINOR_RAW)
LIST(GET PATCH_RAW 1 PATCH_RAW)
STRING(REPLACE ")" "" MAJOR_VER "${MAJOR_RAW}")
STRING(REPLACE ")" "" MINOR_VER "${MINOR_RAW}")
STRING(REPLACE ")" "" PATCH_VER "${PATCH_RAW}")

TARGET_COMPILE_DEFINITIONS(sid PRIVATE VERSION="${MAJOR_VER}.${MINOR_VER}.${PATCH_VER}")


code:
/*
 * SidInstrument.cpp - ResID based software-synthesizer
 *
 * Copyright (c) 2008 Csaba Hruska <csaba.hruska/at/gmail.com>
 *                    Attila Herman <attila589/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 <QPainter>
#include <QDomElement>

#include <cmath>
#include <cstdio>

#include "sid.h"

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

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

#define C64_PAL_CYCLES_PER_SEC  985248

#define NUMSIDREGS 0x19
#define SIDWRITEDELAY 9 // lda $xxxx,x 4 cycles, sta $d400,x 5 cycles
#define SIDWAVEDELAY 4 // and $xxxx,x 4 cycles extra

unsigned char sidorder[] =
  {0x15,0x16,0x18,0x17,
   0x05,0x06,0x02,0x03,0x00,0x01,0x04,
   0x0c,0x0d,0x09,0x0a,0x07,0x08,0x0b,
   0x13,0x14,0x10,0x11,0x0e,0x0f,0x12};

static const char *attackTime[16] = { "2 ms", "8 ms", "16 ms", "24 ms",
									"38 ms", "56 ms", "68 ms", "80 ms",
									"100 ms", "250 ms", "500 ms", "800 ms",
									"1 s", "3 s", "5 s", "8 s" };
static const char *decRelTime[16] = { "6 ms", "24 ms", "48 ms", "72 ms",
									"114 ms", "168 ms", "204 ms", "240 ms",
									"300 ms", "750 ms", "1.5 s", "2.4 s",
									"3 s", "9 s", "15 s", "24 s" };
// release time time in ms
static const int relTime[16] = { 6, 24, 48, 72, 114, 168, 204, 240, 300, 750,
								1500, 2400, 3000, 9000, 15000, 24000 };

extern "C"
{
Plugin::Descriptor PLUGIN_EXPORT sid_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"SID",
	QT_TRANSLATE_NOOP( "PluginBrowser", "Emulation of the MOS6581 and MOS8580 "
					"SID.\nThis chip was used in the Commodore 64 computer." ),

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

}

voiceObject::voiceObject( Model * _parent, int _idx ) :
	Model( _parent ),
	m_pulseWidthModel( 2048.0f, 0.0f, 4095.0f, 1.0f, this,
					tr( "Voice %1 pulse width" ).arg( _idx+1 ) ),
	m_attackModel( 8.0f, 0.0f, 15.0f, 1.0f, this,
					tr( "Voice %1 attack" ).arg( _idx+1 ) ),
	m_decayModel( 8.0f, 0.0f, 15.0f, 1.0f, this,
					tr( "Voice %1 decay" ).arg( _idx+1 ) ),
	m_sustainModel( 15.0f, 0.0f, 15.0f, 1.0f, this,
					tr( "Voice %1 sustain" ).arg( _idx+1 ) ),
	m_releaseModel( 8.0f, 0.0f, 15.0f, 1.0f, this,
					tr( "Voice %1 release" ).arg( _idx+1 ) ),
	m_coarseModel( 0.0f, -24.0, 24.0, 1.0f, this,
					tr( "Voice %1 coarse detuning" ).arg( _idx+1 ) ),
	m_waveFormModel( TriangleWave, 0, NumWaveShapes-1, this,
					tr( "Voice %1 wave shape" ).arg( _idx+1 ) ),

	m_syncModel( false, this, tr( "Voice %1 sync" ).arg( _idx+1 ) ),
	m_ringModModel( false, this, tr( "Voice %1 ring modulate" ).arg( _idx+1 ) ),
	m_filteredModel( false, this, tr( "Voice %1 filtered" ).arg( _idx+1 ) ),
	m_testModel( false, this, tr( "Voice %1 test" ).arg( _idx+1 ) )
{
}

voiceObject::~voiceObject()
{
}

SidInstrument::SidInstrument( InstrumentTrack * _instrument_track ) :
	Instrument( _instrument_track, &sid_plugin_descriptor ),
	// filter
	m_filterFCModel( 1024.0f, 0.0f, 2047.0f, 1.0f, this, tr( "Cutoff frequency" ) ),
	m_filterResonanceModel( 8.0f, 0.0f, 15.0f, 1.0f, this, tr( "Resonance" ) ),
	m_filterModeModel( LowPass, 0, NumFilterTypes-1, this, tr( "Filter type" )),

	// misc
	m_voice3OffModel( false, this, tr( "Voice 3 off" ) ),
	m_volumeModel( 15.0f, 0.0f, 15.0f, 1.0f, this, tr( "Volume" ) ),
	m_chipModel( sidMOS8580, 0, NumChipModels-1, this, tr( "Chip model" ) )
{
	for( int i = 0; i < 3; ++i )
	{
		m_voice[i] = new voiceObject( this, i );
	}
}

SidInstrument::~SidInstrument()
{
}

void SidInstrument::saveSettings( QDomDocument & _doc,
							QDomElement & _this )
{
	// voices
	for( int i = 0; i < 3; ++i )
	{
		const QString is = QString::number( i );

		m_voice[i]->m_pulseWidthModel.saveSettings(
										_doc, _this, "pulsewidth" + is );
		m_voice[i]->m_attackModel.saveSettings(
										_doc, _this, "attack" + is );
		m_voice[i]->m_decayModel.saveSettings(
										_doc, _this, "decay" + is );
		m_voice[i]->m_sustainModel.saveSettings(
										_doc, _this, "sustain" + is );
		m_voice[i]->m_releaseModel.saveSettings(
										_doc, _this, "release" + is );
		m_voice[i]->m_coarseModel.saveSettings(
										_doc, _this, "coarse" + is );
		m_voice[i]->m_waveFormModel.saveSettings(
										_doc, _this,"waveform" + is );
		m_voice[i]->m_syncModel.saveSettings(
										_doc, _this, "sync" + is );
		m_voice[i]->m_ringModModel.saveSettings(
										_doc, _this, "ringmod" + is );
		m_voice[i]->m_filteredModel.saveSettings(
										_doc, _this,"filtered" + is );
		m_voice[i]->m_testModel.saveSettings(
										_doc, _this, "test" + is );
	}

	// filter
	m_filterFCModel.saveSettings( _doc, _this, "filterFC" );
	m_filterResonanceModel.saveSettings( _doc, _this, "filterResonance" );
	m_filterModeModel.saveSettings( _doc, _this, "filterMode" );

	// misc
	m_voice3OffModel.saveSettings( _doc, _this, "voice3Off" );
	m_volumeModel.saveSettings( _doc, _this, "volume" );
	m_chipModel.saveSettings( _doc, _this, "chipModel" );
}

void SidInstrument::loadSettings( const QDomElement & _this )
{
	// voices
	for( int i = 0; i < 3; ++i )
	{
		const QString is = QString::number( i );

		m_voice[i]->m_pulseWidthModel.loadSettings( _this, "pulsewidth" + is );
		m_voice[i]->m_attackModel.loadSettings( _this, "attack" + is );
		m_voice[i]->m_decayModel.loadSettings( _this, "decay" + is );
		m_voice[i]->m_sustainModel.loadSettings( _this, "sustain" + is );
		m_voice[i]->m_releaseModel.loadSettings( _this, "release" + is );
		m_voice[i]->m_coarseModel.loadSettings( _this, "coarse" + is );
		m_voice[i]->m_waveFormModel.loadSettings( _this, "waveform" + is );
		m_voice[i]->m_syncModel.loadSettings( _this, "sync" + is );
		m_voice[i]->m_ringModModel.loadSettings( _this, "ringmod" + is );
		m_voice[i]->m_filteredModel.loadSettings( _this, "filtered" + is );
		m_voice[i]->m_testModel.loadSettings( _this, "test" + is );
	}

	// filter
	m_filterFCModel.loadSettings( _this, "filterFC" );
	m_filterResonanceModel.loadSettings( _this, "filterResonance" );
	m_filterModeModel.loadSettings( _this, "filterMode" );

	// misc
	m_voice3OffModel.loadSettings( _this, "voice3Off" );
	m_volumeModel.loadSettings( _this, "volume" );
	m_chipModel.loadSettings( _this, "chipModel" );
}

QString SidInstrument::nodeName() const
{
	return( sid_plugin_descriptor.name );
}

f_cnt_t SidInstrument::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 = (int)m_voice[i]->m_releaseModel.value();
	}

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

static int sid_fillbuffer(unsigned char* sidreg, SID *sid, int tdelta, short *ptr, int samples)
{
  int tdelta2;
  int result;
  int total = 0;
  int c;
//  customly added
  int residdelay = 0;

  int badline = rand() % NUMSIDREGS;

  for (c = 0; c < NUMSIDREGS; c++)
  {
    unsigned char o = sidorder[c];

  	// Extra delay for loading the waveform (and mt_chngate,x)
  	if ((o == 4) || (o == 11) || (o == 18))
  	{
  	  tdelta2 = SIDWAVEDELAY;
      result = sid->clock(tdelta2, ptr, samples);
      total += result;
      ptr += result;
      samples -= result;
      tdelta -= SIDWAVEDELAY;
    }

    // Possible random badline delay once per writing
    if ((badline == c) && (residdelay))
  	{
      tdelta2 = residdelay;
      result = sid->clock(tdelta2, ptr, samples);
      total += result;
      ptr += result;
      samples -= result;
      tdelta -= residdelay;
    }

    sid->write(o, sidreg[o]);

    tdelta2 = SIDWRITEDELAY;
    result = sid->clock(tdelta2, ptr, samples);
    total += result;
    ptr += result;
    samples -= result;
    tdelta -= SIDWRITEDELAY;
  }
  result = sid->clock(tdelta, ptr, samples);
  total += result;

  return total;
}

void SidInstrument::playNote( NotePlayHandle * _n,
						sampleFrame * _working_buffer )
{
	const f_cnt_t tfp = _n->totalFramesPlayed();

	const int clockrate = C64_PAL_CYCLES_PER_SEC;
	const int samplerate = Engine::mixer()->processingSampleRate();

	if ( tfp == 0 )
	{
		SID *sid = new SID();
		sid->set_sampling_parameters( clockrate, SAMPLE_FAST, samplerate );
		sid->set_chip_model( MOS8580 );
		sid->enable_filter( true );
		sid->reset();
		_n->m_pluginData = sid;
	}
	const fpp_t frames = _n->framesLeftForCurrentPeriod();
	const f_cnt_t offset = _n->noteOffset();

	SID *sid = static_cast<SID *>( _n->m_pluginData );
	int delta_t = clockrate * frames / samplerate + 4;
	// avoid variable length array for msvc compat
	short* buf = reinterpret_cast<short*>(_working_buffer + offset);
	unsigned char sidreg[NUMSIDREGS];

	for (int c = 0; c < NUMSIDREGS; c++)
	{
		sidreg[c] = 0x00;
	}

	if( (ChipModel)m_chipModel.value() == sidMOS6581 )
	{
		sid->set_chip_model( MOS6581 );
	}
	else
	{
		sid->set_chip_model( MOS8580 );
	}

	// voices
	reg8 data8 = 0;
	reg8 data16 = 0;
	reg8 base = 0;
	float freq = 0.0;
	float note = 0.0;
	for( reg8 i = 0 ; i < 3 ; ++i )
	{
		base = i*7;
		// freq ( Fn = Fout / Fclk * 16777216 ) + coarse detuning
		freq = _n->frequency();
		note = 69.0 + 12.0 * log( freq / 440.0 ) / log( 2 );
		note += m_voice[i]->m_coarseModel.value();
		freq = 440.0 * pow( 2.0, (note-69.0)/12.0 );
		data16 = int( freq / float(clockrate) * 16777216.0 );

		sidreg[base+0] = data16&0x00FF;
		sidreg[base+1] = (data16>>8)&0x00FF;
		// pw
		data16 = (int)m_voice[i]->m_pulseWidthModel.value();

		sidreg[base+2] = data16&0x00FF;
		sidreg[base+3] = (data16>>8)&0x000F;
		// control: wave form, (test), ringmod, sync, gate
		data8 = _n->isReleased()?0:1;
		data8 += m_voice[i]->m_syncModel.value()?2:0;
		data8 += m_voice[i]->m_ringModModel.value()?4:0;
		data8 += m_voice[i]->m_testModel.value()?8:0;
		switch( m_voice[i]->m_waveFormModel.value() )
		{
			default: break;
			case voiceObject::NoiseWave:	data8 += 128; break;
			case voiceObject::SquareWave:	data8 += 64; break;
			case voiceObject::SawWave:		data8 += 32; break;
			case voiceObject::TriangleWave:	data8 += 16; break;
		}
		sidreg[base+4] = data8&0x00FF;
		// ad
		data16 = (int)m_voice[i]->m_attackModel.value();

		data8 = (data16&0x0F)<<4;
		data16 = (int)m_voice[i]->m_decayModel.value();

		data8 += (data16&0x0F);
		sidreg[base+5] = data8&0x00FF;
		// sr
		data16 = (int)m_voice[i]->m_sustainModel.value();

		data8 = (data16&0x0F)<<4;
		data16 = (int)m_voice[i]->m_releaseModel.value();

		data8 += (data16&0x0F);
		sidreg[base+6] = data8&0x00FF;
	}
	// filtered

	// FC (FilterCutoff)
	data16 = (int)m_filterFCModel.value();
	sidreg[21] = data16&0x0007;
	sidreg[22] = (data16>>3)&0x00FF;

	// res, filt ex,3,2,1
	data16 = (int)m_filterResonanceModel.value();
	data8 = (data16&0x000F)<<4;
	data8 += m_voice[2]->m_filteredModel.value()?4:0;
	data8 += m_voice[1]->m_filteredModel.value()?2:0;
	data8 += m_voice[0]->m_filteredModel.value()?1:0;
	sidreg[23] = data8&0x00FF;

	// mode vol
	data16 = (int)m_volumeModel.value();
	data8 = data16&0x000F;
	data8 += m_voice3OffModel.value()?128:0;

	switch( m_filterModeModel.value() )
	{
		default: break;
		case LowPass:	data8 += 16; break;
		case BandPass:	data8 += 32; break;
		case HighPass:	data8 += 64; break;
	}

	sidreg[24] = data8&0x00FF;

	int num = sid_fillbuffer(sidreg, sid,delta_t,buf, frames);
	if(num!=frames)
		printf("!!!Not enough samples\n");

	// loop backwards to avoid overwriting data in the short-to-float conversion
	for( fpp_t frame = frames - 1; frame >= 0; frame-- )
	{
		sample_t s = float(buf[frame])/32768.0;
		for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
		{
			_working_buffer[frame+offset][ch] = s;
		}
	}

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

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

PluginView * SidInstrument::instantiateView( QWidget * _parent )
{
	return( new SidInstrumentView( this, _parent ) );
}

class sidKnob : public Knob
{
public:
	sidKnob( QWidget * _parent ) :
			Knob( knobStyled, _parent )
	{
		setFixedSize( 16, 16 );
		setCenterPointX( 7.5 );
		setCenterPointY( 7.5 );
		setInnerRadius( 2 );
		setOuterRadius( 8 );
		setTotalAngle( 270.0 );
		setLineWidth( 2 );
	}
};

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

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

	m_volKnob = new sidKnob( this );
	m_volKnob->setHintText( tr( "Volume:" ), "" );
	m_volKnob->move( 7, 64 );

	m_resKnob = new sidKnob( this );
	m_resKnob->setHintText( tr( "Resonance:" ), "" );
	m_resKnob->move( 7 + 28, 64 );

	m_cutKnob = new sidKnob( this );
	m_cutKnob->setHintText( tr( "Cutoff frequency:" ), " Hz" );
	m_cutKnob->move( 7 + 2*28, 64 );

	PixmapButton * hp_btn = new PixmapButton( this, NULL );
	hp_btn->move( 140, 77 );
	hp_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "hpred" ) );
	hp_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "hp" ) );
	ToolTip::add( hp_btn, tr( "High-pass filter ") );

	PixmapButton * bp_btn = new PixmapButton( this, NULL );
	bp_btn->move( 164, 77 );
	bp_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "bpred" ) );
	bp_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "bp" ) );
	ToolTip::add( bp_btn, tr( "Band-pass filter ") );

	PixmapButton * lp_btn = new PixmapButton( this, NULL );
	lp_btn->move( 185, 77 );
	lp_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "lpred" ) );
	lp_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "lp" ) );
	ToolTip::add( lp_btn, tr( "Low-pass filter ") );

	m_passBtnGrp = new automatableButtonGroup( this );
	m_passBtnGrp->addButton( hp_btn );
	m_passBtnGrp->addButton( bp_btn );
	m_passBtnGrp->addButton( lp_btn );

	m_offButton = new PixmapButton( this, NULL );
	m_offButton->setCheckable( true );
	m_offButton->move( 207, 77 );
	m_offButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "3offred" ) );
	m_offButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "3off" ) );
	ToolTip::add( m_offButton, tr( "Voice 3 off ") );

	PixmapButton * mos6581_btn = new PixmapButton( this, NULL );
	mos6581_btn->move( 170, 59 );
	mos6581_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "6581red" ) );
	mos6581_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "6581" ) );
	ToolTip::add( mos6581_btn, tr( "MOS6581 SID ") );

	PixmapButton * mos8580_btn = new PixmapButton( this, NULL );
	mos8580_btn->move( 207, 59 );
	mos8580_btn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "8580red" ) );
	mos8580_btn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "8580" ) );
	ToolTip::add( mos8580_btn, tr( "MOS8580 SID ") );

	m_sidTypeBtnGrp = new automatableButtonGroup( this );
	m_sidTypeBtnGrp->addButton( mos6581_btn );
	m_sidTypeBtnGrp->addButton( mos8580_btn );

	for( int i = 0; i < 3; i++ )
	{
		Knob *ak = new sidKnob( this );
		ak->setHintText( tr("Attack:"), "" );
		ak->move( 7, 114 + i*50 );

		Knob *dk = new sidKnob( this );
		dk->setHintText( tr("Decay:") , "" );
		dk->move( 7 + 28, 114 + i*50 );

		Knob *sk = new sidKnob( this );
		sk->setHintText( tr("Sustain:"), "" );
		sk->move( 7 + 2*28, 114 + i*50 );

		Knob *rk = new sidKnob( this );
		rk->setHintText( tr("Release:"), "" );
		rk->move( 7 + 3*28, 114 + i*50 );

		Knob *pwk = new sidKnob( this );
		pwk->setHintText( tr("Pulse Width:"), "" );
		pwk->move( 7 + 4*28, 114 + i*50 );

		Knob *crsk = new sidKnob( this );
		crsk->setHintText( tr("Coarse:"), " semitones" );
		crsk->move( 147, 114 + i*50 );

		PixmapButton * pulse_btn = new PixmapButton( this, NULL );
		pulse_btn->move( 187, 101 + i*50 );
		pulse_btn->setActiveGraphic(
			PLUGIN_NAME::getIconPixmap( "pulsered" ) );
		pulse_btn->setInactiveGraphic(
			PLUGIN_NAME::getIconPixmap( "pulse" ) );
		ToolTip::add( pulse_btn, tr( "Pulse wave" ) );

		PixmapButton * triangle_btn = new PixmapButton( this, NULL );
		triangle_btn->move( 168, 101 + i*50 );
		triangle_btn->setActiveGraphic(
			PLUGIN_NAME::getIconPixmap( "trianglered" ) );
		triangle_btn->setInactiveGraphic(
			PLUGIN_NAME::getIconPixmap( "triangle" ) );
		ToolTip::add( triangle_btn, tr( "Triangle wave" ) );

		PixmapButton * saw_btn = new PixmapButton( this, NULL );
		saw_btn->move( 207, 101 + i*50 );
		saw_btn->setActiveGraphic(
			PLUGIN_NAME::getIconPixmap( "sawred" ) );
		saw_btn->setInactiveGraphic(
			PLUGIN_NAME::getIconPixmap( "saw" ) );
		ToolTip::add( saw_btn, tr( "Saw wave" ) );

		PixmapButton * noise_btn = new PixmapButton( this, NULL );
		noise_btn->move( 226, 101 + i*50 );
		noise_btn->setActiveGraphic(
			PLUGIN_NAME::getIconPixmap( "noisered" ) );
		noise_btn->setInactiveGraphic(
			PLUGIN_NAME::getIconPixmap( "noise" ) );
		ToolTip::add( noise_btn, tr( "Noise" ) );

		automatableButtonGroup * wfbg =
			new automatableButtonGroup( this );

		wfbg->addButton( pulse_btn );
		wfbg->addButton( triangle_btn );
		wfbg->addButton( saw_btn );
		wfbg->addButton( noise_btn );

		PixmapButton * sync_btn = new PixmapButton( this, NULL );
		sync_btn->setCheckable( true );
		sync_btn->move( 207, 134 + i*50 );
		sync_btn->setActiveGraphic(
			PLUGIN_NAME::getIconPixmap( "syncred" ) );
		sync_btn->setInactiveGraphic(
			PLUGIN_NAME::getIconPixmap( "sync" ) );
		ToolTip::add( sync_btn, tr( "Sync" ) );

		PixmapButton * ringMod_btn = new PixmapButton( this, NULL );
		ringMod_btn->setCheckable( true );
		ringMod_btn->move( 170, 116 + i*50 );
		ringMod_btn->setActiveGraphic(
			PLUGIN_NAME::getIconPixmap( "ringred" ) );
		ringMod_btn->setInactiveGraphic(
			PLUGIN_NAME::getIconPixmap( "ring" ) );
		ToolTip::add( ringMod_btn, tr( "Ring modulation" ) );

		PixmapButton * filter_btn = new PixmapButton( this, NULL );
		filter_btn->setCheckable( true );
		filter_btn->move( 207, 116 + i*50 );
		filter_btn->setActiveGraphic(
			PLUGIN_NAME::getIconPixmap( "filterred" ) );
		filter_btn->setInactiveGraphic(
			PLUGIN_NAME::getIconPixmap( "filter" ) );
		ToolTip::add( filter_btn, tr( "Filtered" ) );

		PixmapButton * test_btn = new PixmapButton( this, NULL );
		test_btn->setCheckable( true );
		test_btn->move( 170, 134 + i*50 );
		test_btn->setActiveGraphic(
			PLUGIN_NAME::getIconPixmap( "testred" ) );
		test_btn->setInactiveGraphic(
			PLUGIN_NAME::getIconPixmap( "test" ) );
		ToolTip::add( test_btn, tr( "Test" ) );

		m_voiceKnobs[i] = voiceKnobs( ak, dk, sk, rk, pwk, crsk, wfbg,
								sync_btn, ringMod_btn, filter_btn, test_btn );
	}
}

SidInstrumentView::~SidInstrumentView()
{
}

void SidInstrumentView::updateKnobHint()
{
	SidInstrument * k = castModel<SidInstrument>();

	for( int i = 0; i < 3; ++i )
	{
		m_voiceKnobs[i].m_attKnob->setHintText( tr( "Attack:" ) + " ", " (" +
				QString::fromLatin1( attackTime[(int)k->m_voice[i]->
				m_attackModel.value()] ) + ")" );
		ToolTip::add( m_voiceKnobs[i].m_attKnob,
						attackTime[(int)k->m_voice[i]->m_attackModel.value()] );

		m_voiceKnobs[i].m_decKnob->setHintText( tr( "Decay:" ) + " ", " (" +
				QString::fromLatin1( decRelTime[(int)k->m_voice[i]->
				m_decayModel.value()] ) + ")" );
		ToolTip::add( m_voiceKnobs[i].m_decKnob,
						decRelTime[(int)k->m_voice[i]->m_decayModel.value()] );

		m_voiceKnobs[i].m_relKnob->setHintText( tr( "Release:" ) + " ", " (" +
				QString::fromLatin1( decRelTime[(int)k->m_voice[i]->
				m_releaseModel.value()] )  + ")" );
		ToolTip::add( m_voiceKnobs[i].m_relKnob,
						decRelTime[(int)k->m_voice[i]->m_releaseModel.value()]);

		m_voiceKnobs[i].m_pwKnob->setHintText( tr( "Pulse width:" )+ " ", " (" +
				QString::number(  (double)k->m_voice[i]->
				m_pulseWidthModel.value() / 40.95 ) + "%)" );
		ToolTip::add( m_voiceKnobs[i].m_pwKnob,
				QString::number( (double)k->m_voice[i]->
				m_pulseWidthModel.value() / 40.95 ) + "%" );
	}
	m_cutKnob->setHintText( tr( "Cutoff frequency:" ) + " ", " (" +
				QString::number ( (int) ( 9970.0 / 2047.0 *
				(double)k->m_filterFCModel.value() + 30.0 ) ) + " Hz)" );
	ToolTip::add( m_cutKnob, QString::number( (int) ( 9970.0 / 2047.0 *
					 (double)k->m_filterFCModel.value() + 30.0 ) ) + " Hz" );
}

void SidInstrumentView::updateKnobToolTip()
{
	SidInstrument * k = castModel<SidInstrument>();
	for( int i = 0; i < 3; ++i )
	{
		ToolTip::add( m_voiceKnobs[i].m_sustKnob,
				QString::number( (int)k->m_voice[i]->m_sustainModel.value() ) );
		ToolTip::add( m_voiceKnobs[i].m_crsKnob,
				QString::number( (int)k->m_voice[i]->m_coarseModel.value() ) +
				" semitones" );
	}
	ToolTip::add( m_volKnob,
					QString::number( (int)k->m_volumeModel.value() ) );
	ToolTip::add( m_resKnob,
					QString::number( (int)k->m_filterResonanceModel.value() ) );
}

void SidInstrumentView::modelChanged()
{
	SidInstrument * k = castModel<SidInstrument>();

	m_volKnob->setModel( &k->m_volumeModel );
	m_resKnob->setModel( &k->m_filterResonanceModel );
	m_cutKnob->setModel( &k->m_filterFCModel );
	m_passBtnGrp->setModel( &k->m_filterModeModel );
	m_offButton->setModel(  &k->m_voice3OffModel );
	m_sidTypeBtnGrp->setModel(  &k->m_chipModel );

	for( int i = 0; i < 3; ++i )
	{
		m_voiceKnobs[i].m_attKnob->setModel(
					&k->m_voice[i]->m_attackModel );
		m_voiceKnobs[i].m_decKnob->setModel(
					&k->m_voice[i]->m_decayModel );
		m_voiceKnobs[i].m_sustKnob->setModel(
					&k->m_voice[i]->m_sustainModel );
		m_voiceKnobs[i].m_relKnob->setModel(
					&k->m_voice[i]->m_releaseModel );
		m_voiceKnobs[i].m_pwKnob->setModel(
					&k->m_voice[i]->m_pulseWidthModel );
		m_voiceKnobs[i].m_crsKnob->setModel(
					&k->m_voice[i]->m_coarseModel );
		m_voiceKnobs[i].m_waveFormBtnGrp->setModel(
					&k->m_voice[i]->m_waveFormModel );
		m_voiceKnobs[i].m_syncButton->setModel(
					&k->m_voice[i]->m_syncModel );
		m_voiceKnobs[i].m_ringModButton->setModel(
					&k->m_voice[i]->m_ringModModel );
		m_voiceKnobs[i].m_filterButton->setModel(
					&k->m_voice[i]->m_filteredModel );
		m_voiceKnobs[i].m_testButton->setModel(
					&k->m_voice[i]->m_testModel );
	}

	for( int i = 0; i < 3; ++i )
	{
		connect( &k->m_voice[i]->m_attackModel, SIGNAL( dataChanged() ),
			this, SLOT( updateKnobHint() ) );
		connect( &k->m_voice[i]->m_decayModel, SIGNAL( dataChanged() ),
			this, SLOT( updateKnobHint() ) );
		connect( &k->m_voice[i]->m_releaseModel, SIGNAL( dataChanged() ),
			this, SLOT( updateKnobHint() ) );
		connect( &k->m_voice[i]->m_pulseWidthModel, SIGNAL( dataChanged() ),
			this, SLOT( updateKnobHint() ) );
		connect( &k->m_voice[i]->m_sustainModel, SIGNAL( dataChanged() ),
			this, SLOT( updateKnobToolTip() ) );
		connect( &k->m_voice[i]->m_coarseModel, SIGNAL( dataChanged() ),
			this, SLOT( updateKnobToolTip() ) );
	}

	connect( &k->m_volumeModel, SIGNAL( dataChanged() ),
		this, SLOT( updateKnobToolTip() ) );
	connect( &k->m_filterResonanceModel, SIGNAL( dataChanged() ),
		this, SLOT( updateKnobToolTip() ) );
	connect( &k->m_filterFCModel, SIGNAL( dataChanged() ),
		this, SLOT( updateKnobHint() ) );

	updateKnobHint();
	updateKnobToolTip();
}

extern "C"
{

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

}


code:
/*
 * SidInstrument.h - ResID based software-synthesizer
 *
 * Copyright (c) 2008 Csaba Hruska <csaba.hruska/at/gmail.com>
 *                    Attila Herman <attila589/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 _SID_H
#define _SID_H

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

class SidInstrumentView;
class NotePlayHandle;
class automatableButtonGroup;
class PixmapButton;

class voiceObject : public Model
{
	Q_OBJECT
	MM_OPERATORS
public:
	enum WaveForm {
		SquareWave = 0,
		TriangleWave,
		SawWave,
		NoiseWave,
		NumWaveShapes
	};
	voiceObject( Model * _parent, int _idx );
	virtual ~voiceObject();

private:
	FloatModel m_pulseWidthModel;
	FloatModel m_attackModel;
	FloatModel m_decayModel;
	FloatModel m_sustainModel;
	FloatModel m_releaseModel;
	FloatModel m_coarseModel;
	IntModel m_waveFormModel;
	BoolModel m_syncModel;
	BoolModel m_ringModModel;
	BoolModel m_filteredModel;
	BoolModel m_testModel;

	friend class SidInstrument;
	friend class SidInstrumentView;
} ;

class SidInstrument : public Instrument
{
	Q_OBJECT
public:
	enum FilerType {
		HighPass = 0,
		BandPass,
		LowPass,
		NumFilterTypes
	};

	enum ChipModel {
		sidMOS6581 = 0,
		sidMOS8580,
		NumChipModels
	};

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

	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:
	// voices
	voiceObject * m_voice[3];

	// filter
	FloatModel m_filterFCModel;
	FloatModel m_filterResonanceModel;
	IntModel m_filterModeModel;

	// misc
	BoolModel m_voice3OffModel;
	FloatModel m_volumeModel;

	IntModel m_chipModel;

	friend class SidInstrumentView;

} ;

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

private:
	virtual void modelChanged();

	automatableButtonGroup * m_passBtnGrp;
	automatableButtonGroup * m_sidTypeBtnGrp;

	struct voiceKnobs
	{
		voiceKnobs( Knob * a,
					Knob * d,
					Knob * s,
					Knob * r,
					Knob * pw,
					Knob * crs,
					automatableButtonGroup * wfbg,
					PixmapButton * syncb,
					PixmapButton * ringb,
					PixmapButton * filterb,
					PixmapButton * testb ) :
			m_attKnob( a ),
			m_decKnob( d ),
			m_sustKnob( s ),
			m_relKnob( r ),
			m_pwKnob( pw ),
			m_crsKnob( crs ),
			m_waveFormBtnGrp( wfbg ),
			m_syncButton( syncb ),
			m_ringModButton( ringb ),
			m_filterButton( filterb ),
			m_testButton( testb )
		{
		}
		voiceKnobs()
		{
		}
		Knob * m_attKnob;
		Knob * m_decKnob;
		Knob * m_sustKnob;
		Knob * m_relKnob;
		Knob * m_pwKnob;
		Knob * m_crsKnob;
		automatableButtonGroup * m_waveFormBtnGrp;
		PixmapButton * m_syncButton;
		PixmapButton * m_ringModButton;
		PixmapButton * m_filterButton;
		PixmapButton * m_testButton;
	} ;

	voiceKnobs m_voiceKnobs[3];

	Knob * m_volKnob;
	Knob * m_resKnob;
	Knob * m_cutKnob;
	PixmapButton * m_offButton;

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

#endif


code:
/*
 * Analyzer.cpp - definition of Analyzer class.
 *
 * Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
 *
 * Based partially on Eq plugin code,
 * Copyright (c) 2014-2017, 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 "Analyzer.h"

#ifdef SA_DEBUG
	#include <chrono>
	#include <iostream>
#endif

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

extern "C" {
	Plugin::Descriptor PLUGIN_EXPORT analyzer_plugin_descriptor =
	{
		"spectrumanalyzer",
		"Spectrum Analyzer",
		QT_TRANSLATE_NOOP("PluginBrowser", "A graphical spectrum analyzer."),
		"Martin Pavelek <he29/dot/HS/at/gmail/dot/com>",
		0x0112,
		Plugin::Effect,
		new PluginPixmapLoader("logo"),
		NULL,
		NULL
	};
}

Analyzer::Analyzer(Model *parent, const Plugin::Descriptor::SubPluginFeatures::Key *key) :
	Effect(&analyzer_plugin_descriptor, parent, key),
	m_processor(&m_controls),
	m_controls(this),
	m_processorThread(m_processor, m_inputBuffer),
	// Buffer is sized to cover 4* the current maximum LMMS audio buffer size,
	// so that it has some reserve space in case data processor is busy.
	m_inputBuffer(4 * m_maxBufferSize)
{
	m_processorThread.start();
}

Analyzer::~Analyzer()
{
	m_processor.terminate();
	m_inputBuffer.wakeAll();
	m_processorThread.wait();
}

// Take audio data and pass them to the spectrum processor.
bool Analyzer::processAudioBuffer(sampleFrame *buffer, const fpp_t frame_count)
{
	// Measure time spent in audio thread; both average and peak should be well under 1 ms.
	#ifdef SA_DEBUG
		unsigned int audio_time = std::chrono::high_resolution_clock::now().time_since_epoch().count();
		if (audio_time - m_last_dump_time > 5000000000)	// print every 5 seconds
		{
			std::cout << "Analyzer audio thread: " << m_sum_execution / m_dump_count << " ms avg / "
				<< m_max_execution << " ms peak." << std::endl;
			m_last_dump_time = audio_time;
			m_sum_execution = m_max_execution = m_dump_count = 0;
		}
	#endif

	if (!isEnabled() || !isRunning ()) {return false;}

	// Skip processing if the controls dialog isn't visible, it would only waste CPU cycles.
	if (m_controls.isViewVisible())
	{
		// To avoid processing spikes on audio thread, data are stored in
		// a lockless ringbuffer and processed in a separate thread.
		m_inputBuffer.write(buffer, frame_count, true);
	}
	#ifdef SA_DEBUG
		audio_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - audio_time;
		m_dump_count++;
		m_sum_execution += audio_time / 1000000.0;
		if (audio_time / 1000000.0 > m_max_execution) {m_max_execution = audio_time / 1000000.0;}
	#endif

	return isRunning();
}

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


code:
/* Analyzer.h - declaration of Analyzer class.
 *
 * Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
 *
 * Based partially on Eq plugin code,
 * Copyright (c) 2014-2017, 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 ANALYZER_H
#define ANALYZER_H

#include <QWaitCondition>

#include "DataprocLauncher.h"
#include "Effect.h"
#include "LocklessRingBuffer.h"
#include "SaControls.h"
#include "SaProcessor.h"

//! Top level class; handles LMMS interface and feeds data to the data processor.
class Analyzer : public Effect
{
public:
	Analyzer(Model *parent, const Descriptor::SubPluginFeatures::Key *key);
	virtual ~Analyzer();

	bool processAudioBuffer(sampleFrame *buffer, const fpp_t frame_count) override;
	EffectControls *controls() override {return &m_controls;}

	SaProcessor *getProcessor() {return &m_processor;}

private:
	SaProcessor m_processor;
	SaControls m_controls;

	// Maximum LMMS buffer size (hard coded, the actual constant is hard to get)
	const unsigned int m_maxBufferSize = 4096;

	// QThread::create() workaround
	// Replace DataprocLauncher by QThread and replace initializer in constructor
	// with the following commented line when LMMS CI starts using Qt > 5.9
	//m_processorThread = QThread::create([=]{m_processor.analyze(m_inputBuffer);});
	DataprocLauncher m_processorThread;

	LocklessRingBuffer<sampleFrame> m_inputBuffer;

	#ifdef SA_DEBUG
		int m_last_dump_time;
		int m_dump_count;
		float m_sum_execution;
		float m_max_execution;
	#endif
};

#endif // ANALYZER_H


code:
INCLUDE(BuildPlugin)
INCLUDE_DIRECTORIES(${FFTW3F_INCLUDE_DIRS})

LINK_LIBRARIES(${FFTW3F_LIBRARIES})

BUILD_PLUGIN(analyzer Analyzer.cpp SaProcessor.cpp SaControls.cpp SaControlsDialog.cpp SaSpectrumView.cpp SaWaterfallView.cpp
MOCFILES SaProcessor.h SaControls.h SaControlsDialog.h SaSpectrumView.h SaWaterfallView.h DataprocLauncher.h EMBEDDED_RESOURCES *.svg logo.png)


code:
/*
 * DataprocLauncher.h - QThread::create workaround for older Qt version
 *
 * Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef DATAPROCLAUNCHER_H
#define DATAPROCLAUNCHER_H

#include <QThread>

#include "SaProcessor.h"
#include "LocklessRingBuffer.h"

class DataprocLauncher : public QThread
{
public:
	explicit DataprocLauncher(SaProcessor &proc, LocklessRingBuffer<sampleFrame> &buffer)
		: m_processor(&proc),
		m_inputBuffer(&buffer)
	{
	}

private:
	void run() override
	{
		m_processor->analyze(*m_inputBuffer);
	}

	SaProcessor *m_processor;
	LocklessRingBuffer<sampleFrame> *m_inputBuffer;
};

#endif // DATAPROCLAUNCHER_H


code:
# Spectrum Analyzer plugin

## Overview

This plugin consists of three widgets and back-end code to provide them with required data.

The top-level widget is `SaControlDialog`. It populates configuration widgets (created dynamically) and instantiates spectrum display widgets. Its main back-end class is `SaControls`, which holds all configuration values.

`SaSpectrumView` and `SaWaterfallView` widgets show the result of spectrum analysis. Their main back-end class is `SaProcessor`, which performs FFT analysis on data received from the `Analyzer` class, which in turn handles the interface with LMMS.

## Threads

The Spectrum Analyzer is involved in three different threads:
 - **Effect mixer thread**: periodically calls `Analyzer::processAudioBuffer()` to provide the plugin with more data. This thread is real-time sensitive -- any latency spikes can potentially cause interruptions in the audio stream. For this reason, `Analyzer::processAudioBuffer()` must finish as fast as possible and must not call any functions that could cause it to be delayed for unpredictable amount of time. A lock-less ring buffer is used to safely feed data to the FFT analysis thread without risking any latency spikes due to a shared mutex being unavailable at the time of writing.
 - **FFT analysis thread**: a standalone thread formed by the `SaProcessor::analyze()` function. Takes in data from the ring buffer, performs FFT analysis and prepares results for display. This thread is not real-time sensitive but excessive locking is discouraged to maintain good performance.
 - **GUI thread**: periodically triggers `paintEvent()` of all Qt widgets, including `SaSpectrumView` and `SaWaterfallView`. While it is not as sensitive to latency spikes as the effect mixer thread, the `paintEvent()`s appear to be called sequentially and the execution time of each widget therefore adds to the total time needed to complete one full refresh cycle. This means the maximum frame rate of the Qt GUI will be limited to `1 / total_execution_time`. Good performance of the `paintEvent()` functions should be therefore kept in mind.

## Changelog
	1.1.2	2019-11-18
		- waterfall is no longer cut short when width limit is reached
		- various small tweaks based on final review
	1.1.1	2019-10-13
		- improved interface for accessing SaProcessor private data
		- readme file update
		- other small improvements based on reviews
	1.1.0	2019-08-29
		- advanced config: expose hidden constants to user
		- advanced config: add support for FFT window overlapping
		- waterfall: display at native resolution on high-DPI screens
		- waterfall: add cursor and improve label density
		- FFT: fix normalization so that 0 dBFS matches full-scale sinewave
		- FFT: decouple data acquisition from processing and display
		- FFT: separate lock for reallocation (to avoid some needless waiting)
		- moved ranges and other constants to a separate file
		- debug: better performance measurements
		- various performance optimizations
	1.0.3	2019-07-25
		- rename and tweak amplitude ranges based on feedback
	1.0.2	2019-07-12
		- variety of small changes based on code review
	1.0.1	2019-06-02
		- code style changes
		- added tool-tips
		- use const for unmodified arrays passed to fft_helpers
	1.0.0	2019-04-07
		- initial release


code:
/*
 * SaControls.cpp - definition of SaControls class.
 *
 * Copyright (c) 2019 Martin Pavelek <he29/dot/HS/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 "SaControls.h"

#include <QtXml/QDomElement>

#include "Analyzer.h"
#include "SaControlsDialog.h"

SaControls::SaControls(Analyzer *effect) :
	EffectControls(effect),
	m_effect(effect),

	// initialize bool models and set default values
	m_pauseModel(false, this, tr("Pause")),
	m_refFreezeModel(false, this, tr("Reference freeze")),

	m_waterfallModel(false, this, tr("Waterfall")),
	m_smoothModel(false, this, tr("Averaging")),
	m_stereoModel(false, this, tr("Stereo")),
	m_peakHoldModel(false, this, tr("Peak hold")),

	m_logXModel(true, this, tr("Logarithmic frequency")),
	m_logYModel(true, this, tr("Logarithmic amplitude")),

	// default values of combo boxes are set after they are populated
	m_freqRangeModel(this, tr("Frequency range")),
	m_ampRangeModel(this, tr("Amplitude range")),
	m_blockSizeModel(this, tr("FFT block size")),
	m_windowModel(this, tr("FFT window type")),

	// Advanced settings knobs
	m_envelopeResolutionModel(0.25f, 0.1f, 3.0f, 0.05f, this, tr("Peak envelope resolution")),
	m_spectrumResolutionModel(1.5f, 0.1f, 3.0f, 0.05f, this, tr("Spectrum display resolution")),
	m_peakDecayFactorModel(0.992f, 0.95f, 0.999f, 0.001f, this, tr("Peak decay multiplier")),
	m_averagingWeightModel(0.15f, 0.01f, 0.5f, 0.01f, this, tr("Averaging weight")),
	m_waterfallHeightModel(300.0f, 50.0f, 1000.0f, 50.0f, this, tr("Waterfall history size")),
	m_waterfallGammaModel(0.30f, 0.10f, 1.00f, 0.05f, this, tr("Waterfall gamma correction")),
	m_windowOverlapModel(2.0f, 1.0f, 4.0f, 1.0f, this, tr("FFT window overlap")),
	m_zeroPaddingModel(2.0f, 0.0f, 4.0f, 1.0f, this, tr("FFT zero padding"))
{
	// Frequency and amplitude ranges; order must match
	// FREQUENCY_RANGES and AMPLITUDE_RANGES defined in SaControls.h
	m_freqRangeModel.addItem(tr("Full (auto)"));
	m_freqRangeModel.addItem(tr("Audible"));
	m_freqRangeModel.addItem(tr("Bass"));
	m_freqRangeModel.addItem(tr("Mids"));
	m_freqRangeModel.addItem(tr("High"));
	m_freqRangeModel.setValue(m_freqRangeModel.findText(tr("Full (auto)")));

	m_ampRangeModel.addItem(tr("Extended"));
	m_ampRangeModel.addItem(tr("Audible"));
	m_ampRangeModel.addItem(tr("Loud"));
	m_ampRangeModel.addItem(tr("Silent"));
	m_ampRangeModel.setValue(m_ampRangeModel.findText(tr("Audible")));

	// FFT block size labels are generated automatically, based on
	// FFT_BLOCK_SIZES vector defined in fft_helpers.h
	for (unsigned int i = 0; i < FFT_BLOCK_SIZES.size(); i++)
	{
		if (i == 0)
		{
			m_blockSizeModel.addItem((std::to_string(FFT_BLOCK_SIZES[i]) + " ").c_str() + tr("(High time res.)"));
		}
		else if (i == FFT_BLOCK_SIZES.size() - 1)
		{
			m_blockSizeModel.addItem((std::to_string(FFT_BLOCK_SIZES[i]) + " ").c_str() + tr("(High freq. res.)"));
		}
		else
		{
			m_blockSizeModel.addItem(std::to_string(FFT_BLOCK_SIZES[i]).c_str());
		}
	}
	m_blockSizeModel.setValue(m_blockSizeModel.findText("2048"));

	// Window type order must match FFT_WINDOWS defined in fft_helpers.h
	m_windowModel.addItem(tr("Rectangular (Off)"));
	m_windowModel.addItem(tr("Blackman-Harris (Default)"));
	m_windowModel.addItem(tr("Hamming"));
	m_windowModel.addItem(tr("Hanning"));
	m_windowModel.setValue(m_windowModel.findText(tr("Blackman-Harris (Default)")));

	// Colors
	// Background color is defined by Qt / theme.
	// Make sure the sum of colors for L and R channel results into a neutral
	// color that has at least one component equal to 255 (i.e. ideally white).
	// This means the color overflows to zero exactly when signal reaches
	// clipping threshold, indicating the problematic frequency to user.
	// Mono waterfall color should have similarly at least one component at 255.
	m_colorL = QColor(51, 148, 204, 135);
	m_colorR = QColor(204, 107, 51, 135);
	m_colorMono = QColor(51, 148, 204, 204);
	m_colorMonoW = QColor(64, 185, 255, 255);
	m_colorBG = QColor(7, 7, 7, 255);			// ~20 % gray (after gamma correction)
	m_colorGrid = QColor(30, 34, 38, 255);		// ~40 % gray (slightly cold / blue)
	m_colorLabels = QColor(192, 202, 212, 255);	// ~90 % gray (slightly cold / blue)
}

// Create the SaControlDialog widget which handles display of GUI elements.
EffectControlDialog* SaControls::createView()
{
	return new SaControlsDialog(this, m_effect->getProcessor());
}

void SaControls::loadSettings(const QDomElement &_this)
{
	m_waterfallModel.loadSettings(_this, "Waterfall");
	m_smoothModel.loadSettings(_this, "Smooth");
	m_stereoModel.loadSettings(_this, "Stereo");
	m_peakHoldModel.loadSettings(_this, "PeakHold");
	m_logXModel.loadSettings(_this, "LogX");
	m_logYModel.loadSettings(_this, "LogY");
	m_freqRangeModel.loadSettings(_this, "RangeX");
	m_ampRangeModel.loadSettings(_this, "RangeY");
	m_blockSizeModel.loadSettings(_this, "BlockSize");
	m_windowModel.loadSettings(_this, "WindowType");

	m_envelopeResolutionModel.loadSettings(_this, "EnvelopeRes");
	m_spectrumResolutionModel.loadSettings(_this, "SpectrumRes");
	m_peakDecayFactorModel.loadSettings(_this, "PeakDecayFactor");
	m_averagingWeightModel.loadSettings(_this, "AverageWeight");
	m_waterfallHeightModel.loadSettings(_this, "WaterfallHeight");
	m_waterfallGammaModel.loadSettings(_this, "WaterfallGamma");
	m_windowOverlapModel.loadSettings(_this, "WindowOverlap");
	m_zeroPaddingModel.loadSettings(_this, "ZeroPadding");
}

void SaControls::saveSettings(QDomDocument &doc, QDomElement &parent)
{
	m_waterfallModel.saveSettings(doc, parent, "Waterfall");
	m_smoothModel.saveSettings(doc, parent, "Smooth");
	m_stereoModel.saveSettings(doc, parent, "Stereo");
	m_peakHoldModel.saveSettings(doc, parent, "PeakHold");
	m_logXModel.saveSettings(doc, parent, "LogX");
	m_logYModel.saveSettings(doc, parent, "LogY");
	m_freqRangeModel.saveSettings(doc, parent, "RangeX");
	m_ampRangeModel.saveSettings(doc, parent, "RangeY");
	m_blockSizeModel.saveSettings(doc, parent, "BlockSize");
	m_windowModel.saveSettings(doc, parent, "WindowType");

	m_envelopeResolutionModel.saveSettings(doc, parent, "EnvelopeRes");
	m_spectrumResolutionModel.saveSettings(doc, parent, "SpectrumRes");
	m_peakDecayFactorModel.saveSettings(doc, parent, "PeakDecayFactor");
	m_averagingWeightModel.saveSettings(doc, parent, "AverageWeight");
	m_waterfallHeightModel.saveSettings(doc, parent, "WaterfallHeight");
	m_waterfallGammaModel.saveSettings(doc, parent, "WaterfallGamma");
	m_windowOverlapModel.saveSettings(doc, parent, "WindowOverlap");
	m_zeroPaddingModel.saveSettings(doc, parent, "ZeroPadding");

}


code:
/*
 * SaControls.h - declaration of SaControls class.
 *
 * Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef SACONTROLS_H
#define SACONTROLS_H

#include "ComboBoxModel.h"
#include "EffectControls.h"
#include "lmms_constants.h"

//#define SA_DEBUG 1	// define SA_DEBUG to enable performance measurements

class Analyzer;

// Holds all the configuration values
class SaControls : public EffectControls
{
	Q_OBJECT
public:
	explicit SaControls(Analyzer* effect);
	virtual ~SaControls() {}

	EffectControlDialog* createView() override;

	void saveSettings (QDomDocument& doc, QDomElement& parent) override;
	void loadSettings (const QDomElement &_this) override;

	QString nodeName() const override {return "Analyzer";}
	int controlCount() override {return 20;}

private:
	Analyzer *m_effect;

	// basic settings
	BoolModel m_pauseModel;
	BoolModel m_refFreezeModel;

	BoolModel m_waterfallModel;
	BoolModel m_smoothModel;
	BoolModel m_stereoModel;
	BoolModel m_peakHoldModel;

	BoolModel m_logXModel;
	BoolModel m_logYModel;

	ComboBoxModel m_freqRangeModel;
	ComboBoxModel m_ampRangeModel;
	ComboBoxModel m_blockSizeModel;
	ComboBoxModel m_windowModel;

	// advanced settings
	FloatModel m_envelopeResolutionModel;
	FloatModel m_spectrumResolutionModel;
	FloatModel m_peakDecayFactorModel;
	FloatModel m_averagingWeightModel;
	FloatModel m_waterfallHeightModel;
	FloatModel m_waterfallGammaModel;
	FloatModel m_windowOverlapModel;
	FloatModel m_zeroPaddingModel;

	// colors (hard-coded, values must add up to specific numbers)
	QColor m_colorL;		//!< color of the left channel
	QColor m_colorR;		//!< color of the right channel
	QColor m_colorMono;		//!< mono color for spectrum display
	QColor m_colorMonoW;	//!< mono color for waterfall display
	QColor m_colorBG;		//!< spectrum display background color
	QColor m_colorGrid;		//!< color of grid lines
	QColor m_colorLabels;	//!< color of axis labels

	friend class SaControlsDialog;
	friend class SaSpectrumView;
	friend class SaWaterfallView;
	friend class SaProcessor;
};
#endif // SACONTROLS_H