diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 63f007fe1..b68ef0658 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -238,7 +238,7 @@ TrackContainerView QFrame{ /* autoscroll, loop, stop behaviour toggle buttons */ -nStateButton { +nStateButton { max-height: 26px; max-width: 26px; min-height: 26px; @@ -366,7 +366,7 @@ toolButton#playButton { toolButton#stopButton { border-top-right-radius: 5px 15px; - border-bottom-right-radius: 5px 15px; + border-bottom-right-radius: 5px 15px; } /* record and record-accompany can be styled with #recordButton and #recordAccompanyButton respectively */ @@ -608,6 +608,42 @@ sidInstrumentView knob { qproperty-lineWidth: 2; } +WatsynView knob { + qproperty-innerRadius: 1; + qproperty-outerRadius: 7; + qproperty-centerPointX: 9.5; + qproperty-centerPointY: 9.5; + qproperty-lineWidth: 2; +} + +WatsynView knob#aKnob { + color: #43b2ff; + qproperty-outerColor: #43b2ff; +} + +WatsynView knob#bKnob { + color: #fc5431; + qproperty-outerColor: #fc5431; +} + +WatsynView knob#mixKnob { + color: #43ff82; + qproperty-outerColor: #43ff82; + qproperty-outerRadius: 13; + qproperty-centerPointX: 15.5; + qproperty-centerPointY: 15.5; +} + +WatsynView knob#mixenvKnob { + color: #43ff82; + qproperty-outerColor: #43ff82; +} + +WatsynView knob#xtalkKnob { + color: #fb50fb; + qproperty-outerColor: #fb50fb; +} + /* palette information - each colour definition must be on a single line, and the line must begin with "palette:", with no leading whitespace * colour codes MUST be of the form #RRGGBB */ diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 59aadfbe3..b1247ce18 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -28,6 +28,7 @@ ADD_SUBDIRECTORY(triple_oscillator) ADD_SUBDIRECTORY(vestige) ADD_SUBDIRECTORY(vst_base) ADD_SUBDIRECTORY(VstEffect) +ADD_SUBDIRECTORY(watsyn) ADD_SUBDIRECTORY(waveshaper) ADD_SUBDIRECTORY(vibed) ADD_SUBDIRECTORY(zynaddsubfx) diff --git a/plugins/watsyn/CMakeLists.txt b/plugins/watsyn/CMakeLists.txt new file mode 100644 index 000000000..c5344f7e7 --- /dev/null +++ b/plugins/watsyn/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(watsyn Watsyn.cpp Watsyn.h MOCFILES Watsyn.h EMBEDDED_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.png) diff --git a/plugins/watsyn/Watsyn.cpp b/plugins/watsyn/Watsyn.cpp new file mode 100644 index 000000000..747141304 --- /dev/null +++ b/plugins/watsyn/Watsyn.cpp @@ -0,0 +1,1238 @@ +/* + * Watsyn.cpp - a 4-oscillator modulating wavetable synth + * + * Copyright (c) 2014 Vesa Kivimäki + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 + +#include "Watsyn.h" +#include "engine.h" +#include "InstrumentTrack.h" +#include "templates.h" +#include "tooltip.h" +#include "song.h" +#include "lmms_math.h" + +#include "embed.cpp" + +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT watsyn_plugin_descriptor = +{ + STRINGIFY( PLUGIN_NAME ), + "Watsyn", + QT_TRANSLATE_NOOP( "pluginBrowser", + "4-oscillator modulatable wavetable synth" ), + "Vesa Kivimäki ", + 0x0100, + Plugin::Instrument, + new PluginPixmapLoader( "logo" ), + NULL, + NULL +} ; + +} + + + + +WatsynObject::WatsynObject( float * _A1wave, float * _A2wave, + float * _B1wave, float * _B2wave, + int _amod, int _bmod, const sample_rate_t _samplerate, NotePlayHandle * _nph, fpp_t _frames, + WatsynInstrument * _w ) : + m_amod( _amod ), + m_bmod( _bmod ), + m_samplerate( _samplerate ), + m_nph( _nph ), + m_fpp( _frames ), + m_parent( _w ) +{ + m_abuf = new sampleFrame[_frames]; + m_bbuf = new sampleFrame[_frames]; + + m_lphase[A1_OSC] = 0.0f; + m_lphase[A2_OSC] = 0.0f; + m_lphase[B1_OSC] = 0.0f; + m_lphase[B2_OSC] = 0.0f; + + m_rphase[A1_OSC] = 0.0f; + m_rphase[A2_OSC] = 0.0f; + m_rphase[B1_OSC] = 0.0f; + m_rphase[B2_OSC] = 0.0f; + + // copy wavegraphs to the synth object to prevent race conditions + + memcpy( &m_A1wave, _A1wave, sizeof( m_A1wave ) ); + memcpy( &m_A2wave, _A2wave, sizeof( m_A2wave ) ); + memcpy( &m_B1wave, _B1wave, sizeof( m_B1wave ) ); + memcpy( &m_B2wave, _B2wave, sizeof( m_B2wave ) ); +} + + + +WatsynObject::~WatsynObject() +{ + delete[] m_abuf; + delete[] m_bbuf; +} + + +void WatsynObject::renderOutput( fpp_t _frames ) +{ + if( m_abuf == NULL ) + m_abuf = new sampleFrame[m_fpp]; + if( m_bbuf == NULL ) + m_bbuf = new sampleFrame[m_fpp]; + + for( fpp_t frame = 0; frame < _frames; frame++ ) + { + // put phases of 1-series oscs into variables because phase modulation might happen + float A1_lphase = m_lphase[A1_OSC]; + float A1_rphase = m_rphase[A1_OSC]; + float B1_lphase = m_lphase[B1_OSC]; + float B1_rphase = m_rphase[B1_OSC]; + + ///////////// A-series ///////////////// + + // A2 + sample_t A2_L = interpolate( m_A2wave[ static_cast( m_lphase[A2_OSC] ) % WAVELEN ], + m_A2wave[ static_cast( m_lphase[A2_OSC] + 1 ) % WAVELEN ], + fraction( m_lphase[A2_OSC] ) ) * m_parent->m_lvol[A2_OSC]; + sample_t A2_R = interpolate( m_A2wave[ static_cast( m_rphase[A2_OSC] ) % WAVELEN ], + m_A2wave[ static_cast( m_rphase[A2_OSC] + 1 ) % WAVELEN ], + fraction( m_rphase[A2_OSC] ) ) * m_parent->m_rvol[A2_OSC]; + // if phase mod, add to phases + if( m_amod == MOD_PM ) + { + A1_lphase = fmodf( A1_lphase + A2_L * PMOD_AMT, WAVELEN ); + while( A1_lphase < 0 ) A1_lphase += WAVELEN; + A1_rphase = fmodf( A1_rphase + A2_R * PMOD_AMT, WAVELEN ); + while( A1_rphase < 0 ) A1_rphase += WAVELEN; + } + // A1 + sample_t A1_L = interpolate( m_A1wave[ static_cast( A1_lphase ) % WAVELEN ], + m_A1wave[ static_cast( A1_lphase + 1 ) % WAVELEN ], + fraction( A1_lphase ) ) * m_parent->m_lvol[A1_OSC]; + sample_t A1_R = interpolate( m_A1wave[ static_cast( A1_rphase ) % WAVELEN ], + m_A1wave[ static_cast( A1_rphase + 1 ) % WAVELEN ], + fraction( A1_rphase ) ) * m_parent->m_rvol[A1_OSC]; + + ///////////// B-series ///////////////// + + // B2 + sample_t B2_L = interpolate( m_B2wave[ static_cast( m_lphase[B2_OSC] ) % WAVELEN ], + m_B2wave[ static_cast( m_lphase[B2_OSC] + 1 ) % WAVELEN ], + fraction( m_lphase[B2_OSC] ) ) * m_parent->m_lvol[B2_OSC]; + sample_t B2_R = interpolate( m_B2wave[ static_cast( m_rphase[B2_OSC] ) % WAVELEN ], + m_B2wave[ static_cast( m_rphase[B2_OSC] + 1 ) % WAVELEN ], + fraction( m_rphase[B2_OSC] ) ) * m_parent->m_rvol[B2_OSC]; + + // if crosstalk active, add a1 + const float xt = m_parent->m_xtalk.value(); + if( xt > 0.0 ) + { + B2_L += ( A1_L * xt ) / 100.0f; + B2_R += ( A1_R * xt ) / 100.0f; + } + + // if phase mod, add to phases + if( m_bmod == MOD_PM ) + { + B1_lphase = fmodf( B1_lphase + B2_L * PMOD_AMT, WAVELEN ); + while( B1_lphase < 0 ) B1_lphase += WAVELEN; + B1_rphase = fmodf( B1_rphase + B2_R * PMOD_AMT, WAVELEN ); + while( B1_rphase < 0 ) B1_rphase += WAVELEN; + } + // B1 + sample_t B1_L = interpolate( m_B1wave[ static_cast( B1_lphase ) % WAVELEN ], + m_B1wave[ static_cast( B1_lphase + 1 ) % WAVELEN ], + fraction( B1_lphase ) ) * m_parent->m_lvol[B1_OSC]; + sample_t B1_R = interpolate( m_B1wave[ static_cast( B1_rphase ) % WAVELEN ], + m_B1wave[ static_cast( B1_rphase + 1 ) % WAVELEN ], + fraction( B1_rphase ) ) * m_parent->m_rvol[B1_OSC]; + + + // A-series modulation) + switch( m_amod ) + { + case MOD_MIX: + A1_L = ( A1_L + A2_L ) / 2.0; + A1_R = ( A1_R + A2_R ) / 2.0; + break; + case MOD_AM: + A1_L *= qMax( 0.0f, A2_L + 1.0f ); + A1_R *= qMax( 0.0f, A2_R + 1.0f ); + break; + case MOD_RM: + A1_L *= A2_L; + A1_R *= A2_R; + break; + } + m_abuf[frame][0] = A1_L; + m_abuf[frame][1] = A1_R; + + // B-series modulation (other than phase mod) + switch( m_bmod ) + { + case MOD_MIX: + B1_L = ( B1_L + B2_L ) / 2.0; + B1_R = ( B1_R + B2_R ) / 2.0; + break; + case MOD_AM: + B1_L *= qMax( 0.0f, B2_L + 1.0f ); + B1_R *= qMax( 0.0f, B2_R + 1.0f ); + break; + case MOD_RM: + B1_L *= B2_L; + B1_R *= B2_R; + break; + } + m_bbuf[frame][0] = B1_L; + m_bbuf[frame][1] = B1_R; + + // update phases + for( int i = 0; i < NUM_OSCS; i++ ) + { + m_lphase[i] += ( static_cast( WAVELEN ) / ( m_samplerate / ( m_nph->frequency() * m_parent->m_lfreq[i] ) ) ); + m_lphase[i] = fmodf( m_lphase[i], WAVELEN ); + m_rphase[i] += ( static_cast( WAVELEN ) / ( m_samplerate / ( m_nph->frequency() * m_parent->m_rfreq[i] ) ) ); + m_rphase[i] = fmodf( m_rphase[i], WAVELEN ); + } + } + +} + + + +WatsynInstrument::WatsynInstrument( InstrumentTrack * _instrument_track ) : + Instrument( _instrument_track, &watsyn_plugin_descriptor ), + + a1_vol( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Volume A1" ) ), + a2_vol( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Volume A2" ) ), + b1_vol( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Volume B1" ) ), + b2_vol( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Volume B2" ) ), + + a1_pan( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "Panning A1" ) ), + a2_pan( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "Panning A2" ) ), + b1_pan( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "Panning B1" ) ), + b2_pan( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "Panning B2" ) ), + + a1_mult( 8.0f, 1.0, 24.0, 1.0, this, tr( "Freq. multiplier A1" ) ), + a2_mult( 8.0f, 1.0, 24.0, 1.0, this, tr( "Freq. multiplier A2" ) ), + b1_mult( 8.0f, 1.0, 24.0, 1.0, this, tr( "Freq. multiplier B1" ) ), + b2_mult( 8.0f, 1.0, 24.0, 1.0, this, tr( "Freq. multiplier B2" ) ), + + a1_ltune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Left detune A1" ) ), + a2_ltune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Left detune A2" ) ), + b1_ltune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Left detune B1" ) ), + b2_ltune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Left detune B2" ) ), + + a1_rtune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Right detune A1" ) ), + a2_rtune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Right detune A2" ) ), + b1_rtune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Right detune B1" ) ), + b2_rtune( 0.0f, -600.0f, 600.0f, 1.0f, this, tr( "Right detune B2" ) ), + + a1_graph( -1.0f, 1.0f, GRAPHLEN, this ), + a2_graph( -1.0f, 1.0f, GRAPHLEN, this ), + b1_graph( -1.0f, 1.0f, GRAPHLEN, this ), + b2_graph( -1.0f, 1.0f, GRAPHLEN, this ), + + m_abmix( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "A-B Mix" ) ), + m_envAmt( 0.0f, -200.0f, 200.0f, 1.0f, this, tr( "A-B Mix envelope amount" ) ), + + m_envAtt( 0.0f, 0.0f, 2000.0f, 1.0f, 2000.0f, this, tr( "A-B Mix envelope attack" ) ), + m_envHold( 0.0f, 0.0f, 2000.0f, 1.0f, 2000.0f, this, tr( "A-B Mix envelope hold" ) ), + m_envDec( 0.0f, 0.0f, 2000.0f, 1.0f, 2000.0f, this, tr( "A-B Mix envelope decay" ) ), + + m_xtalk( 0.0f, 0.0f, 100.0f, 0.1f, this, tr( "A1-B2 Crosstalk" ) ), + + m_amod( 0, 0, 3, this, tr( "A2-A1 modulation" ) ), + m_bmod( 0, 0, 3, this, tr( "B2-B1 modulation" ) ), + + m_selectedGraph( 0, 0, 3, this, tr( "Selected graph" ) ) +{ + connect( &a1_vol, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) ); + connect( &a2_vol, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) ); + connect( &b1_vol, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) ); + connect( &b2_vol, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) ); + + connect( &a1_pan, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) ); + connect( &a2_pan, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) ); + connect( &b1_pan, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) ); + connect( &b2_pan, SIGNAL( dataChanged() ), this, SLOT( updateVolumes() ) ); + + connect( &a1_mult, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + connect( &a2_mult, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + connect( &b1_mult, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + connect( &b2_mult, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + + connect( &a1_ltune, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + connect( &a2_ltune, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + connect( &b1_ltune, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + connect( &b2_ltune, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + + connect( &a1_rtune, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + connect( &a2_rtune, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + connect( &b1_rtune, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + connect( &b2_rtune, SIGNAL( dataChanged() ), this, SLOT( updateFreq() ) ); + + connect( &a1_graph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( updateWaves() ) ); + connect( &a2_graph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( updateWaves() ) ); + connect( &b1_graph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( updateWaves() ) ); + connect( &b2_graph, SIGNAL( samplesChanged( int, int ) ), this, SLOT( updateWaves() ) ); + + a1_graph.setWaveToSine(); + a2_graph.setWaveToSine(); + b1_graph.setWaveToSine(); + b2_graph.setWaveToSine(); + + updateVolumes(); + updateFreq(); + updateWaves(); +} + + +WatsynInstrument::~WatsynInstrument() +{ +} + + +void WatsynInstrument::playNote( NotePlayHandle * _n, + sampleFrame * _working_buffer ) +{ + if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == NULL ) + { + WatsynObject * w = new WatsynObject( + &A1_wave[0], + &A2_wave[0], + &B1_wave[0], + &B2_wave[0], + m_amod.value(), m_bmod.value(), + engine::mixer()->processingSampleRate(), _n, + engine::mixer()->framesPerPeriod(), this ); + + _n->m_pluginData = w; + } + + const fpp_t frames = _n->framesLeftForCurrentPeriod(); + + WatsynObject * w = static_cast( _n->m_pluginData ); + + sampleFrame * abuf = w->abuf(); + sampleFrame * bbuf = w->bbuf(); + + w-> renderOutput( frames ); + + // envelope parameters + const float envAmt = m_envAmt.value(); + const float envAtt = ( m_envAtt.value() * w->samplerate() ) / 1000.0f; + const float envHold = ( m_envHold.value() * w->samplerate() ) / 1000.0f; + const float envDec = ( m_envDec.value() * w->samplerate() ) / 1000.0f; + const float envLen = envAtt + envDec + envHold; + const float tfp_ = static_cast( _n->totalFramesPlayed() ); + + // if sample-exact is enabled, use sample-exact calculations... + if( engine::mixer()->currentQualitySettings().sampleExactControllers ) + { + for( fpp_t f=0; f < frames; f++ ) + { + const float tfp = tfp_ + f; + // handle mixing envelope + float mixvalue = m_abmix.value( f ); + if( envAmt != 0.0f && tfp < envLen ) + { + if( tfp < envAtt ) + { + mixvalue = qBound( -100.0f, mixvalue + ( tfp / envAtt * envAmt ), 100.0f ); + } + else if ( tfp >= envAtt && tfp < envAtt + envHold ) + { + mixvalue = qBound( -100.0f, mixvalue + envAmt, 100.0f ); + } + else + { + mixvalue = qBound( -100.0f, mixvalue + envAmt - ( ( tfp - ( envAtt + envHold ) ) / envDec * envAmt ), 100.0f ); + } + } + // get knob values in sample-exact way + const float bmix = ( ( mixvalue + 100.0 ) / 200.0 ); + const float amix = 1.0 - bmix; + + // mix a/b streams according to mixing knob + _working_buffer[f][0] = ( abuf[f][0] * amix ) + + ( bbuf[f][0] * bmix ); + _working_buffer[f][1] = ( abuf[f][1] * amix ) + + ( bbuf[f][1] * bmix ); + } + } + + // if sample-exact is not enabled, use simpler calculations: + // if mix envelope is active, and we haven't gone past the envelope end, use envelope-aware calculation... + else if( envAmt != 0.0f && tfp_ < envLen ) + { + const float mixvalue_ = m_abmix.value(); + for( fpp_t f=0; f < frames; f++ ) + { + float mixvalue = mixvalue_; + const float tfp = tfp_ + f; + // handle mixing envelope + if( tfp < envAtt ) + { + mixvalue = qBound( -100.0f, mixvalue + ( tfp / envAtt * envAmt ), 100.0f ); + } + else if ( tfp >= envAtt && tfp < envAtt + envHold ) + { + mixvalue = qBound( -100.0f, mixvalue + envAmt, 100.0f ); + } + else + { + mixvalue = qBound( -100.0f, mixvalue + envAmt - ( ( tfp - ( envAtt + envHold ) ) / envDec * envAmt ), 100.0f ); + } + + // get knob values + const float bmix = ( ( mixvalue + 100.0 ) / 200.0 ); + const float amix = 1.0 - bmix; + + // mix a/b streams according to mixing knob + _working_buffer[f][0] = ( abuf[f][0] * amix ) + + ( bbuf[f][0] * bmix ); + _working_buffer[f][1] = ( abuf[f][1] * amix ) + + ( bbuf[f][1] * bmix ); + } + } + + // ... mix envelope is inactive or we've past the end of envelope, so use a faster calculation to save cpu + else + { + // get knob values + const float bmix = ( ( m_abmix.value() + 100.0 ) / 200.0 ); + const float amix = 1.0 - bmix; + for( fpp_t f=0; f < frames; f++ ) + { + // mix a/b streams according to mixing knob + _working_buffer[f][0] = ( abuf[f][0] * amix ) + + ( bbuf[f][0] * bmix ); + _working_buffer[f][1] = ( abuf[f][1] * amix ) + + ( bbuf[f][1] * bmix ); + } + } + + applyRelease( _working_buffer, _n ); + + instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); +} + + +void WatsynInstrument::deleteNotePluginData( NotePlayHandle * _n ) +{ + delete static_cast( _n->m_pluginData ); +} + + +void WatsynInstrument::saveSettings( QDomDocument & _doc, + QDomElement & _this ) +{ + a1_vol.saveSettings( _doc, _this, "a1_vol" ); + a2_vol.saveSettings( _doc, _this, "a2_vol" ); + b1_vol.saveSettings( _doc, _this, "b1_vol" ); + b2_vol.saveSettings( _doc, _this, "b2_vol" ); + + a1_pan.saveSettings( _doc, _this, "a1_pan" ); + a2_pan.saveSettings( _doc, _this, "a2_pan" ); + b1_pan.saveSettings( _doc, _this, "b1_pan" ); + b2_pan.saveSettings( _doc, _this, "b2_pan" ); + + a1_mult.saveSettings( _doc, _this, "a1_mult" ); + a2_mult.saveSettings( _doc, _this, "a2_mult" ); + b1_mult.saveSettings( _doc, _this, "b1_mult" ); + b2_mult.saveSettings( _doc, _this, "b2_mult" ); + + a1_ltune.saveSettings( _doc, _this, "a1_ltune" ); + a2_ltune.saveSettings( _doc, _this, "a2_ltune" ); + b1_ltune.saveSettings( _doc, _this, "b1_ltune" ); + b2_ltune.saveSettings( _doc, _this, "b2_ltune" ); + + a1_rtune.saveSettings( _doc, _this, "a1_rtune" ); + a2_rtune.saveSettings( _doc, _this, "a2_rtune" ); + b1_rtune.saveSettings( _doc, _this, "b1_rtune" ); + b2_rtune.saveSettings( _doc, _this, "b2_rtune" ); + + // save graphs + QString sampleString; + + base64::encode( (const char *)a1_graph.samples(), a1_graph.length() * sizeof(float), sampleString ); + _this.setAttribute( "a1_wave", sampleString ); + base64::encode( (const char *)a2_graph.samples(), a2_graph.length() * sizeof(float), sampleString ); + _this.setAttribute( "a2_wave", sampleString ); + base64::encode( (const char *)b1_graph.samples(), b1_graph.length() * sizeof(float), sampleString ); + _this.setAttribute( "b1_wave", sampleString ); + base64::encode( (const char *)b2_graph.samples(), b2_graph.length() * sizeof(float), sampleString ); + _this.setAttribute( "b2_wave", sampleString ); + + m_abmix.saveSettings( _doc, _this, "abmix" ); + m_envAmt.saveSettings( _doc, _this, "envAmt" ); + m_envAtt.saveSettings( _doc, _this, "envAtt" ); + m_envHold.saveSettings( _doc, _this, "envHold" ); + m_envDec.saveSettings( _doc, _this, "envDec" ); + + m_xtalk.saveSettings( _doc, _this, "xtalk" ); + + m_amod.saveSettings( _doc, _this, "amod" ); + m_bmod.saveSettings( _doc, _this, "bmod" ); +/* m_selectedGraph.saveSettings( _doc, _this, "selgraph" );*/ +} + + +void WatsynInstrument::loadSettings( const QDomElement & _this ) +{ + a1_vol.loadSettings( _this, "a1_vol" ); + a2_vol.loadSettings( _this, "a2_vol" ); + b1_vol.loadSettings( _this, "b1_vol" ); + b2_vol.loadSettings( _this, "b2_vol" ); + + a1_pan.loadSettings( _this, "a1_pan" ); + a2_pan.loadSettings( _this, "a2_pan" ); + b1_pan.loadSettings( _this, "b1_pan" ); + b2_pan.loadSettings( _this, "b2_pan" ); + + a1_mult.loadSettings( _this, "a1_mult" ); + a2_mult.loadSettings( _this, "a2_mult" ); + b1_mult.loadSettings( _this, "b1_mult" ); + b2_mult.loadSettings( _this, "b2_mult" ); + + a1_ltune.loadSettings( _this, "a1_ltune" ); + a2_ltune.loadSettings( _this, "a2_ltune" ); + b1_ltune.loadSettings( _this, "b1_ltune" ); + b2_ltune.loadSettings( _this, "b2_ltune" ); + + a1_rtune.loadSettings( _this, "a1_rtune" ); + a2_rtune.loadSettings( _this, "a2_rtune" ); + b1_rtune.loadSettings( _this, "b1_rtune" ); + b2_rtune.loadSettings( _this, "b2_rtune" ); + + // load graphs + int size = 0; + char * dst = 0; + + base64::decode( _this.attribute( "a1_wave"), &dst, &size ); + a1_graph.setSamples( (float*) dst ); + base64::decode( _this.attribute( "a2_wave"), &dst, &size ); + a2_graph.setSamples( (float*) dst ); + base64::decode( _this.attribute( "b1_wave"), &dst, &size ); + b1_graph.setSamples( (float*) dst ); + base64::decode( _this.attribute( "b2_wave"), &dst, &size ); + b2_graph.setSamples( (float*) dst ); + + delete[] dst; + + m_abmix.loadSettings( _this, "abmix" ); + + m_envAmt.loadSettings( _this, "envAmt" ); + m_envAtt.loadSettings( _this, "envAtt" ); + m_envHold.loadSettings( _this, "envHold" ); + m_envDec.loadSettings( _this, "envDec" ); + + m_xtalk.loadSettings( _this, "xtalk" ); + + m_amod.loadSettings( _this, "amod" ); + m_bmod.loadSettings( _this, "bmod" ); +/* m_selectedGraph.loadSettings( _this, "selgraph" );*/ +} + + +QString WatsynInstrument::nodeName() const +{ + return( watsyn_plugin_descriptor.name ); +} + + +PluginView * WatsynInstrument::instantiateView( QWidget * _parent ) +{ + return( new WatsynView( this, _parent ) ); +} + + +void WatsynInstrument::updateVolumes() +{ + m_lvol[A1_OSC] = leftCh( a1_vol.value(), a1_pan.value() ); + m_rvol[A1_OSC] = rightCh( a1_vol.value(), a1_pan.value() ); + + m_lvol[A2_OSC] = leftCh( a2_vol.value(), a2_pan.value() ); + m_rvol[A2_OSC] = rightCh( a2_vol.value(), a2_pan.value() ); + + m_lvol[B1_OSC] = leftCh( b1_vol.value(), b1_pan.value() ); + m_rvol[B1_OSC] = rightCh( b1_vol.value(), b1_pan.value() ); + + m_lvol[B2_OSC] = leftCh( b2_vol.value(), b2_pan.value() ); + m_rvol[B2_OSC] = rightCh( b2_vol.value(), b2_pan.value() ); +} + +void WatsynInstrument::updateFreq() +{ + // calculate frequencies + m_lfreq[A1_OSC] = ( a1_mult.value() / 8 ) * powf( 2, a1_ltune.value() / 1200 ); + m_rfreq[A1_OSC] = ( a1_mult.value() / 8 ) * powf( 2, a1_rtune.value() / 1200 ); + + m_lfreq[A2_OSC] = ( a2_mult.value() / 8 ) * powf( 2, a2_ltune.value() / 1200 ); + m_rfreq[A2_OSC] = ( a2_mult.value() / 8 ) * powf( 2, a2_rtune.value() / 1200 ); + + m_lfreq[B1_OSC] = ( b1_mult.value() / 8 ) * powf( 2, b1_ltune.value() / 1200 ); + m_rfreq[B1_OSC] = ( b1_mult.value() / 8 ) * powf( 2, b1_rtune.value() / 1200 ); + + m_lfreq[B2_OSC] = ( b2_mult.value() / 8 ) * powf( 2, b2_ltune.value() / 1200 ); + m_rfreq[B2_OSC] = ( b2_mult.value() / 8 ) * powf( 2, b2_rtune.value() / 1200 ); +} + + +void WatsynInstrument::updateWaves() +{ + // do cip+oversampling on the wavetables to improve quality + cipcpy( &A1_wave[0], const_cast( a1_graph.samples() ) ); + cipcpy( &A2_wave[0], const_cast( a2_graph.samples() ) ); + cipcpy( &B1_wave[0], const_cast( b1_graph.samples() ) ); + cipcpy( &B2_wave[0], const_cast( b2_graph.samples() ) ); +} + + +WatsynView::WatsynView( Instrument * _instrument, + QWidget * _parent ) : + InstrumentView( _instrument, _parent ) +{ + setAutoFillBackground( true ); + QPalette pal; + + pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); + setPalette( pal ); + +// knobs... lots of em + + makeknob( a1_volKnob, 130, A1ROW, "Volume", "%", "aKnob" ) + makeknob( a2_volKnob, 130, A2ROW, "Volume", "%", "aKnob" ) + makeknob( b1_volKnob, 130, B1ROW, "Volume", "%", "bKnob" ) + makeknob( b2_volKnob, 130, B2ROW, "Volume", "%", "bKnob" ) + + makeknob( a1_panKnob, 154, A1ROW, "Panning", "", "aKnob" ) + makeknob( a2_panKnob, 154, A2ROW, "Panning", "", "aKnob" ) + makeknob( b1_panKnob, 154, B1ROW, "Panning", "", "bKnob" ) + makeknob( b2_panKnob, 154, B2ROW, "Panning", "", "bKnob" ) + + makeknob( a1_multKnob, 178, A1ROW, "Freq. multiplier", "/8", "aKnob" ) + makeknob( a2_multKnob, 178, A2ROW, "Freq. multiplier", "/8", "aKnob" ) + makeknob( b1_multKnob, 178, B1ROW, "Freq. multiplier", "/8", "bKnob" ) + makeknob( b2_multKnob, 178, B2ROW, "Freq. multiplier", "/8", "bKnob" ) + + makeknob( a1_ltuneKnob, 202, A1ROW, "Left detune", " cents", "aKnob" ) + makeknob( a2_ltuneKnob, 202, A2ROW, "Left detune", " cents", "aKnob" ) + makeknob( b1_ltuneKnob, 202, B1ROW, "Left detune", " cents", "bKnob" ) + makeknob( b2_ltuneKnob, 202, B2ROW, "Left detune", " cents", "bKnob" ) + + makeknob( a1_rtuneKnob, 226, A1ROW, "Right detune", " cents", "aKnob" ) + makeknob( a2_rtuneKnob, 226, A2ROW, "Right detune", " cents", "aKnob" ) + makeknob( b1_rtuneKnob, 226, B1ROW, "Right detune", " cents", "bKnob" ) + makeknob( b2_rtuneKnob, 226, B2ROW, "Right detune", " cents", "bKnob" ) + + makeknob( m_abmixKnob, 4, 3, "A-B Mix", "", "mixKnob" ) + + makeknob( m_envAmtKnob, 88, 3, "Mix envelope amount", "", "mixenvKnob" ) + + maketsknob( m_envAttKnob, 88, A1ROW, "Mix envelope attack", " ms", "mixenvKnob" ) + maketsknob( m_envHoldKnob, 88, A2ROW, "Mix envelope hold", " ms", "mixenvKnob" ) + maketsknob( m_envDecKnob, 88, B1ROW, "Mix envelope decay", " ms", "mixenvKnob" ) + + makeknob( m_xtalkKnob, 88, B2ROW, "Crosstalk", "", "xtalkKnob" ) + +// let's set volume knobs + a1_volKnob -> setVolumeKnob( true ); + a2_volKnob -> setVolumeKnob( true ); + b1_volKnob -> setVolumeKnob( true ); + b2_volKnob -> setVolumeKnob( true ); + + m_abmixKnob -> setFixedSize( 31, 31 ); + + +// button groups next. +// graph select buttons + pixmapButton * a1_selectButton = new pixmapButton( this, NULL ); + a1_selectButton -> move( 4, 121 ); + a1_selectButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "a1_active" ) ); + a1_selectButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "a1_inactive" ) ); + toolTip::add( a1_selectButton, tr( "Select oscillator A1") ); + + pixmapButton * a2_selectButton = new pixmapButton( this, NULL ); + a2_selectButton -> move( 44, 121 ); + a2_selectButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "a2_active" ) ); + a2_selectButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "a2_inactive" ) ); + toolTip::add( a2_selectButton, tr( "Select oscillator A2") ); + + pixmapButton * b1_selectButton = new pixmapButton( this, NULL ); + b1_selectButton -> move( 84, 121 ); + b1_selectButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "b1_active" ) ); + b1_selectButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "b1_inactive" ) ); + toolTip::add( b1_selectButton, tr( "Select oscillator B1") ); + + pixmapButton * b2_selectButton = new pixmapButton( this, NULL ); + b2_selectButton -> move( 124, 121 ); + b2_selectButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "b2_active" ) ); + b2_selectButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "b2_inactive" ) ); + toolTip::add( b2_selectButton, tr( "Select oscillator B2") ); + + m_selectedGraphGroup = new automatableButtonGroup( this ); + m_selectedGraphGroup -> addButton( a1_selectButton ); + m_selectedGraphGroup -> addButton( a2_selectButton ); + m_selectedGraphGroup -> addButton( b1_selectButton ); + m_selectedGraphGroup -> addButton( b2_selectButton ); + +// A-modulation button group + pixmapButton * amod_mixButton = new pixmapButton( this, NULL ); + amod_mixButton -> move( 4, 50 ); + amod_mixButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "amix_active" ) ); + amod_mixButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "amix_inactive" ) ); + toolTip::add( amod_mixButton, tr( "Mix output of A2 to A1" ) ); + + pixmapButton * amod_amButton = new pixmapButton( this, NULL ); + amod_amButton -> move( 4, 66 ); + amod_amButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "aam_active" ) ); + amod_amButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "aam_inactive" ) ); + toolTip::add( amod_amButton, tr( "Modulate amplitude of A1 with output of A2" ) ); + + pixmapButton * amod_rmButton = new pixmapButton( this, NULL ); + amod_rmButton -> move( 4, 82 ); + amod_rmButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "arm_active" ) ); + amod_rmButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "arm_inactive" ) ); + toolTip::add( amod_rmButton, tr( "Ring-modulate A1 and A2" ) ); + + pixmapButton * amod_pmButton = new pixmapButton( this, NULL ); + amod_pmButton -> move( 4, 98 ); + amod_pmButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "apm_active" ) ); + amod_pmButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "apm_inactive" ) ); + toolTip::add( amod_pmButton, tr( "Modulate phase of A1 with output of A2" ) ); + + m_aModGroup = new automatableButtonGroup( this ); + m_aModGroup -> addButton( amod_mixButton ); + m_aModGroup -> addButton( amod_amButton ); + m_aModGroup -> addButton( amod_rmButton ); + m_aModGroup -> addButton( amod_pmButton ); + +// B-modulation button group + pixmapButton * bmod_mixButton = new pixmapButton( this, NULL ); + bmod_mixButton -> move( 44, 50 ); + bmod_mixButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "bmix_active" ) ); + bmod_mixButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "bmix_inactive" ) ); + toolTip::add( bmod_mixButton, tr( "Mix output of B2 to B1" ) ); + + pixmapButton * bmod_amButton = new pixmapButton( this, NULL ); + bmod_amButton -> move( 44, 66 ); + bmod_amButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "bam_active" ) ); + bmod_amButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "bam_inactive" ) ); + toolTip::add( bmod_amButton, tr( "Modulate amplitude of B1 with output of B2" ) ); + + pixmapButton * bmod_rmButton = new pixmapButton( this, NULL ); + bmod_rmButton -> move( 44, 82 ); + bmod_rmButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "brm_active" ) ); + bmod_rmButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "brm_inactive" ) ); + toolTip::add( bmod_rmButton, tr( "Ring-modulate B1 and B2" ) ); + + pixmapButton * bmod_pmButton = new pixmapButton( this, NULL ); + bmod_pmButton -> move( 44, 98 ); + bmod_pmButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "bpm_active" ) ); + bmod_pmButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "bpm_inactive" ) ); + toolTip::add( bmod_pmButton, tr( "Modulate phase of B1 with output of B2" ) ); + + m_bModGroup = new automatableButtonGroup( this ); + m_bModGroup -> addButton( bmod_mixButton ); + m_bModGroup -> addButton( bmod_amButton ); + m_bModGroup -> addButton( bmod_rmButton ); + m_bModGroup -> addButton( bmod_pmButton ); + + +// graph widgets + pal = QPalette(); + pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap("wavegraph") ); +// a1 graph + a1_graph = new graph( this, graph::LinearStyle, 224, 105 ); + a1_graph->move( 4, 141 ); + a1_graph->setAutoFillBackground( true ); + a1_graph->setGraphColor( QColor( 0x43, 0xb2, 0xff ) ); + toolTip::add( a1_graph, tr ( "Draw your own waveform here by dragging your mouse on this graph." ) ); + a1_graph->setPalette( pal ); + +// a2 graph + a2_graph = new graph( this, graph::LinearStyle, 224, 105 ); + a2_graph->move( 4, 141 ); + a2_graph->setAutoFillBackground( true ); + a2_graph->setGraphColor( QColor( 0x43, 0xb2, 0xff ) ); + toolTip::add( a2_graph, tr ( "Draw your own waveform here by dragging your mouse on this graph." ) ); + a2_graph->setPalette( pal ); + +// b1 graph + b1_graph = new graph( this, graph::LinearStyle, 224, 105 ); + b1_graph->move( 4, 141 ); + b1_graph->setAutoFillBackground( true ); + b1_graph->setGraphColor( QColor( 0xfc, 0x54, 0x31 ) ); + toolTip::add( b1_graph, tr ( "Draw your own waveform here by dragging your mouse on this graph." ) ); + b1_graph->setPalette( pal ); + +// b2 graph + b2_graph = new graph( this, graph::LinearStyle, 224, 105 ); + b2_graph->move( 4, 141 ); + b2_graph->setAutoFillBackground( true ); + b2_graph->setGraphColor( QColor( 0xfc, 0x54, 0x31 ) ); + toolTip::add( b2_graph, tr ( "Draw your own waveform here by dragging your mouse on this graph." ) ); + b2_graph->setPalette( pal ); + + +// misc pushbuttons +// waveform modifications + + m_loadButton = new pixmapButton( this, tr( "Load waveform" ) ); + m_loadButton -> move ( 173, 121 ); + m_loadButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "load_active" ) ); + m_loadButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "load_inactive" ) ); + toolTip::add( m_loadButton, tr( "Click to load a waveform from a sample file" ) ); + + m_phaseLeftButton = new pixmapButton( this, tr( "Phase left" ) ); + m_phaseLeftButton -> move ( 193, 121 ); + m_phaseLeftButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "phl_active" ) ); + m_phaseLeftButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "phl_inactive" ) ); + toolTip::add( m_phaseLeftButton, tr( "Click to shift phase by -15 degrees" ) ); + + m_phaseRightButton = new pixmapButton( this, tr( "Phase right" ) ); + m_phaseRightButton -> move ( 210, 121 ); + m_phaseRightButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "phr_active" ) ); + m_phaseRightButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "phr_inactive" ) ); + toolTip::add( m_phaseRightButton, tr( "Click to shift phase by +15 degrees" ) ); + + m_normalizeButton = new pixmapButton( this, tr( "Normalize" ) ); + m_normalizeButton -> move ( 230, 121 ); + m_normalizeButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "norm_active" ) ); + m_normalizeButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "norm_inactive" ) ); + toolTip::add( m_normalizeButton, tr( "Click to normalize" ) ); + + + m_invertButton = new pixmapButton( this, tr( "Invert" ) ); + m_invertButton -> move ( 230, 138 ); + m_invertButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "inv_active" ) ); + m_invertButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "inv_inactive" ) ); + toolTip::add( m_invertButton, tr( "Click to invert" ) ); + + m_smoothButton = new pixmapButton( this, tr( "Smooth" ) ); + m_smoothButton -> move ( 230, 155 ); + m_smoothButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "smooth_active" ) ); + m_smoothButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "smooth_inactive" ) ); + toolTip::add( m_smoothButton, tr( "Click to smooth" ) ); + +// waveforms + + m_sinWaveButton = new pixmapButton( this, tr( "Sine wave" ) ); + m_sinWaveButton -> move ( 230, 176 ); + m_sinWaveButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "sin_active" ) ); + m_sinWaveButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "sin_inactive" ) ); + toolTip::add( m_sinWaveButton, tr( "Click for sine wave" ) ); + + m_triWaveButton = new pixmapButton( this, tr( "Triangle wave" ) ); + m_triWaveButton -> move ( 230, 194 ); + m_triWaveButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "tri_active" ) ); + m_triWaveButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "tri_inactive" ) ); + toolTip::add( m_triWaveButton, tr( "Click for triangle wave" ) ); + + m_sawWaveButton = new pixmapButton( this, tr( "Triangle wave" ) ); + m_sawWaveButton -> move ( 230, 212 ); + m_sawWaveButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "saw_active" ) ); + m_sawWaveButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "saw_inactive" ) ); + toolTip::add( m_sawWaveButton, tr( "Click for saw wave" ) ); + + m_sqrWaveButton = new pixmapButton( this, tr( "Square wave" ) ); + m_sqrWaveButton -> move ( 230, 230 ); + m_sqrWaveButton -> setActiveGraphic( PLUGIN_NAME::getIconPixmap( "sqr_active" ) ); + m_sqrWaveButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "sqr_inactive" ) ); + toolTip::add( m_sqrWaveButton, tr( "Click for square wave" ) ); + + + + connect( m_sinWaveButton, SIGNAL( clicked() ), this, SLOT( sinWaveClicked() ) ); + connect( m_triWaveButton, SIGNAL( clicked() ), this, SLOT( triWaveClicked() ) ); + connect( m_sawWaveButton, SIGNAL( clicked() ), this, SLOT( sawWaveClicked() ) ); + connect( m_sqrWaveButton, SIGNAL( clicked() ), this, SLOT( sqrWaveClicked() ) ); + connect( m_normalizeButton, SIGNAL( clicked() ), this, SLOT( normalizeClicked() ) ); + connect( m_invertButton, SIGNAL( clicked() ), this, SLOT( invertClicked() ) ); + connect( m_smoothButton, SIGNAL( clicked() ), this, SLOT( smoothClicked() ) ); + connect( m_phaseLeftButton, SIGNAL( clicked() ), this, SLOT( phaseLeftClicked() ) ); + connect( m_phaseRightButton, SIGNAL( clicked() ), this, SLOT( phaseRightClicked() ) ); + connect( m_loadButton, SIGNAL( clicked() ), this, SLOT( loadClicked() ) ); + + connect( a1_selectButton, SIGNAL( clicked() ), this, SLOT( updateLayout() ) ); + connect( a2_selectButton, SIGNAL( clicked() ), this, SLOT( updateLayout() ) ); + connect( b1_selectButton, SIGNAL( clicked() ), this, SLOT( updateLayout() ) ); + connect( b2_selectButton, SIGNAL( clicked() ), this, SLOT( updateLayout() ) ); + + updateLayout(); +} + + +WatsynView::~WatsynView() +{ +} + + + +void WatsynView::updateLayout() +{ + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->show(); + a2_graph->hide(); + b1_graph->hide(); + b2_graph->hide(); + break; + case A2_OSC: + a1_graph->hide(); + a2_graph->show(); + b1_graph->hide(); + b2_graph->hide(); + break; + case B1_OSC: + a1_graph->hide(); + a2_graph->hide(); + b1_graph->show(); + b2_graph->hide(); + break; + case B2_OSC: + a1_graph->hide(); + a2_graph->hide(); + b1_graph->hide(); + b2_graph->show(); + break; + } +} + + + +void WatsynView::sinWaveClicked() +{ + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->model()->setWaveToSine(); + engine::getSong()->setModified(); + break; + case A2_OSC: + a2_graph->model()->setWaveToSine(); + engine::getSong()->setModified(); + break; + case B1_OSC: + b1_graph->model()->setWaveToSine(); + engine::getSong()->setModified(); + break; + case B2_OSC: + b2_graph->model()->setWaveToSine(); + engine::getSong()->setModified(); + break; + } +} + + +void WatsynView::triWaveClicked() +{ + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->model()->setWaveToTriangle(); + engine::getSong()->setModified(); + break; + case A2_OSC: + a2_graph->model()->setWaveToTriangle(); + engine::getSong()->setModified(); + break; + case B1_OSC: + b1_graph->model()->setWaveToTriangle(); + engine::getSong()->setModified(); + break; + case B2_OSC: + b2_graph->model()->setWaveToTriangle(); + engine::getSong()->setModified(); + break; + } +} + + +void WatsynView::sawWaveClicked() +{ + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->model()->setWaveToSaw(); + engine::getSong()->setModified(); + break; + case A2_OSC: + a2_graph->model()->setWaveToSaw(); + engine::getSong()->setModified(); + break; + case B1_OSC: + b1_graph->model()->setWaveToSaw(); + engine::getSong()->setModified(); + break; + case B2_OSC: + b2_graph->model()->setWaveToSaw(); + engine::getSong()->setModified(); + break; + } +} + + +void WatsynView::sqrWaveClicked() +{ + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->model()->setWaveToSquare(); + engine::getSong()->setModified(); + break; + case A2_OSC: + a2_graph->model()->setWaveToSquare(); + engine::getSong()->setModified(); + break; + case B1_OSC: + b1_graph->model()->setWaveToSquare(); + engine::getSong()->setModified(); + break; + case B2_OSC: + b2_graph->model()->setWaveToSquare(); + engine::getSong()->setModified(); + break; + } +} + + +void WatsynView::normalizeClicked() +{ + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->model()->normalize(); + engine::getSong()->setModified(); + break; + case A2_OSC: + a2_graph->model()->normalize(); + engine::getSong()->setModified(); + break; + case B1_OSC: + b1_graph->model()->normalize(); + engine::getSong()->setModified(); + break; + case B2_OSC: + b2_graph->model()->normalize(); + engine::getSong()->setModified(); + break; + } +} + + +void WatsynView::invertClicked() +{ + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->model()->invert(); + engine::getSong()->setModified(); + break; + case A2_OSC: + a2_graph->model()->invert(); + engine::getSong()->setModified(); + break; + case B1_OSC: + b1_graph->model()->invert(); + engine::getSong()->setModified(); + break; + case B2_OSC: + b2_graph->model()->invert(); + engine::getSong()->setModified(); + break; + } +} + + +void WatsynView::smoothClicked() +{ + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->model()->smooth(); + engine::getSong()->setModified(); + break; + case A2_OSC: + a2_graph->model()->smooth(); + engine::getSong()->setModified(); + break; + case B1_OSC: + b1_graph->model()->smooth(); + engine::getSong()->setModified(); + break; + case B2_OSC: + b2_graph->model()->smooth(); + engine::getSong()->setModified(); + break; + } +} + + +void WatsynView::phaseLeftClicked() +{ + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->model()->shiftPhase( -15 ); + engine::getSong()->setModified(); + break; + case A2_OSC: + a2_graph->model()->shiftPhase( -15 ); + engine::getSong()->setModified(); + break; + case B1_OSC: + b1_graph->model()->shiftPhase( -15 ); + engine::getSong()->setModified(); + break; + case B2_OSC: + b2_graph->model()->shiftPhase( -15 ); + engine::getSong()->setModified(); + break; + } +} + + +void WatsynView::phaseRightClicked() +{ + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->model()->shiftPhase( 15 ); + engine::getSong()->setModified(); + break; + case A2_OSC: + a2_graph->model()->shiftPhase( 15 ); + engine::getSong()->setModified(); + break; + case B1_OSC: + b1_graph->model()->shiftPhase( 15 ); + engine::getSong()->setModified(); + break; + case B2_OSC: + b2_graph->model()->shiftPhase( 15 ); + engine::getSong()->setModified(); + break; + } +} + + +void WatsynView::loadClicked() +{ + QString fileName; + switch( m_selectedGraphGroup->model()->value() ) + { + case A1_OSC: + a1_graph->model()->setWaveToUser(); + engine::getSong()->setModified(); + break; + case A2_OSC: + a2_graph->model()->setWaveToUser(); + engine::getSong()->setModified(); + break; + case B1_OSC: + b1_graph->model()->setWaveToUser(); + engine::getSong()->setModified(); + break; + case B2_OSC: + b2_graph->model()->setWaveToUser(); + engine::getSong()->setModified(); + break; + } +} + + +void WatsynView::modelChanged() +{ + WatsynInstrument * w = castModel(); + + a1_volKnob -> setModel( &w -> a1_vol ); + a2_volKnob -> setModel( &w -> a2_vol ); + b1_volKnob -> setModel( &w -> b1_vol ); + b2_volKnob -> setModel( &w -> b2_vol ); + + a1_panKnob -> setModel( &w -> a1_pan ); + a2_panKnob -> setModel( &w -> a2_pan ); + b1_panKnob -> setModel( &w -> b1_pan ); + b2_panKnob -> setModel( &w -> b2_pan ); + + a1_multKnob -> setModel( &w -> a1_mult ); + a2_multKnob -> setModel( &w -> a2_mult ); + b1_multKnob -> setModel( &w -> b1_mult ); + b2_multKnob -> setModel( &w -> b2_mult ); + + a1_ltuneKnob -> setModel( &w -> a1_ltune ); + a2_ltuneKnob -> setModel( &w -> a2_ltune ); + b1_ltuneKnob -> setModel( &w -> b1_ltune ); + b2_ltuneKnob -> setModel( &w -> b2_ltune ); + + a1_rtuneKnob -> setModel( &w -> a1_rtune ); + a2_rtuneKnob -> setModel( &w -> a2_rtune ); + b1_rtuneKnob -> setModel( &w -> b1_rtune ); + b2_rtuneKnob -> setModel( &w -> b2_rtune ); + + m_abmixKnob -> setModel( &w -> m_abmix ); + + m_selectedGraphGroup -> setModel( &w -> m_selectedGraph ); + + m_aModGroup -> setModel( &w -> m_amod ); + m_bModGroup -> setModel( &w -> m_bmod ); + + a1_graph -> setModel( &w -> a1_graph ); + a2_graph -> setModel( &w -> a2_graph ); + b1_graph -> setModel( &w -> b1_graph ); + b2_graph -> setModel( &w -> b2_graph ); + + m_envAmtKnob -> setModel( &w -> m_envAmt ); + m_envAttKnob -> setModel( &w -> m_envAtt ); + m_envHoldKnob -> setModel( &w -> m_envHold ); + m_envDecKnob -> setModel( &w -> m_envDec ); + + m_xtalkKnob -> setModel( &w -> m_xtalk ); +} + + + + + +extern "C" +{ + +// necessary for getting instance out of shared lib +Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data ) +{ + return( new WatsynInstrument( static_cast( _data ) ) ); +} + + +} + + +#include "moc_Watsyn.cxx" diff --git a/plugins/watsyn/Watsyn.h b/plugins/watsyn/Watsyn.h new file mode 100644 index 000000000..5b4072202 --- /dev/null +++ b/plugins/watsyn/Watsyn.h @@ -0,0 +1,379 @@ +/* + * Watsyn.h - a 4-oscillator modulating wavetable synth + * + * Copyright (c) 2014 Vesa Kivimäki + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 WATSYN_H +#define WATSYN_H + +#include "Instrument.h" +#include "InstrumentView.h" +#include "graph.h" +#include "AutomatableModel.h" +#include "automatable_button.h" +#include "TempoSyncKnob.h" +#include "NotePlayHandle.h" +#include "pixmap_button.h" + + +#define makeknob( name, x, y, hint, unit, oname ) \ + name = new knob( knobStyled, this ); \ + name ->move( x, y ); \ + name ->setHintText( tr( hint ) + " ", unit ); \ + name ->setObjectName( oname ); \ + name ->setFixedSize( 19, 19 ); + +#define maketsknob( name, x, y, hint, unit, oname ) \ + name = new TempoSyncKnob( knobStyled, this ); \ + name ->move( x, y ); \ + name ->setHintText( tr( hint ) + " ", unit ); \ + name ->setObjectName( oname ); \ + name ->setFixedSize( 19, 19 ); + +#define A1ROW 26 +#define A2ROW 49 +#define B1ROW 72 +#define B2ROW 95 + + +const int GRAPHLEN = 220; +const int WAVELEN = 4400; + +const int WAVERATIO = WAVELEN / GRAPHLEN; + +const int PMOD_AMT = WAVELEN / 2; + +const int MOD_MIX = 0; +const int MOD_AM = 1; +const int MOD_RM = 2; +const int MOD_PM = 3; +const int NUM_MODS = 4; + +const int A1_OSC = 0; +const int A2_OSC = 1; +const int B1_OSC = 2; +const int B2_OSC = 3; +const int NUM_OSCS = 4; + +class WatsynInstrument; + +class WatsynObject +{ +public: + WatsynObject( float * _A1wave, float * _A2wave, + float * _B1wave, float * _B2wave, + int _amod, int _bmod, const sample_rate_t _samplerate, NotePlayHandle * _nph, fpp_t _frames, + WatsynInstrument * _w ); + virtual ~WatsynObject(); + + void renderOutput( fpp_t _frames ); + + inline sampleFrame * abuf() const + { + return m_abuf; + } + inline sampleFrame * bbuf() const + { + return m_bbuf; + } + inline sample_rate_t samplerate() const + { + return m_samplerate; + } + +private: + // linear interpolation + inline sample_t interpolate( sample_t s1, sample_t s2, float x ) + { + return s1 + ( s2 - s1 ) * x; + } +/* + // quick and dirty approximation of cubic interpolation + inline sample_t interpolate( sample_t s1, sample_t s2, float x ) + { + const float x2 = powf( x, 2 ); + const float x3 = powf( x, 3 ); + const float m = s2 - s1; + + return ( ( x3 * 2.0 - x2 * 3.0 + 1.0 ) * s1 ) + + ( ( x3 * -2.0 + x2 * 3.0 ) * s2 ); + + ( ( x + x3 * 2.0 - x2 * 3.0 ) * m ); + } +*/ + // more accurate cubic interpolation... + // consumes more cpu than ^ but doesn't bring a marked increase in sound quality IMO +/* inline sample_t interpolate( sample_t s0, sample_t s1, sample_t s2, sample_t s3, float x ) + { + const float x2 = powf( x, 2 ); + const float x3 = powf( x, 3 ); + const float m1 = ( s2 - s0 ) / 2; + const float m2 = ( s3 - s1 ) / 2; + + return ( ( x3 * 2.0 - x2 * 3.0 + 1.0 ) * s1 ) + + ( ( x3 * -2.0 + x2 * 3.0 ) * s2 ) + + ( ( x3 - x2 * 2 + x ) * m1 ) + + ( ( x3 - x2 ) * m2 ); + }*/ + + + int m_amod; + int m_bmod; + + const sample_rate_t m_samplerate; + NotePlayHandle * m_nph; + + fpp_t m_fpp; + + WatsynInstrument * m_parent; + + sampleFrame * m_abuf; + sampleFrame * m_bbuf; + + float m_lphase [NUM_OSCS]; + float m_rphase [NUM_OSCS]; + + float m_A1wave [WAVELEN]; + float m_A2wave [WAVELEN]; + float m_B1wave [WAVELEN]; + float m_B2wave [WAVELEN]; +}; + +class WatsynInstrument : public Instrument +{ + Q_OBJECT +public: + WatsynInstrument( InstrumentTrack * _instrument_track ); + virtual ~WatsynInstrument(); + + 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 + { + return( 64 ); + } + + virtual PluginView * instantiateView( QWidget * _parent ); + +public slots: + void updateVolumes(); + void updateFreq(); + void updateWaves(); + +protected: + float m_lvol [NUM_OSCS]; + float m_rvol [NUM_OSCS]; + + float m_lfreq [NUM_OSCS]; + float m_rfreq [NUM_OSCS]; + +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; + } + + // memcpy with cubic interpolation (cip for short) and 10x oversampling to increase wavetable quality + inline void cipcpy( float * _dst, float * _src ) + { + // calculate cyclic tangents + float tang[GRAPHLEN]; + tang[0] = ( _src[1] - _src[ GRAPHLEN - 1] ) / 2; + tang[ GRAPHLEN - 1 ] = ( _src[0] - _src[ GRAPHLEN - 2 ] ) / 2; + for( int i = 1; i < GRAPHLEN-1; i++ ) + { + tang[i] = ( _src[i+1] - _src[i-1] ) / 2; + } + + // calculate cspline + for( int i=0; i < WAVELEN; i++ ) + { + const float s1 = _src[ i / WAVERATIO ]; + const float s2 = _src[ ( i / WAVERATIO + 1 ) % GRAPHLEN ]; + const float m1 = tang[ i / WAVERATIO ]; + const float m2 = tang[ ( i / WAVERATIO + 1 ) % GRAPHLEN ]; + + const float x = static_cast( i % WAVERATIO ) / WAVERATIO; + const float x2 = x * x; + const float x3 = x * x * x; + + _dst[i] = ( ( x3 * 2.0 - x2 * 3.0 + 1.0 ) * s1 ) + + ( ( x3 * -2.0 + x2 * 3.0 ) * s2 ) + + ( ( x3 - x2 * 2 + x ) * m1 ) + + ( ( x3 - x2 ) * m2 ); + } + } + + FloatModel a1_vol; + FloatModel a2_vol; + FloatModel b1_vol; + FloatModel b2_vol; + + FloatModel a1_pan; + FloatModel a2_pan; + FloatModel b1_pan; + FloatModel b2_pan; + + FloatModel a1_mult; + FloatModel a2_mult; + FloatModel b1_mult; + FloatModel b2_mult; + + FloatModel a1_ltune; + FloatModel a2_ltune; + FloatModel b1_ltune; + FloatModel b2_ltune; + + FloatModel a1_rtune; + FloatModel a2_rtune; + FloatModel b1_rtune; + FloatModel b2_rtune; + + graphModel a1_graph; + graphModel a2_graph; + graphModel b1_graph; + graphModel b2_graph; + + FloatModel m_abmix; + + FloatModel m_envAmt; + + TempoSyncKnobModel m_envAtt; + TempoSyncKnobModel m_envHold; + TempoSyncKnobModel m_envDec; + + FloatModel m_xtalk; + + IntModel m_amod; + IntModel m_bmod; + + IntModel m_selectedGraph; + + float A1_wave [WAVELEN]; + float A2_wave [WAVELEN]; + float B1_wave [WAVELEN]; + float B2_wave [WAVELEN]; + + friend class WatsynObject; + friend class WatsynView; +}; + + +class WatsynView : public InstrumentView +{ + Q_OBJECT +public: + WatsynView( Instrument * _instrument, + QWidget * _parent ); + virtual ~WatsynView(); + +protected slots: + void updateLayout(); + + void sinWaveClicked(); + void triWaveClicked(); + void sawWaveClicked(); + void sqrWaveClicked(); + + void smoothClicked(); + void normalizeClicked(); + void invertClicked(); + void phaseLeftClicked(); + void phaseRightClicked(); + void loadClicked(); + +private: + virtual void modelChanged(); + +// knobs + knob * a1_volKnob; + knob * a2_volKnob; + knob * b1_volKnob; + knob * b2_volKnob; + + knob * a1_panKnob; + knob * a2_panKnob; + knob * b1_panKnob; + knob * b2_panKnob; + + knob * a1_multKnob; + knob * a2_multKnob; + knob * b1_multKnob; + knob * b2_multKnob; + + knob * a1_ltuneKnob; + knob * a2_ltuneKnob; + knob * b1_ltuneKnob; + knob * b2_ltuneKnob; + + knob * a1_rtuneKnob; + knob * a2_rtuneKnob; + knob * b1_rtuneKnob; + knob * b2_rtuneKnob; + + knob * m_abmixKnob; + + knob * m_envAmtKnob; + + TempoSyncKnob * m_envAttKnob; + TempoSyncKnob * m_envHoldKnob; + TempoSyncKnob * m_envDecKnob; + + knob * m_xtalkKnob; + + automatableButtonGroup * m_selectedGraphGroup; + automatableButtonGroup * m_aModGroup; + automatableButtonGroup * m_bModGroup; + + graph * a1_graph; + graph * a2_graph; + graph * b1_graph; + graph * b2_graph; + + pixmapButton * m_sinWaveButton; + pixmapButton * m_triWaveButton; + pixmapButton * m_sawWaveButton; + pixmapButton * m_sqrWaveButton; + pixmapButton * m_normalizeButton; + pixmapButton * m_invertButton; + pixmapButton * m_smoothButton; + pixmapButton * m_phaseLeftButton; + pixmapButton * m_phaseRightButton; + pixmapButton * m_loadButton; + +}; + +#endif diff --git a/plugins/watsyn/a1_active.png b/plugins/watsyn/a1_active.png new file mode 100644 index 000000000..06dbee185 Binary files /dev/null and b/plugins/watsyn/a1_active.png differ diff --git a/plugins/watsyn/a1_inactive.png b/plugins/watsyn/a1_inactive.png new file mode 100644 index 000000000..2352da085 Binary files /dev/null and b/plugins/watsyn/a1_inactive.png differ diff --git a/plugins/watsyn/a2_active.png b/plugins/watsyn/a2_active.png new file mode 100644 index 000000000..cd8963a41 Binary files /dev/null and b/plugins/watsyn/a2_active.png differ diff --git a/plugins/watsyn/a2_inactive.png b/plugins/watsyn/a2_inactive.png new file mode 100644 index 000000000..6aeacacfb Binary files /dev/null and b/plugins/watsyn/a2_inactive.png differ diff --git a/plugins/watsyn/aam_active.png b/plugins/watsyn/aam_active.png new file mode 100644 index 000000000..984c8f776 Binary files /dev/null and b/plugins/watsyn/aam_active.png differ diff --git a/plugins/watsyn/aam_inactive.png b/plugins/watsyn/aam_inactive.png new file mode 100644 index 000000000..979e48b93 Binary files /dev/null and b/plugins/watsyn/aam_inactive.png differ diff --git a/plugins/watsyn/amix_active.png b/plugins/watsyn/amix_active.png new file mode 100644 index 000000000..6c76d44ff Binary files /dev/null and b/plugins/watsyn/amix_active.png differ diff --git a/plugins/watsyn/amix_inactive.png b/plugins/watsyn/amix_inactive.png new file mode 100644 index 000000000..846cbf6e7 Binary files /dev/null and b/plugins/watsyn/amix_inactive.png differ diff --git a/plugins/watsyn/apm_active.png b/plugins/watsyn/apm_active.png new file mode 100644 index 000000000..3011bfee4 Binary files /dev/null and b/plugins/watsyn/apm_active.png differ diff --git a/plugins/watsyn/apm_inactive.png b/plugins/watsyn/apm_inactive.png new file mode 100644 index 000000000..2ecea0683 Binary files /dev/null and b/plugins/watsyn/apm_inactive.png differ diff --git a/plugins/watsyn/arm_active.png b/plugins/watsyn/arm_active.png new file mode 100644 index 000000000..cd70a1fd4 Binary files /dev/null and b/plugins/watsyn/arm_active.png differ diff --git a/plugins/watsyn/arm_inactive.png b/plugins/watsyn/arm_inactive.png new file mode 100644 index 000000000..8fed33926 Binary files /dev/null and b/plugins/watsyn/arm_inactive.png differ diff --git a/plugins/watsyn/artwork.png b/plugins/watsyn/artwork.png new file mode 100644 index 000000000..bd321cdbd Binary files /dev/null and b/plugins/watsyn/artwork.png differ diff --git a/plugins/watsyn/b1_active.png b/plugins/watsyn/b1_active.png new file mode 100644 index 000000000..5788f45c0 Binary files /dev/null and b/plugins/watsyn/b1_active.png differ diff --git a/plugins/watsyn/b1_inactive.png b/plugins/watsyn/b1_inactive.png new file mode 100644 index 000000000..780c3b19d Binary files /dev/null and b/plugins/watsyn/b1_inactive.png differ diff --git a/plugins/watsyn/b2_active.png b/plugins/watsyn/b2_active.png new file mode 100644 index 000000000..cba084752 Binary files /dev/null and b/plugins/watsyn/b2_active.png differ diff --git a/plugins/watsyn/b2_inactive.png b/plugins/watsyn/b2_inactive.png new file mode 100644 index 000000000..00bfa14fa Binary files /dev/null and b/plugins/watsyn/b2_inactive.png differ diff --git a/plugins/watsyn/bam_active.png b/plugins/watsyn/bam_active.png new file mode 100644 index 000000000..194b9d9b3 Binary files /dev/null and b/plugins/watsyn/bam_active.png differ diff --git a/plugins/watsyn/bam_inactive.png b/plugins/watsyn/bam_inactive.png new file mode 100644 index 000000000..b679094d8 Binary files /dev/null and b/plugins/watsyn/bam_inactive.png differ diff --git a/plugins/watsyn/bmix_active.png b/plugins/watsyn/bmix_active.png new file mode 100644 index 000000000..8a2fbc280 Binary files /dev/null and b/plugins/watsyn/bmix_active.png differ diff --git a/plugins/watsyn/bmix_inactive.png b/plugins/watsyn/bmix_inactive.png new file mode 100644 index 000000000..60b7aa37c Binary files /dev/null and b/plugins/watsyn/bmix_inactive.png differ diff --git a/plugins/watsyn/bpm_active.png b/plugins/watsyn/bpm_active.png new file mode 100644 index 000000000..250b5ff0c Binary files /dev/null and b/plugins/watsyn/bpm_active.png differ diff --git a/plugins/watsyn/bpm_inactive.png b/plugins/watsyn/bpm_inactive.png new file mode 100644 index 000000000..94d2a94d2 Binary files /dev/null and b/plugins/watsyn/bpm_inactive.png differ diff --git a/plugins/watsyn/brm_active.png b/plugins/watsyn/brm_active.png new file mode 100644 index 000000000..2bed299e4 Binary files /dev/null and b/plugins/watsyn/brm_active.png differ diff --git a/plugins/watsyn/brm_inactive.png b/plugins/watsyn/brm_inactive.png new file mode 100644 index 000000000..03a361214 Binary files /dev/null and b/plugins/watsyn/brm_inactive.png differ diff --git a/plugins/watsyn/inv_active.png b/plugins/watsyn/inv_active.png new file mode 100644 index 000000000..31aacc621 Binary files /dev/null and b/plugins/watsyn/inv_active.png differ diff --git a/plugins/watsyn/inv_inactive.png b/plugins/watsyn/inv_inactive.png new file mode 100644 index 000000000..637bf5390 Binary files /dev/null and b/plugins/watsyn/inv_inactive.png differ diff --git a/plugins/watsyn/load_active.png b/plugins/watsyn/load_active.png new file mode 100644 index 000000000..e704e1db1 Binary files /dev/null and b/plugins/watsyn/load_active.png differ diff --git a/plugins/watsyn/load_inactive.png b/plugins/watsyn/load_inactive.png new file mode 100644 index 000000000..93b1926c7 Binary files /dev/null and b/plugins/watsyn/load_inactive.png differ diff --git a/plugins/watsyn/logo.png b/plugins/watsyn/logo.png new file mode 100644 index 000000000..3e77966ff Binary files /dev/null and b/plugins/watsyn/logo.png differ diff --git a/plugins/watsyn/norm_active.png b/plugins/watsyn/norm_active.png new file mode 100644 index 000000000..2795f2785 Binary files /dev/null and b/plugins/watsyn/norm_active.png differ diff --git a/plugins/watsyn/norm_inactive.png b/plugins/watsyn/norm_inactive.png new file mode 100644 index 000000000..1fd29f4b7 Binary files /dev/null and b/plugins/watsyn/norm_inactive.png differ diff --git a/plugins/watsyn/phl_active.png b/plugins/watsyn/phl_active.png new file mode 100644 index 000000000..b4e34246d Binary files /dev/null and b/plugins/watsyn/phl_active.png differ diff --git a/plugins/watsyn/phl_inactive.png b/plugins/watsyn/phl_inactive.png new file mode 100644 index 000000000..ef55ac066 Binary files /dev/null and b/plugins/watsyn/phl_inactive.png differ diff --git a/plugins/watsyn/phr_active.png b/plugins/watsyn/phr_active.png new file mode 100644 index 000000000..9d22adae7 Binary files /dev/null and b/plugins/watsyn/phr_active.png differ diff --git a/plugins/watsyn/phr_inactive.png b/plugins/watsyn/phr_inactive.png new file mode 100644 index 000000000..2ce5ccf85 Binary files /dev/null and b/plugins/watsyn/phr_inactive.png differ diff --git a/plugins/watsyn/saw_active.png b/plugins/watsyn/saw_active.png new file mode 100644 index 000000000..da9dceef3 Binary files /dev/null and b/plugins/watsyn/saw_active.png differ diff --git a/plugins/watsyn/saw_inactive.png b/plugins/watsyn/saw_inactive.png new file mode 100644 index 000000000..570dab73f Binary files /dev/null and b/plugins/watsyn/saw_inactive.png differ diff --git a/plugins/watsyn/sin_active.png b/plugins/watsyn/sin_active.png new file mode 100644 index 000000000..c3cbffc43 Binary files /dev/null and b/plugins/watsyn/sin_active.png differ diff --git a/plugins/watsyn/sin_inactive.png b/plugins/watsyn/sin_inactive.png new file mode 100644 index 000000000..afa36345d Binary files /dev/null and b/plugins/watsyn/sin_inactive.png differ diff --git a/plugins/watsyn/smooth_active.png b/plugins/watsyn/smooth_active.png new file mode 100644 index 000000000..e23abe245 Binary files /dev/null and b/plugins/watsyn/smooth_active.png differ diff --git a/plugins/watsyn/smooth_inactive.png b/plugins/watsyn/smooth_inactive.png new file mode 100644 index 000000000..7f9511427 Binary files /dev/null and b/plugins/watsyn/smooth_inactive.png differ diff --git a/plugins/watsyn/sqr_active.png b/plugins/watsyn/sqr_active.png new file mode 100644 index 000000000..444bcead2 Binary files /dev/null and b/plugins/watsyn/sqr_active.png differ diff --git a/plugins/watsyn/sqr_inactive.png b/plugins/watsyn/sqr_inactive.png new file mode 100644 index 000000000..c61f086a5 Binary files /dev/null and b/plugins/watsyn/sqr_inactive.png differ diff --git a/plugins/watsyn/tri_active.png b/plugins/watsyn/tri_active.png new file mode 100644 index 000000000..690d1ebde Binary files /dev/null and b/plugins/watsyn/tri_active.png differ diff --git a/plugins/watsyn/tri_inactive.png b/plugins/watsyn/tri_inactive.png new file mode 100644 index 000000000..1e8f13c5c Binary files /dev/null and b/plugins/watsyn/tri_inactive.png differ diff --git a/plugins/watsyn/wavegraph.png b/plugins/watsyn/wavegraph.png new file mode 100644 index 000000000..97b66efb7 Binary files /dev/null and b/plugins/watsyn/wavegraph.png differ diff --git a/src/gui/widgets/graph.cpp b/src/gui/widgets/graph.cpp index a7011537c..fbf3b5a43 100644 --- a/src/gui/widgets/graph.cpp +++ b/src/gui/widgets/graph.cpp @@ -679,7 +679,11 @@ void graphModel::shiftPhase( int _deg ) // shift phase for( int i = 0; i < length(); i++ ) - m_samples[i] = temp[ ( i + offset ) % length() ]; + { + int o = ( i + offset ) % length(); + while( o < 0 ) o += length(); + m_samples[i] = temp[o]; + } emit samplesChanged( 0, length()-1 ); }