/* * Monstro.h - a semi-modular 3-osc synth with modulation matrix * * Copyright (c) 2014 Vesa Kivimäki * * This file is part of LMMS - http://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 MONSTRO_H #define MONSTRO_H #include "Instrument.h" #include "InstrumentView.h" #include "AutomatableModel.h" #include "AutomatableButton.h" #include "TempoSyncKnob.h" #include "NotePlayHandle.h" #include "PixmapButton.h" #include "ComboBox.h" #include "Oscillator.h" #include "lmms_math.h" #include "BandLimitedWave.h" // // UI Macros // #define makeknob( name, x, y, hint, unit, oname ) \ name = new Knob( knobStyled, view ); \ name ->move( x, y ); \ name ->setHintText( hint, unit ); \ name ->setObjectName( oname ); \ name ->setFixedSize( 20, 20 ); #define maketsknob( name, x, y, hint, unit, oname ) \ name = new TempoSyncKnob( knobStyled, view ); \ name ->move( x, y ); \ name ->setHintText( hint, unit ); \ name ->setObjectName( oname ); \ name ->setFixedSize( 20, 20 ); #define maketinyled( name, x, y, ttip ) \ name = new PixmapButton( view, NULL ); \ name -> setCheckable( true ); \ name -> move( x, y ); \ name -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "tinyled_on" ) ); \ name -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "tinyled_off" ) ); \ ToolTip::add( name, ttip ); // UI constants const int O1ROW = 22; const int O2ROW = 22 + 39; const int O3ROW = 22 + 39 * 2; const int LFOROW = 22 + 39 * 3; const int E1ROW = 22 + 39 * 4; const int E2ROW = 22 + 39 * 5; const int KNOBCOL1 = 16; const int KNOBCOL2 = 16 + 30; const int KNOBCOL3 = 16 + 30 * 2; const int KNOBCOL4 = 16 + 30 * 3; const int KNOBCOL5 = 16 + 30 * 4; const int KNOBCOL6 = 16 + 30 * 5; const int KNOBCOL7 = 16 + 30 * 6; const int LFOCOL1 = KNOBCOL2; const int LFOCOL2 = KNOBCOL2 + 26; const int LFOCOL3 = KNOBCOL2 + 26*2; const int LFOCOL4 = 171; const int LFOCOL5 = 171 + 26; const int LFOCOL6 = 171 + 26*2; const int MATCOL1 = 32; const int MATCOL2 = 32 + 25; const int MATCOL3 = 32 + 25*2; const int MATCOL4 = 32 + 25*3; const int MATCOL5 = 149; const int MATCOL6 = 149 + 25; const int MATCOL7 = 149 + 25*2; const int MATCOL8 = 149 + 25*3; const int MATROW1 = 22; const int MATROW2 = 22 + 39; const int MATROW3 = 22 + 39*2; const int MATROW4 = 22 + 39*3; const int MATROW5 = 22 + 39*4; const int MATROW6 = 22 + 39*5; const int OPVIEW = 0; const int MATVIEW = 1; // waveform enumerators const int WAVE_SINE = 0; const int WAVE_TRI = 1; const int WAVE_SAW = 2; const int WAVE_RAMP = 3; const int WAVE_SQR = 4; const int WAVE_MOOG = 5; const int WAVE_SQRSOFT = 6; const int WAVE_SINABS = 7; const int WAVE_EXP = 8; const int WAVE_NOISE = 9; const int WAVE_TRI_D = 10; const int WAVE_SAW_D = 11; const int WAVE_RAMP_D = 12; const int WAVE_SQR_D = 13; const int WAVE_MOOG_D = 14; const int NUM_WAVES = 15; // lfo-specific enumerators const int WAVE_RANDOM = 9; const int WAVE_RANDOM_SMOOTH = 10; const int NUM_LFO_WAVES = 11; // modulation enumerators const int MOD_MIX = 0; const int MOD_AM = 1; const int MOD_FM = 2; const int MOD_PM = 3; const int NUM_MODS = 4; const float MODCLIP = 2.0; const float MIN_FREQ = 18.0f; const float MAX_FREQ = 48000.0f; const float INTEGRATOR = 3.0f / 7.0f; const float FM_AMOUNT = 0.25f; const float PW_MIN = 0.25f; const float PW_MAX = 100.0f - PW_MIN; class MonstroInstrument; class MonstroView; class MonstroSynth { MM_OPERATORS public: MonstroSynth( MonstroInstrument * _i, NotePlayHandle * _nph ); virtual ~MonstroSynth(); void renderOutput( fpp_t _frames, sampleFrame * _buf ); private: MonstroInstrument * m_parent; NotePlayHandle * m_nph; inline void updateModulators( float * env1, float * env2, float * lfo1, float * lfo2, int frames ); // linear interpolation /* inline sample_t interpolate( sample_t s1, sample_t s2, float x ) { return s1 + ( s2 - s1 ) * x; }*/ // using interpolation.h from now on inline sample_t calcSlope( int slope, sample_t s ); // checks for lower bound for phase, upper bound is already checked by oscillator-functions in both // oscillator.h and bandlimitedwave.h so we save some cpu by only checking lower bound inline float lowBoundCheck( float ph ) { return ph < 0.0f ? ph - ( static_cast( ph ) - 1.0f ) : ph; } inline sample_t oscillate( int _wave, const float _ph, float _wavelen ) { switch( _wave ) { case WAVE_SINE: return Oscillator::sinSample( _ph ); break; case WAVE_TRI: //return Oscillator::triangleSample( _ph ); return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLTriangle ); break; case WAVE_SAW: //return Oscillator::sawSample( _ph ); return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLSaw ); break; case WAVE_RAMP: //return Oscillator::sawSample( _ph ) * -1.0; return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLSaw ) * -1.0; break; case WAVE_SQR: //return Oscillator::squareSample( _ph ); return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLSquare ); break; case WAVE_SQRSOFT: { const float ph = fraction( _ph ); if( ph < 0.1 ) return Oscillator::sinSample( ph * 5 + 0.75 ); else if( ph < 0.5 ) return 1.0f; else if( ph < 0.6 ) return Oscillator::sinSample( ph * 5 + 0.75 ); else return -1.0f; break; } case WAVE_MOOG: //return Oscillator::moogSawSample( _ph ); return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLMoog ); break; case WAVE_SINABS: return qAbs( Oscillator::sinSample( _ph ) ); break; case WAVE_EXP: return Oscillator::expSample( _ph ); break; case WAVE_NOISE: return Oscillator::noiseSample( _ph ); break; case WAVE_TRI_D: return Oscillator::triangleSample( _ph ); break; case WAVE_SAW_D: return Oscillator::sawSample( _ph ); break; case WAVE_RAMP_D: return Oscillator::sawSample( _ph ) * -1.0; break; case WAVE_SQR_D: return Oscillator::squareSample( _ph ); break; case WAVE_MOOG_D: return Oscillator::moogSawSample( _ph ); break; } return 0.0; } float m_osc1l_phase; float m_osc1r_phase; float m_osc2l_phase; float m_osc2r_phase; float m_osc3l_phase; float m_osc3r_phase; sample_t m_env_phase [2]; float m_lfo_phase [2]; sample_t m_lfo_last [2]; sample_t m_lfo_next [2]; float m_lfo_inc [2]; float m_lfo_rate [2]; float m_env_sus [2]; int m_lfovalue[2]; int m_lfoatt[2]; float m_env_pre[2]; float m_env_att[2]; float m_env_hold[2]; float m_env_dec[2]; float m_env_rel[2]; sample_t m_osc1l_last; sample_t m_osc1r_last; sample_t m_l_last; sample_t m_r_last; float m_ph2l_last; float m_ph2r_last; float m_ph3l_last; float m_ph3r_last; bool m_invert2l; bool m_invert3l; bool m_invert2r; bool m_invert3r; int m_counter2l; int m_counter2r; int m_counter3l; int m_counter3r; }; class MonstroInstrument : public Instrument { Q_OBJECT #define setwavemodel( name ) \ name .addItem( tr( "Sine wave" ), static_cast( new PluginPixmapLoader( "sin" ) ) ); \ name .addItem( tr( "Bandlimited Triangle wave" ), static_cast( new PluginPixmapLoader( "tri" ) ) ); \ name .addItem( tr( "Bandlimited Saw wave" ), static_cast( new PluginPixmapLoader( "saw" ) ) ); \ name .addItem( tr( "Bandlimited Ramp wave" ), static_cast( new PluginPixmapLoader( "ramp" ) ) ); \ name .addItem( tr( "Bandlimited Square wave" ), static_cast( new PluginPixmapLoader( "sqr" ) ) ); \ name .addItem( tr( "Bandlimited Moog saw wave" ), static_cast( new PluginPixmapLoader( "moog" ) ) ); \ name .addItem( tr( "Soft square wave" ), static_cast( new PluginPixmapLoader( "sqrsoft" ) ) ); \ name .addItem( tr( "Absolute sine wave" ), static_cast( new PluginPixmapLoader( "sinabs" ) ) ); \ name .addItem( tr( "Exponential wave" ), static_cast( new PluginPixmapLoader( "exp" ) ) ); \ name .addItem( tr( "White noise" ), static_cast( new PluginPixmapLoader( "noise" ) ) ); \ name .addItem( tr( "Digital Triangle wave" ), static_cast( new PluginPixmapLoader( "tri" ) ) ); \ name .addItem( tr( "Digital Saw wave" ), static_cast( new PluginPixmapLoader( "saw" ) ) ); \ name .addItem( tr( "Digital Ramp wave" ), static_cast( new PluginPixmapLoader( "ramp" ) ) ); \ name .addItem( tr( "Digital Square wave" ), static_cast( new PluginPixmapLoader( "sqr" ) ) ); \ name .addItem( tr( "Digital Moog saw wave" ), static_cast( new PluginPixmapLoader( "moog" ) ) ); #define setlfowavemodel( name ) \ name .addItem( tr( "Sine wave" ), static_cast( new PluginPixmapLoader( "sin" ) ) ); \ name .addItem( tr( "Triangle wave" ), static_cast( new PluginPixmapLoader( "tri" ) ) ); \ name .addItem( tr( "Saw wave" ), static_cast( new PluginPixmapLoader( "saw" ) ) ); \ name .addItem( tr( "Ramp wave" ), static_cast( new PluginPixmapLoader( "ramp" ) ) ); \ name .addItem( tr( "Square wave" ), static_cast( new PluginPixmapLoader( "sqr" ) ) ); \ name .addItem( tr( "Moog saw wave" ), static_cast( new PluginPixmapLoader( "moog" ) ) ); \ name .addItem( tr( "Soft square wave" ), static_cast( new PluginPixmapLoader( "sqrsoft" ) ) ); \ name .addItem( tr( "Abs. sine wave" ), static_cast( new PluginPixmapLoader( "sinabs" ) ) ); \ name .addItem( tr( "Exponential wave" ), static_cast( new PluginPixmapLoader( "exp" ) ) ); \ name .addItem( tr( "Random" ), static_cast( new PluginPixmapLoader( "rand" ) ) ); \ name .addItem( tr( "Random smooth" ), static_cast( new PluginPixmapLoader( "rand" ) ) ); public: MonstroInstrument( InstrumentTrack * _instrument_track ); virtual ~MonstroInstrument(); virtual void playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ); virtual void deleteNotePluginData( NotePlayHandle * _n ); virtual void saveSettings( QDomDocument & _doc, QDomElement & _this ); virtual void loadSettings( const QDomElement & _this ); virtual QString nodeName() const; virtual f_cnt_t desiredReleaseFrames() const; virtual PluginView * instantiateView( QWidget * _parent ); public slots: void updateVolume1(); void updateVolume2(); void updateVolume3(); void updateFreq1(); void updateFreq2(); void updateFreq3(); void updatePO1(); void updatePO2(); void updatePO3(); void updateEnvelope1(); void updateEnvelope2(); void updateLFOAtts(); void updateSamplerate(); void updateSlope1(); void updateSlope2(); protected: float m_osc1l_vol; float m_osc1r_vol; float m_osc2l_vol; float m_osc2r_vol; float m_osc3l_vol; float m_osc3r_vol; float m_osc1l_freq; float m_osc1r_freq; float m_osc2l_freq; float m_osc2r_freq; float m_osc3_freq; float m_osc1l_po; float m_osc1r_po; float m_osc2l_po; float m_osc2r_po; float m_osc3l_po; float m_osc3r_po; float m_env1_pre; float m_env1_att; float m_env1_hold; float m_env1_dec; float m_env1_rel; float m_env2_pre; float m_env2_att; float m_env2_hold; float m_env2_dec; float m_env2_rel; f_cnt_t m_env1_len; f_cnt_t m_env2_len; f_cnt_t m_env1_relF; f_cnt_t m_env2_relF; float m_slope [2]; f_cnt_t m_lfo1_att; f_cnt_t m_lfo2_att; sample_rate_t m_samplerate; fpp_t m_fpp; float m_integrator; float m_fmCorrection; int m_counterMax; private: inline float leftCh( float _vol, float _pan ) { return ( _pan <= 0 ? 1.0 : 1.0 - ( _pan / 100.0 ) ) * _vol / 100.0; } inline float rightCh( float _vol, float _pan ) { return ( _pan >= 0 ? 1.0 : 1.0 + ( _pan / 100.0 ) ) * _vol / 100.0; } ////////////////////////////////////// // models of // // operator view knobs // // // ////////////////////////////////////// FloatModel m_osc1Vol; FloatModel m_osc1Pan; FloatModel m_osc1Crs; FloatModel m_osc1Ftl; FloatModel m_osc1Ftr; FloatModel m_osc1Spo; FloatModel m_osc1Pw; BoolModel m_osc1SSR; BoolModel m_osc1SSF; FloatModel m_osc2Vol; FloatModel m_osc2Pan; FloatModel m_osc2Crs; FloatModel m_osc2Ftl; FloatModel m_osc2Ftr; FloatModel m_osc2Spo; ComboBoxModel m_osc2Wave; BoolModel m_osc2SyncH; BoolModel m_osc2SyncR; FloatModel m_osc3Vol; FloatModel m_osc3Pan; FloatModel m_osc3Crs; FloatModel m_osc3Spo; FloatModel m_osc3Sub; ComboBoxModel m_osc3Wave1; ComboBoxModel m_osc3Wave2; BoolModel m_osc3SyncH; BoolModel m_osc3SyncR; ComboBoxModel m_lfo1Wave; TempoSyncKnobModel m_lfo1Att; TempoSyncKnobModel m_lfo1Rate; FloatModel m_lfo1Phs; ComboBoxModel m_lfo2Wave; TempoSyncKnobModel m_lfo2Att; TempoSyncKnobModel m_lfo2Rate; FloatModel m_lfo2Phs; TempoSyncKnobModel m_env1Pre; TempoSyncKnobModel m_env1Att; TempoSyncKnobModel m_env1Hold; TempoSyncKnobModel m_env1Dec; FloatModel m_env1Sus; TempoSyncKnobModel m_env1Rel; FloatModel m_env1Slope; TempoSyncKnobModel m_env2Pre; TempoSyncKnobModel m_env2Att; TempoSyncKnobModel m_env2Hold; TempoSyncKnobModel m_env2Dec; FloatModel m_env2Sus; TempoSyncKnobModel m_env2Rel; FloatModel m_env2Slope; IntModel m_o23Mod; IntModel m_selectedView; ////////////////////////////////////// // models of // // modulation matrix view knobs // // // ////////////////////////////////////// FloatModel m_vol1env1; FloatModel m_vol1env2; FloatModel m_vol1lfo1; FloatModel m_vol1lfo2; FloatModel m_vol2env1; FloatModel m_vol2env2; FloatModel m_vol2lfo1; FloatModel m_vol2lfo2; FloatModel m_vol3env1; FloatModel m_vol3env2; FloatModel m_vol3lfo1; FloatModel m_vol3lfo2; FloatModel m_phs1env1; FloatModel m_phs1env2; FloatModel m_phs1lfo1; FloatModel m_phs1lfo2; FloatModel m_phs2env1; FloatModel m_phs2env2; FloatModel m_phs2lfo1; FloatModel m_phs2lfo2; FloatModel m_phs3env1; FloatModel m_phs3env2; FloatModel m_phs3lfo1; FloatModel m_phs3lfo2; FloatModel m_pit1env1; FloatModel m_pit1env2; FloatModel m_pit1lfo1; FloatModel m_pit1lfo2; FloatModel m_pit2env1; FloatModel m_pit2env2; FloatModel m_pit2lfo1; FloatModel m_pit2lfo2; FloatModel m_pit3env1; FloatModel m_pit3env2; FloatModel m_pit3lfo1; FloatModel m_pit3lfo2; FloatModel m_pw1env1; FloatModel m_pw1env2; FloatModel m_pw1lfo1; FloatModel m_pw1lfo2; FloatModel m_sub3env1; FloatModel m_sub3env2; FloatModel m_sub3lfo1; FloatModel m_sub3lfo2; friend class MonstroSynth; friend class MonstroView; }; class MonstroView : public InstrumentView { Q_OBJECT public: MonstroView( Instrument * _instrument, QWidget * _parent ); virtual ~MonstroView(); protected slots: void updateLayout(); private: virtual void modelChanged(); void setWidgetBackground( QWidget * _widget, const QString & _pic ); QWidget * setupOperatorsView( QWidget * _parent ); QWidget * setupMatrixView( QWidget * _parent ); ////////////////////////////////////// // // // operator view knobs // // // ////////////////////////////////////// Knob * m_osc1VolKnob; Knob * m_osc1PanKnob; Knob * m_osc1CrsKnob; Knob * m_osc1FtlKnob; Knob * m_osc1FtrKnob; Knob * m_osc1SpoKnob; Knob * m_osc1PwKnob; PixmapButton * m_osc1SSRButton; PixmapButton * m_osc1SSFButton; Knob * m_osc2VolKnob; Knob * m_osc2PanKnob; Knob * m_osc2CrsKnob; Knob * m_osc2FtlKnob; Knob * m_osc2FtrKnob; Knob * m_osc2SpoKnob; ComboBox * m_osc2WaveBox; PixmapButton * m_osc2SyncHButton; PixmapButton * m_osc2SyncRButton; Knob * m_osc3VolKnob; Knob * m_osc3PanKnob; Knob * m_osc3CrsKnob; Knob * m_osc3SpoKnob; Knob * m_osc3SubKnob; ComboBox * m_osc3Wave1Box; ComboBox * m_osc3Wave2Box; PixmapButton * m_osc3SyncHButton; PixmapButton * m_osc3SyncRButton; ComboBox * m_lfo1WaveBox; TempoSyncKnob * m_lfo1AttKnob; TempoSyncKnob * m_lfo1RateKnob; Knob * m_lfo1PhsKnob; ComboBox * m_lfo2WaveBox; TempoSyncKnob * m_lfo2AttKnob; TempoSyncKnob * m_lfo2RateKnob; Knob * m_lfo2PhsKnob; TempoSyncKnob * m_env1PreKnob; TempoSyncKnob * m_env1AttKnob; TempoSyncKnob * m_env1HoldKnob; TempoSyncKnob * m_env1DecKnob; Knob * m_env1SusKnob; TempoSyncKnob * m_env1RelKnob; Knob * m_env1SlopeKnob; TempoSyncKnob * m_env2PreKnob; TempoSyncKnob * m_env2AttKnob; TempoSyncKnob * m_env2HoldKnob; TempoSyncKnob * m_env2DecKnob; Knob * m_env2SusKnob; TempoSyncKnob * m_env2RelKnob; Knob * m_env2SlopeKnob; automatableButtonGroup * m_o23ModGroup; automatableButtonGroup * m_selectedViewGroup; QWidget * m_operatorsView; QWidget * m_matrixView; ///////////////////////////////// // // // matrix view knobs // // // ///////////////////////////////// Knob * m_vol1env1Knob; Knob * m_vol1env2Knob; Knob * m_vol1lfo1Knob; Knob * m_vol1lfo2Knob; Knob * m_vol2env1Knob; Knob * m_vol2env2Knob; Knob * m_vol2lfo1Knob; Knob * m_vol2lfo2Knob; Knob * m_vol3env1Knob; Knob * m_vol3env2Knob; Knob * m_vol3lfo1Knob; Knob * m_vol3lfo2Knob; Knob * m_phs1env1Knob; Knob * m_phs1env2Knob; Knob * m_phs1lfo1Knob; Knob * m_phs1lfo2Knob; Knob * m_phs2env1Knob; Knob * m_phs2env2Knob; Knob * m_phs2lfo1Knob; Knob * m_phs2lfo2Knob; Knob * m_phs3env1Knob; Knob * m_phs3env2Knob; Knob * m_phs3lfo1Knob; Knob * m_phs3lfo2Knob; Knob * m_pit1env1Knob; Knob * m_pit1env2Knob; Knob * m_pit1lfo1Knob; Knob * m_pit1lfo2Knob; Knob * m_pit2env1Knob; Knob * m_pit2env2Knob; Knob * m_pit2lfo1Knob; Knob * m_pit2lfo2Knob; Knob * m_pit3env1Knob; Knob * m_pit3env2Knob; Knob * m_pit3lfo1Knob; Knob * m_pit3lfo2Knob; Knob * m_pw1env1Knob; Knob * m_pw1env2Knob; Knob * m_pw1lfo1Knob; Knob * m_pw1lfo2Knob; Knob * m_sub3env1Knob; Knob * m_sub3env2Knob; Knob * m_sub3lfo1Knob; Knob * m_sub3lfo2Knob; }; #endif