diff --git a/cmake/modules/PluginList.cmake b/cmake/modules/PluginList.cmake index 151c5bd66a2..c2d26ecb694 100644 --- a/cmake/modules/PluginList.cmake +++ b/cmake/modules/PluginList.cmake @@ -67,6 +67,7 @@ SET(LMMS_PLUGIN_LIST VstEffect Watsyn WaveShaper + WaveShaper2 Vectorscope Vibed Xpressive diff --git a/include/VectorGraph.h b/include/VectorGraph.h new file mode 100644 index 00000000000..2af4a8ac7f1 --- /dev/null +++ b/include/VectorGraph.h @@ -0,0 +1,450 @@ +/* + * VectorGraph.h - vector-based replacement for Graph.h + * + * Copyright (c) 2018 Joshua Wade + * + * 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. + * + */ + +// Documentation: +// https://github.com/SecondFlight/lmms/wiki/VectorGraph + +#ifndef VECTORGRAPH_H +#define VECTORGRAPH_H + +#include "lmms_export.h" + +#include +#include +#include +#include + +#include "ModelView.h" + +namespace lmms +{ + +class VectorGraphModel; +class VectorGraphPoint; + + +namespace gui +{ + +class LMMS_EXPORT VectorGraph : public QWidget, public ModelView +{ + Q_OBJECT +public: + enum TensionType + { + Hold, + SingleCurve, + DoubleCurve, + Stairs, + Pulse, + Wave + }; + VectorGraph( QWidget * _parent, int _width, int _height ); + virtual ~VectorGraph() = default; + + inline void setResolution(int resolution) + { + m_resolution = resolution; + } + + inline VectorGraphModel * model() + { + return castModel(); + } + + inline int getWidth() + { + return m_width; + } + + inline int getHeight() + { + return m_height; + } + + inline int getMargin() + { + return m_margin; + } + + inline float rawToCoordX(float input) + { + auto result = input * (m_width - 1) * (((m_width - 1) - 2 * m_margin)/(float) (m_width - 1)) + m_margin; + return result; + } + + inline float rawToCoordY(float input) + { + auto result = (1 - input) * (m_height - 1) * ((m_height - 1) - 2 * m_margin)/(float) (m_height - 1) + m_margin; + return result; + } + + inline float coordToRawX(float input) + { + return (input - m_margin) * ((m_width - 1)/(float) ((m_width - 1) - 2 * m_margin)) / (m_width - 1); + } + + inline float coordToRawY(float input) + { + return ((m_height - input) - m_margin) * ((m_height - 1)/(float) ((m_height - 1) - 2 * m_margin)) / (m_height - 1); + } + + inline void setMargin(int margin) + { + // The extra space is necessary for the + // 2-pixel border, and because the + // canvas goes from 1 to (m_width - 1) in + // the X and 1 to (m_height - 1) in the y + m_margin = margin + 3; + } + + float calculateSample(float input); + +signals: + void drawn(); +protected: + void contextMenuEvent(QContextMenuEvent* event) override; + void paintEvent(QPaintEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + +private slots: + void deletePoint(); + void setTensionToHold(); + void setTensionToSingle(); + void setTensionToDouble(); + void setTensionToStairs(); + void setTensionToPulse(); + void setTensionToWave(); + +private: + QPainter m_canvas; + int m_resolution; + int m_width; + int m_height; + int m_currentPoint; + int m_margin; + float getTensionHandleYVal(int index); + float getTensionHandleXVal(int index); + void setLastModifiedPoint(int pointIndex); +}; + +} // namespace gui + +class LMMS_EXPORT VectorGraphModel : public Model +{ + Q_OBJECT +public: + VectorGraphModel(Model *_parent, bool _default_constructed); + virtual ~VectorGraphModel() = default; + + inline void setPoints(QVector points) + { + m_points = points; + } + + inline int getPointCount() + { + return m_points.size(); + } + + inline void setCurrentDraggedPoint(int index) + { + m_currentDraggedPoint = index; + } + + inline void resetCurrentDraggedPoint() + { + m_currentDraggedPoint = -1; + } + + inline int getCurrentDraggedPoint() + { + return m_currentDraggedPoint; + } + + inline void setCurrentDraggedTensionHandle(int index) + { + m_currentDraggedTensionHandle = index; + } + + inline void resetCurrentDraggedTensionHandle() + { + m_currentDraggedTensionHandle = -1; + } + + inline int getCurrentDraggedTensionHandle() + { + return m_currentDraggedTensionHandle; + } + + inline QPoint getStoredCursorPos() + { + return m_storedCursorPos; + } + + inline void setStoredCursorPos(QPoint point) + { + m_storedCursorPos = point; + } + + inline void setLastModifiedTension(float tension) + { + m_lastModifiedTension = tension; + } + + inline float getLastModifiedTension() + { + return m_lastModifiedTension; + } + + inline void setLastModifiedTensionType(gui::VectorGraph::TensionType tensionType) + { + m_lastModifiedTensionType = tensionType; + } + + inline gui::VectorGraph::TensionType getLastModifiedTensionType() + { + return m_lastModifiedTensionType; + } + + void setTensionTypeOnPoint(int index, gui::VectorGraph::TensionType type); + + static inline bool floatEqual(float a, float b, float epsilon) + { + return qFabs(a - b) < epsilon; + } + + VectorGraphPoint * getPoint(int index); + float calculateSample(float input); + float calculateSectionSample(float input, int sectionStartIndex); + int getSectionStartIndex(float input); + void insertPointAfter(int index, VectorGraphPoint point); + void tryMove(int index, float x, float y); + int getPointIndexFromCoords(int x, int y, int canvasWidth, int canvasHeight); + int getPointIndexFromTensionHandleCoords(int x, int y, int canvasWidth, int canvasHeight); + void deletePoint(int index); + + + const inline int getPointSize() + { + return 5; + } + const inline int getTensionHandleSize() + { + return 3; + } + inline bool isGridEnabled() + { + return m_gridEnabled; + } + inline void setIsGridEnabled(bool enabled) + { + m_gridEnabled = enabled; + } + inline int getNumGridLines() + { + return m_numGridLines; + } + inline void setNumGridLines(int num) + { + m_numGridLines = num; + } + inline bool isGridSnapEnabled() + { + return m_gridSnapEnabled; + } + inline void setIsGridSnapEnabled(bool enabled) + { + m_gridSnapEnabled = enabled; + } + +private: + QVector m_points; + int m_currentDraggedPoint; + int m_currentDraggedTensionHandle; + QPoint m_storedCursorPos; + float m_lastModifiedTension; + gui::VectorGraph::TensionType m_lastModifiedTensionType; + bool m_gridEnabled; + int m_numGridLines; + bool m_gridSnapEnabled; + + static inline bool arePointsWithinDistance(float x1, float x2, float y1, float y2, float distance) + { + //return qPow(x2 - x1, 2) + qPow(y2 - y1, 2) <= qPow(distance, 2); + qreal aSquared = qPow(x2 - x1, 2); + qreal bSquared = qPow(y2 - y1, 2); + qreal cSquared = qPow(distance, 2); + bool result = aSquared + bSquared <= cSquared; + return result; + } + + float calculateSingleCurve(float input, VectorGraphPoint * point); +}; + + + +class VectorGraphPoint +{ +public: + VectorGraphPoint(float x, float y, float tension, gui::VectorGraph::TensionType type); + VectorGraphPoint(VectorGraphPoint * point); + VectorGraphPoint(); + inline float x() + { + return m_x; + } + inline float y() + { + return m_y; + } + inline void setX(float x) + { + m_x = x; + } + inline void setY(float y) + { + m_y = y; + } + inline float tension() + { + return m_tension; + } + inline gui::VectorGraph::TensionType tensionType() + { + return m_tensionType; + } + inline float tensionPower() + { + return m_tensionPower; + } + inline float absTensionPower() + { + return m_absTensionPower; + } + inline float dryAmt() + { + return m_dryAmt; + } + inline void setTension(float tension) + { + m_tension = tension; + + // Variables for single curve + m_tensionPower = qPow(20, -1 * tension); + m_absTensionPower = qPow(20, qAbs(tension)); + + m_invTensionPower = qPow(20, tension); + m_invAbsTensionPower = qPow(20, qAbs(-1 * tension)); + + m_dryAmt = qPow(1 - qAbs(tension), 5); + } + inline void invertTension() + { + auto tensionPowerStore = m_tensionPower; + auto absTensionPowerStore = m_absTensionPower; + m_tensionPower = m_invTensionPower; + m_absTensionPower = m_invAbsTensionPower; + m_invTensionPower = tensionPowerStore; + m_invAbsTensionPower = absTensionPowerStore; + m_tension = -1 * m_tension; + } + inline void lockX() + { + m_isXLocked = true; + } + inline void lockY() + { + m_isYLocked = true; + } + inline void permaLockX() + { + m_isXPermaLocked = true; + } + inline void permaLockY() + { + m_isYPermaLocked = true; + } + inline void unlockX() + { + m_isXLocked = false; + } + inline void unlockY() + { + m_isYLocked = false; + } + inline void permaUnlockX() + { + m_isXPermaLocked = false; + } + inline void permaUnlockY() + { + m_isYPermaLocked = false; + } + inline bool isXLocked() + { + return m_isXLocked || m_isXPermaLocked; + } + inline bool isYLocked() + { + return m_isYLocked || m_isYPermaLocked; + } + inline void setTensionType(gui::VectorGraph::TensionType type) + { + m_tensionType = type; + } + inline gui::VectorGraph::TensionType getTensionType() + { + return m_tensionType; + } + inline bool canBeDeleted() + { + return m_canBeDeleted; + } + inline void setDeletable(bool deletable) + { + m_canBeDeleted = deletable; + } +private: + float m_x; + float m_y; + float m_tension; + float m_tensionPower; + float m_absTensionPower; + float m_invTensionPower; + float m_invAbsTensionPower; + float m_dryAmt; + gui::VectorGraph::TensionType m_tensionType; + bool m_isXLocked; + bool m_isYLocked; + bool m_isXPermaLocked; + bool m_isYPermaLocked; + bool m_canBeDeleted; +}; + +} // namespace lmms + +#endif diff --git a/plugins/WaveShaper2/CMakeLists.txt b/plugins/WaveShaper2/CMakeLists.txt new file mode 100644 index 00000000000..9eb3ead569e --- /dev/null +++ b/plugins/WaveShaper2/CMakeLists.txt @@ -0,0 +1,7 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(waveshaper2 + WaveShaper2.cpp WaveShaper2Controls.cpp WaveShaper2ControlDialog.cpp + MOCFILES WaveShaper2Controls.h WaveShaper2ControlDialog.h + EMBEDDED_RESOURCES *.png +) diff --git a/plugins/WaveShaper2/WaveShaper2.cpp b/plugins/WaveShaper2/WaveShaper2.cpp new file mode 100644 index 00000000000..9fc842cee27 --- /dev/null +++ b/plugins/WaveShaper2/WaveShaper2.cpp @@ -0,0 +1,169 @@ +/* + * waveshaper.cpp - waveshaper effect-plugin + * + * Copyright (c) 2018 Joshua Wade (https://github.com/SecondFlight) + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2006-2009 Tobias Doerffel + * + * 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 "plugin_export.h" + +#include "WaveShaper2.h" +#include "lmms_math.h" +#include "embed.h" +#include "interpolation.h" + + + +namespace lmms +{ + +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT waveshaper2_plugin_descriptor = +{ + LMMS_STRINGIFY( PLUGIN_NAME ), + "Waveshaper 2", + QT_TRANSLATE_NOOP( "pluginBrowser", + "plugin for waveshaping" ), + "Joshua Wade ", + 0x0100, + Plugin::Effect, + new PluginPixmapLoader("logo"), + NULL, + NULL +} ; + +} + + +WaveShaper2Effect::WaveShaper2Effect( Model * _parent, + const Descriptor::SubPluginFeatures::Key * _key ) : + Effect( &waveshaper2_plugin_descriptor, _parent, _key ), + m_wsControls( this ) +{ +} + + + + +WaveShaper2Effect::~WaveShaper2Effect() +{ +} + + + + +bool WaveShaper2Effect::processAudioBuffer( sampleFrame * _buf, + const fpp_t _frames ) +{ + if( !isEnabled() || !isRunning () ) + { + return( false ); + } + +// variables for effect + int i = 0; + + double out_sum = 0.0; + const float d = dryLevel(); + const float w = wetLevel(); + float input = m_wsControls.m_inputModel.value(); + float output = m_wsControls.m_outputModel.value(); + + ValueBuffer *inputBuffer = m_wsControls.m_inputModel.valueBuffer(); + ValueBuffer *outputBufer = m_wsControls.m_outputModel.valueBuffer(); + + int inputInc = inputBuffer ? 1 : 0; + int outputInc = outputBufer ? 1 : 0; + + const float *inputPtr = inputBuffer ? &( inputBuffer->values()[ 0 ] ) : &input; + const float *outputPtr = outputBufer ? &( outputBufer->values()[ 0 ] ) : &output; + + for( fpp_t f = 0; f < _frames; ++f ) + { + float s[2] = { _buf[f][0], _buf[f][1] }; + +// apply input gain + s[0] *= *inputPtr; + s[1] *= *inputPtr; + +// clip if clip enabled + //if( clip ) + //{ + s[0] = qBound( -1.0f, s[0], 1.0f ); + s[1] = qBound( -1.0f, s[1], 1.0f ); + //} + +// start effect + + for( i=0; i <= 1; ++i ) + { + bool invert = false; + if (s[i] < 0) + invert = true; + + if (invert) + s[i] *= -1; + + s[i] = m_wsControls.m_vectorGraphModel.calculateSample(s[i]); + + if (invert) + s[i] *= -1; + } + +// apply output gain + s[0] *= *outputPtr; + s[1] *= *outputPtr; + + out_sum += _buf[f][0]*_buf[f][0] + _buf[f][1]*_buf[f][1]; +// mix wet/dry signals + _buf[f][0] = d * _buf[f][0] + w * s[0]; + _buf[f][1] = d * _buf[f][1] + w * s[1]; + + outputPtr += outputInc; + inputPtr += inputInc; + } + + checkGate( out_sum / _frames ); + + return( isRunning() ); +} + + + + +extern "C" +{ + +// necessary for getting instance out of shared lib +PLUGIN_EXPORT Plugin * lmms_plugin_main( Model * _parent, void * _data ) +{ + return( new WaveShaper2Effect( _parent, + static_cast( + _data ) ) ); +} + +} + +} // namespace lmms + diff --git a/plugins/WaveShaper2/WaveShaper2.h b/plugins/WaveShaper2/WaveShaper2.h new file mode 100644 index 00000000000..90c5263d331 --- /dev/null +++ b/plugins/WaveShaper2/WaveShaper2.h @@ -0,0 +1,64 @@ +/* + * waveshaper.h - waveshaper effect-plugin + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2006-2008 Tobias Doerffel + * + * 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 WAVESHAPER2_H +#define WAVESHAPER2_H + +#include "Effect.h" +#include "WaveShaper2Controls.h" + + +namespace lmms +{ + +class WaveShaper2Effect : public Effect +{ +public: + WaveShaper2Effect( Model * _parent, + const Descriptor::SubPluginFeatures::Key * _key ); + virtual ~WaveShaper2Effect(); + virtual bool processAudioBuffer( sampleFrame * _buf, + const fpp_t _frames ); + + virtual EffectControls * controls() + { + return( &m_wsControls ); + } + + +private: + + WaveShaper2Controls m_wsControls; + + friend class WaveShaper2Controls; + +} ; + +} // namespace lmms + + + +#endif diff --git a/plugins/WaveShaper2/WaveShaper2ControlDialog.cpp b/plugins/WaveShaper2/WaveShaper2ControlDialog.cpp new file mode 100644 index 00000000000..c0b6174a4d2 --- /dev/null +++ b/plugins/WaveShaper2/WaveShaper2ControlDialog.cpp @@ -0,0 +1,82 @@ +/* + * waveshaper_control_dialog.cpp - control-dialog for waveshaper-effect + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2006-2008 Tobias Doerffel + * + * 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 + +#include "WaveShaper2ControlDialog.h" +#include "WaveShaper2Controls.h" +#include "embed.h" +//#include "Graph.h" +#include "VectorGraph.h" +#include "PixmapButton.h" +#include "LedCheckBox.h" + +namespace lmms::gui +{ + +WaveShaper2ControlDialog::WaveShaper2ControlDialog( + WaveShaper2Controls * _controls ) : + EffectControlDialog( _controls ) +{ + setAutoFillBackground( true ); + QPalette pal; + pal.setBrush( backgroundRole(), + PLUGIN_NAME::getIconPixmap( "artwork" ) ); + setPalette( pal ); + setFixedSize( 224, 274 ); + + VectorGraph * graph = new VectorGraph( this, 204, 205 ); + graph->move( 10, 6 ); + graph->setMaximumSize( 204, 205 ); + graph -> setModel( &_controls -> m_vectorGraphModel ); + + + + Knob * inputKnob = new Knob( knobBright_26, this); + inputKnob -> setVolumeKnob( true ); + inputKnob -> setVolumeRatio( 1.0 ); + inputKnob -> move( 26, 225 ); + inputKnob->setModel( &_controls->m_inputModel ); + inputKnob->setLabel( tr( "INPUT" ) ); + inputKnob->setHintText( tr( "Input gain:" ) , "" ); + + Knob * outputKnob = new Knob( knobBright_26, this ); + outputKnob -> setVolumeKnob( true ); + outputKnob -> setVolumeRatio( 1.0 ); + outputKnob -> move( 76, 225 ); + outputKnob->setModel( &_controls->m_outputModel ); + outputKnob->setLabel( tr( "OUTPUT" ) ); + outputKnob->setHintText( tr( "Output gain:" ), "" ); + + LedCheckBox * clipInputToggle = new LedCheckBox( "Clip input", this, + tr( "Clip input" ), LedCheckBox::Green ); + clipInputToggle -> move( 131, 252 ); + clipInputToggle -> setModel( &_controls -> m_clipModel ); + clipInputToggle->setToolTip( tr( "Clip input signal to 0dB" ) ); +} + +} // namespace lmms::gui + diff --git a/plugins/WaveShaper2/WaveShaper2ControlDialog.h b/plugins/WaveShaper2/WaveShaper2ControlDialog.h new file mode 100644 index 00000000000..d54dcd6f879 --- /dev/null +++ b/plugins/WaveShaper2/WaveShaper2ControlDialog.h @@ -0,0 +1,55 @@ +/* + * waveshaper_control_dialog.h - control-dialog for waveshaper-effect + * + * * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2006-2008 Tobias Doerffel + * + * 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 WAVESHAPER2_CONTROL_DIALOG_H +#define WAVESHAPER2_CONTROL_DIALOG_H + +#include "EffectControlDialog.h" +#include "VectorGraph.h" + +namespace lmms +{ +class WaveShaper2Controls; +} // namespace lmms + +namespace lmms::gui +{ + +class WaveShaper2ControlDialog : public EffectControlDialog +{ + Q_OBJECT +public: + WaveShaper2ControlDialog( WaveShaper2Controls * _controls ); + virtual ~WaveShaper2ControlDialog() + { + } + + +private: +} ; + +} // namespace lmms::gui + +#endif diff --git a/plugins/WaveShaper2/WaveShaper2Controls.cpp b/plugins/WaveShaper2/WaveShaper2Controls.cpp new file mode 100644 index 00000000000..3141e38b58f --- /dev/null +++ b/plugins/WaveShaper2/WaveShaper2Controls.cpp @@ -0,0 +1,84 @@ +/* + * waveshaper_controls.cpp - controls for waveshaper-effect + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2008 Tobias Doerffel + * + * 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 + +#include "WaveShaper2Controls.h" +#include "WaveShaper2.h" +#include "base64.h" +#include "Graph.h" +#include "Engine.h" +#include "Song.h" + + +#define onedB 1.1220184543019633f + +namespace lmms +{ + +WaveShaper2Controls::WaveShaper2Controls( WaveShaper2Effect * _eff ) : + EffectControls( _eff ), + m_effect( _eff ), + m_inputModel( 1.0f, 0.0f, 5.0f, 0.01f, this, tr( "Input gain" ) ), + m_outputModel( 1.0f, 0.0f, 5.0f, 0.01f, this, tr( "Output gain" ) ), + m_vectorGraphModel( this, false ), + m_clipModel( false, this ) +{ +} + + + + +void WaveShaper2Controls::loadSettings( const QDomElement & _this ) +{ +//load input, output knobs + m_inputModel.loadSettings( _this, "inputGain" ); + m_outputModel.loadSettings( _this, "outputGain" ); + + m_clipModel.loadSettings( _this, "clipInput" ); +} + + + + +void WaveShaper2Controls::saveSettings( QDomDocument & _doc, + QDomElement & _this ) +{ +//save input, output knobs + m_inputModel.saveSettings( _doc, _this, "inputGain" ); + m_outputModel.saveSettings( _doc, _this, "outputGain" ); + + m_clipModel.saveSettings( _doc, _this, "clipInput" ); + +} + + +void WaveShaper2Controls::setDefaultShape() +{ + // +} + +} // namespace lmms diff --git a/plugins/WaveShaper2/WaveShaper2Controls.h b/plugins/WaveShaper2/WaveShaper2Controls.h new file mode 100644 index 00000000000..0f4547c270d --- /dev/null +++ b/plugins/WaveShaper2/WaveShaper2Controls.h @@ -0,0 +1,82 @@ +/* + * waveshaper_controls.h - controls for waveshaper-effect + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2008 Tobias Doerffel + * + * 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 WAVESHAPER2_CONTROLS_H +#define WAVESHAPER2_CONTROLS_H + +#include "EffectControls.h" +#include "WaveShaper2ControlDialog.h" +#include "Knob.h" +#include "Graph.h" + +namespace lmms +{ + +class WaveShaper2Effect; + + +class WaveShaper2Controls : public EffectControls +{ + Q_OBJECT +public: + WaveShaper2Controls( WaveShaper2Effect * _eff ); + virtual ~WaveShaper2Controls() + { + } + + virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent ); + virtual void loadSettings( const QDomElement & _this ); + inline virtual QString nodeName() const + { + return( "waveshaper2controls" ); + } + + virtual void setDefaultShape(); + + virtual int controlCount() + { + return( 4 ); + } + + virtual gui::EffectControlDialog * createView() + { + return( new gui::WaveShaper2ControlDialog( this ) ); + } + +private: + WaveShaper2Effect * m_effect; + FloatModel m_inputModel; + FloatModel m_outputModel; + VectorGraphModel m_vectorGraphModel; + BoolModel m_clipModel; + + friend class gui::WaveShaper2ControlDialog; + friend class WaveShaper2Effect; + +} ; + +} // namespace lmms + +#endif diff --git a/plugins/WaveShaper2/add1_active.png b/plugins/WaveShaper2/add1_active.png new file mode 100644 index 00000000000..71979b1812d Binary files /dev/null and b/plugins/WaveShaper2/add1_active.png differ diff --git a/plugins/WaveShaper2/add1_inactive.png b/plugins/WaveShaper2/add1_inactive.png new file mode 100644 index 00000000000..9557735ed75 Binary files /dev/null and b/plugins/WaveShaper2/add1_inactive.png differ diff --git a/plugins/WaveShaper2/artwork.png b/plugins/WaveShaper2/artwork.png new file mode 100644 index 00000000000..be099b17814 Binary files /dev/null and b/plugins/WaveShaper2/artwork.png differ diff --git a/plugins/WaveShaper2/logo.png b/plugins/WaveShaper2/logo.png new file mode 100644 index 00000000000..9340da708dd Binary files /dev/null and b/plugins/WaveShaper2/logo.png differ diff --git a/plugins/WaveShaper2/reset_active.png b/plugins/WaveShaper2/reset_active.png new file mode 100644 index 00000000000..915e2aede9d Binary files /dev/null and b/plugins/WaveShaper2/reset_active.png differ diff --git a/plugins/WaveShaper2/reset_inactive.png b/plugins/WaveShaper2/reset_inactive.png new file mode 100644 index 00000000000..b23b4852030 Binary files /dev/null and b/plugins/WaveShaper2/reset_inactive.png differ diff --git a/plugins/WaveShaper2/smooth_active.png b/plugins/WaveShaper2/smooth_active.png new file mode 100644 index 00000000000..29e88083460 Binary files /dev/null and b/plugins/WaveShaper2/smooth_active.png differ diff --git a/plugins/WaveShaper2/smooth_inactive.png b/plugins/WaveShaper2/smooth_inactive.png new file mode 100644 index 00000000000..c924b58cfff Binary files /dev/null and b/plugins/WaveShaper2/smooth_inactive.png differ diff --git a/plugins/WaveShaper2/sub1_active.png b/plugins/WaveShaper2/sub1_active.png new file mode 100644 index 00000000000..434baca3281 Binary files /dev/null and b/plugins/WaveShaper2/sub1_active.png differ diff --git a/plugins/WaveShaper2/sub1_inactive.png b/plugins/WaveShaper2/sub1_inactive.png new file mode 100644 index 00000000000..a766e6a1914 Binary files /dev/null and b/plugins/WaveShaper2/sub1_inactive.png differ diff --git a/plugins/WaveShaper2/wavegraph.png b/plugins/WaveShaper2/wavegraph.png new file mode 100644 index 00000000000..658af767ae9 Binary files /dev/null and b/plugins/WaveShaper2/wavegraph.png differ diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 1000ba51d1c..41c9c7417fa 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -118,6 +118,7 @@ SET(LMMS_SRCS gui/widgets/TextFloat.cpp gui/widgets/TimeDisplayWidget.cpp gui/widgets/ToolButton.cpp + gui/widgets/VectorGraph.cpp PARENT_SCOPE ) diff --git a/src/gui/widgets/VectorGraph.cpp b/src/gui/widgets/VectorGraph.cpp new file mode 100644 index 00000000000..323f1a21df5 --- /dev/null +++ b/src/gui/widgets/VectorGraph.cpp @@ -0,0 +1,780 @@ +/* + * VectorGraph.cpp - vector-based replacement for Graph.cpp + * + * Copyright (c) 2018 Joshua Wade + * + * 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. + * + */ + +// Documentation: +// https://github.com/SecondFlight/lmms/wiki/VectorGraph + +#include +#include + +#include "VectorGraph.h" +#include "lmms_math.h" +#include "CaptionMenu.h" + +namespace lmms +{ + +namespace gui +{ + +VectorGraph::VectorGraph( QWidget * _parent, int _width, int _height ) : + QWidget( _parent ), + ModelView(new VectorGraphModel(NULL, true), this) +{ + resize( _width, _height ); + + m_width = _width; + m_height = _height; + + m_resolution = m_width; + m_currentPoint = -1; + setMargin(3); +} + +void VectorGraph::paintEvent( QPaintEvent * event ) +{ + QColor lineColor = QColor(3, 147, 178); + QColor invisible = QColor(0, 0, 0, 0); + QColor backgroundColor = QColor(35, 44, 54); + QColor borderLight = QColor(63, 72, 83); + QColor borderDark = QColor(32, 40, 50); + QColor gridColor = QColor(56, 66, 78); + + QPainter canvas(this); + + // Background + canvas.setBrush(QBrush(backgroundColor)); + canvas.drawRect(QRect(QPoint(0, 1), QPoint(m_width - 1, m_height - 1))); + + + + + // Grid lines + if (model()->isGridEnabled()) + { + QPen gridPen = QPen(); + gridPen.setWidth(1); + gridPen.setColor(gridColor); + canvas.setPen(gridPen); + + + int gridAreaWidth = (m_width - 2 * m_margin); + int gridAreaHeight = (m_height - 2 * m_margin); + + canvas.drawLine(m_margin, m_margin, m_margin, m_margin + gridAreaHeight); + canvas.drawLine(m_margin, m_margin + gridAreaHeight, m_margin + gridAreaWidth, m_margin + gridAreaHeight); + canvas.drawLine(m_margin + gridAreaWidth, m_margin + gridAreaHeight, m_margin + gridAreaWidth, m_margin); + canvas.drawLine(m_margin + gridAreaWidth, m_margin, m_margin, m_margin); + for (int i = 1; i < model()->getNumGridLines(); i++) + { + int x = qRound((gridAreaWidth / (float) model()->getNumGridLines()) * i) + m_margin; + int y = qRound((gridAreaHeight / (float) model()->getNumGridLines()) * i) + m_margin; + canvas.drawLine(x, m_margin, x, m_margin + gridAreaHeight); + canvas.drawLine(m_margin, y, m_margin + gridAreaWidth, y); + } + } + + + + canvas.setRenderHint(QPainter::Antialiasing); + + + QPen pen = QPen(); + pen.setWidthF(1.2); + pen.setColor(lineColor); + canvas.setPen(pen); + + canvas.setBrush(QBrush(invisible)); + + QPainterPath path; + VectorGraphPoint * firstPoint = model()->getPoint(0); + path.moveTo(qRound(rawToCoordX(firstPoint->x())), qRound(rawToCoordY(firstPoint->y()))); + + int currentSection = -1; + + for (int i = 0; i < m_resolution; i++) + { + float x = (float) i / m_resolution; + int potentialNewSection = model()->getSectionStartIndex(x); + while (potentialNewSection != currentSection) + { + currentSection++; + path.lineTo(rawToCoordX(x) - 1, rawToCoordY(model()->getPoint(currentSection)->y())); + } + auto y = model()->calculateSample(x); + path.lineTo(rawToCoordX(x), rawToCoordY(y)); + } + + auto lastPoint = model()->getPoint(model()->getPointCount() - 1); + + path.lineTo(rawToCoordX(lastPoint->x()), rawToCoordY(lastPoint->y())); + + canvas.drawPath(path); + + canvas.setBrush(QBrush(lineColor)); + + for (int i = 0; i < model()->getPointCount(); i++) + { + auto point = model()->getPoint(i); + int ps = model()->getPointSize(); + canvas.drawEllipse(QPointF(rawToCoordX(point->x()), rawToCoordY(point->y())), ps, ps); + } + + canvas.setBrush(QBrush(invisible)); + + for (int i = 1; i < model()->getPointCount(); i++) + { + VectorGraphPoint * thisPoint = model()->getPoint(i); + VectorGraphPoint * prevPoint = model()->getPoint(i - 1); + + auto tensionType = thisPoint->getTensionType(); + + if (tensionType == Hold) + continue; + + int ths = model()->getTensionHandleSize(); + + if (tensionType == DoubleCurve || + tensionType == Stairs || + tensionType == Pulse || + tensionType == Wave) + { + canvas.drawEllipse(QPointF(rawToCoordX((thisPoint->x() + prevPoint->x()) / 2), rawToCoordY((thisPoint->y() + prevPoint->y()) / 2)), ths, ths); + continue; + } + + if (model()->floatEqual(thisPoint->x(), prevPoint->x(), 0.00001)) + { + canvas.drawEllipse(QPointF(rawToCoordX(thisPoint->x()), rawToCoordY((thisPoint->y() + prevPoint->y()) / 2)), ths, ths); + continue; + } + + float xValueToDrawAt = rawToCoordX(getTensionHandleXVal(i)); + float yValueToDrawAt = rawToCoordY(getTensionHandleYVal(i)); + canvas.drawEllipse(QPointF(xValueToDrawAt, yValueToDrawAt), ths, ths); + } + + + + canvas.setRenderHint(QPainter::Antialiasing, false); + + // Border + QPen borderPen = QPen(); + borderPen.setWidth(1); + borderPen.setColor(borderLight); + canvas.setPen(borderPen); + + canvas.drawLine(0, 0, 0, m_height - 1); + canvas.drawLine(0, m_height - 1, m_width - 1, m_height - 1); + canvas.drawLine(m_width - 1, m_height - 1, m_width - 1, 0); + canvas.drawLine(m_width - 1, 0, 0, 0); + + borderPen.setColor(borderDark); + canvas.setPen(borderPen); + + canvas.drawLine(1, 1, 1, m_height - 2); + canvas.drawLine(1, m_height - 2, m_width - 2, m_height - 2); + canvas.drawLine(m_width - 2, m_height - 2, m_width - 2, 1); + canvas.drawLine(m_width - 2, 1, 1, 1); +} + + +void VectorGraph::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::MouseButton::RightButton) + { + int pointIndex = model()->getPointIndexFromCoords(coordToRawX(event->x()) * m_width, coordToRawY(event->y()) * m_height, m_width, m_height); + if (pointIndex >= 0) + { + event->ignore(); + return; + } + + int handleIndex = model()->getPointIndexFromTensionHandleCoords(coordToRawX(event->x()) * m_width, coordToRawY(event->y()) * m_height, m_width, m_height); + if (handleIndex >= 0) + { + model()->getPoint(handleIndex)->setTension(0); + model()->setLastModifiedTension(0); + update(); + return; + } + + int leftBoundIndex = model()->getSectionStartIndex((float) event->x() / m_width); + VectorGraphPoint newPoint = VectorGraphPoint( + coordToRawX(event->x()), + coordToRawY(event->y()), + model()->getLastModifiedTension(), + model()->getLastModifiedTensionType() + ); + model()->insertPointAfter(leftBoundIndex, newPoint); + model()->setCurrentDraggedPoint(leftBoundIndex + 1); + event->accept(); // maybe unnecessary + update(); + } + else if (event->button() == Qt::MouseButton::LeftButton) + { + int pointIndex = model()->getPointIndexFromCoords(coordToRawX(event->x()) * m_width, coordToRawY(event->y()) * m_height, m_width, m_height); + int tensionHandleIndex = model()->getPointIndexFromTensionHandleCoords(coordToRawX(event->x()) * m_width, coordToRawY(event->y()) * m_height, m_width, m_height); + + if (pointIndex > -1) + { + model()->setCurrentDraggedPoint(pointIndex); + } + else if (tensionHandleIndex > -1) + { + model()->setStoredCursorPos(cursor().pos()); + model()->setCurrentDraggedTensionHandle(tensionHandleIndex); + } + } +} + +void VectorGraph::mouseMoveEvent(QMouseEvent *event) +{ + if (event->modifiers() == Qt::KeyboardModifier::AltModifier) + model()->setIsGridSnapEnabled(true); + else + model()->setIsGridSnapEnabled(false); + + if (model()->getCurrentDraggedPoint() != -1) + { + model()->tryMove(model()->getCurrentDraggedPoint(), coordToRawX(event->x()), coordToRawY(event->y())); + update(); + } + + if (model()->getCurrentDraggedTensionHandle() != -1) + { + float delta = cursor().pos().y() - model()->getStoredCursorPos().y(); + model()->setStoredCursorPos(cursor().pos()); + + int index = model()->getCurrentDraggedTensionHandle(); + VectorGraphPoint * previousPoint = model()->getPoint(index - 1); + VectorGraphPoint * pointToEdit = model()->getPoint(index); + + if (previousPoint->y() > pointToEdit->y()) + delta *= -1; + + // Subtracting, moving down vertically makes the y value go up + float newTension = pointToEdit->tension() - delta / 250; // Make adjustable from somewhere else - this is an important tweak + if (newTension > 1) + newTension = 1; + else if (newTension < -1) + newTension = -1; + pointToEdit->setTension(newTension); + update(); + } +} + +void VectorGraph::mouseReleaseEvent(QMouseEvent * event) +{ + if (model()->getCurrentDraggedPoint() != -1) + model()->resetCurrentDraggedPoint(); + + if (model()->getCurrentDraggedTensionHandle() > -1) + { + VectorGraphPoint * point = model()->getPoint(model()->getCurrentDraggedTensionHandle()); + model()->setLastModifiedTension(point->tension()); + model()->setLastModifiedTensionType(point->tensionType()); + + QCursor c = cursor(); + QPoint newCursorPoint( + rawToCoordX(getTensionHandleXVal(model()->getCurrentDraggedTensionHandle())), + rawToCoordY(getTensionHandleYVal(model()->getCurrentDraggedTensionHandle()))); + c.setShape(Qt::ArrowCursor); + setCursor(c); + model()->resetCurrentDraggedTensionHandle(); + } +} + +void VectorGraph::contextMenuEvent(QContextMenuEvent* event) +{ + if (model()->getCurrentDraggedPoint() >= 0) + { + return; + } + + m_currentPoint = model()->getPointIndexFromCoords(coordToRawX(event->x()) * m_width, coordToRawY(event->y()) * m_height, m_width, m_height); + + if (m_currentPoint < 0) + { + return; + } + + CaptionMenu contextMenu(model()->displayName(), this); + contextMenu.addAction(QPixmap(), tr("Hold"), this, SLOT(setTensionToHold())); + contextMenu.addAction(QPixmap(), tr("Single Curve"), this, SLOT(setTensionToSingle())); + contextMenu.addAction(QPixmap(), tr("Double Curve"), this, SLOT(setTensionToDouble())); + contextMenu.addAction(QPixmap(), tr("Stairs"), this, SLOT(setTensionToStairs())); + contextMenu.addAction(QPixmap(), tr("Pulse"), this, SLOT(setTensionToPulse())); + contextMenu.addAction(QPixmap(), tr("Wave"), this, SLOT(setTensionToWave())); + if (model()->getPoint(m_currentPoint)->canBeDeleted()) + { + contextMenu.addSeparator(); + contextMenu.addAction(QPixmap(), tr("&Delete"), this, SLOT(deletePoint())); + } + contextMenu.exec(QCursor::pos()); +} + +float VectorGraph::calculateSample(float input) +{ + return model()->calculateSample(input); +} + +void VectorGraph::deletePoint() +{ + if (m_currentPoint < 0) + return; + + model()->deletePoint(m_currentPoint); + m_currentPoint = -1; + update(); +} + +void VectorGraph::setTensionToHold() +{ + model()->setTensionTypeOnPoint(m_currentPoint, VectorGraph::TensionType::Hold); + setLastModifiedPoint(m_currentPoint); + update(); +} + +void VectorGraph::setTensionToSingle() +{ + model()->setTensionTypeOnPoint(m_currentPoint, VectorGraph::TensionType::SingleCurve); + setLastModifiedPoint(m_currentPoint); + update(); +} + +void VectorGraph::setTensionToDouble() +{ + model()->setTensionTypeOnPoint(m_currentPoint, VectorGraph::TensionType::DoubleCurve); + setLastModifiedPoint(m_currentPoint); + update(); +} + +void VectorGraph::setTensionToStairs() +{ + model()->setTensionTypeOnPoint(m_currentPoint, VectorGraph::TensionType::Stairs); + setLastModifiedPoint(m_currentPoint); + update(); +} + +void VectorGraph::setTensionToPulse() +{ + model()->setTensionTypeOnPoint(m_currentPoint, VectorGraph::TensionType::Pulse); + setLastModifiedPoint(m_currentPoint); + update(); +} + +void VectorGraph::setTensionToWave() +{ + model()->setTensionTypeOnPoint(m_currentPoint, VectorGraph::TensionType::Wave); + setLastModifiedPoint(m_currentPoint); + update(); +} + +float VectorGraph::getTensionHandleYVal(int index) +{ + return model()->calculateSample(getTensionHandleXVal(index)); +} + +float VectorGraph::getTensionHandleXVal(int index) +{ + auto point = model()->getPoint(index); + auto previousPoint = model()->getPoint(index - 1); + return (point->x() + previousPoint->x()) / 2; +} + +void VectorGraph::setLastModifiedPoint(int pointIndex) +{ + VectorGraphPoint * point = model()->getPoint(pointIndex); + model()->setLastModifiedTension(point->tension()); + model()->setLastModifiedTensionType(point->tensionType()); +} + +} // namespace gui + + + +VectorGraphModel::VectorGraphModel(Model * _parent, bool _default_constructed): + Model(_parent, tr("VectorGraph"), _default_constructed) +{ + m_points = QVector(); + + auto firstPoint = VectorGraphPoint(0, 0, 0, gui::VectorGraph::TensionType::SingleCurve); + firstPoint.permaLockX(); + firstPoint.setDeletable(false); + m_points.append(firstPoint); + auto finalPoint = VectorGraphPoint(1, 1, 0, gui::VectorGraph::TensionType::SingleCurve); + finalPoint.permaLockX(); + finalPoint.setDeletable(false); + m_points.append(finalPoint); + + m_currentDraggedPoint = -1; + m_currentDraggedTensionHandle = -1; + m_lastModifiedTension = 0; + m_lastModifiedTensionType = gui::VectorGraph::TensionType::SingleCurve; + m_gridEnabled = true; + m_numGridLines = 12; + m_gridSnapEnabled = false; +} + +VectorGraphPoint * VectorGraphModel::getPoint(int index) +{ + return & m_points[index]; +} + +int VectorGraphModel::getSectionStartIndex(float input) +{ + if (m_points.size() == 0) + { + return -1; + } + else if (m_points.size() == 1) + { + return 0; + } + + for (int i = 1; i < m_points.size(); i++) + { + if (m_points[i].x() > input || floatEqual(m_points[i].x(), input, 0.000001)) // unsure if this is a good epsilon + { + return i - 1; + } + } + + return -1; +} + +float VectorGraphModel::calculateSectionSample(float input, int sectionStartIndex) +{ + if (m_points.size() == 1 && sectionStartIndex == 0) + { + return m_points[0].y(); + } + + VectorGraphPoint * point = getPoint(sectionStartIndex + 1); + if (point->getTensionType() == gui::VectorGraph::TensionType::Hold) + { + return 0; + } + else if (point->getTensionType() == gui::VectorGraph::TensionType::SingleCurve) + { + return calculateSingleCurve(input, point); + } + else if (point->getTensionType() == gui::VectorGraph::TensionType::DoubleCurve) + { + if (input < 0.5) + { + return calculateSingleCurve(input * 2, point) * 0.5; + } + else + { + VectorGraphPoint * newPoint = new VectorGraphPoint(point); + + //setTension is expensive. This is equivalent to the following, but using precomputed values: + //point->setTension(point->tension() * -1); + newPoint->invertTension(); + + auto result = calculateSingleCurve((input - 0.5) * 2, newPoint); + delete newPoint; + return result * 0.5 + 0.5; + } + } + else if (point->getTensionType() == gui::VectorGraph::TensionType::Stairs) + { + // maybe make this one keep the tension from going below 0 + // also maybe make this one discrete instead of continuous + float mult = (1 - ((point->tension() + 1)/2)) * .499 + .001; + int scalar = ((int) (0.5/mult))*2; + int scaledInput = (int) (input * scalar); + float output = scaledInput * 1.0 / (scalar); + return output; + } + else if (point->getTensionType() == gui::VectorGraph::TensionType::Pulse) + { + return ((int) (input * (int) ((point->tension() + 1.05) * 50))) % 2; + } + else if (point->getTensionType() == gui::VectorGraph::TensionType::Wave) + { + // triangle case + if (point->tension() > 0) + { + int count = ((int) ((point->tension() + 0.05) * 25)) * 2 + 1; + float width = 1.0/count; + int currentCount = input*count; + + float output = input*count - currentCount*width*count; + + if (currentCount % 2 == 1) + output = 1 - output; + return output; + } + // sine case + else + { + int count = ((int) ((qAbs(point->tension()) + 0.05) * 25)) * 2 + 1; + return qCos(input * M_PI * count) * -0.5 + 0.5; + } + } + return 0; +} + +float VectorGraphModel::calculateSingleCurve(float input, VectorGraphPoint * point) +{ + // I'm not convinced that the code below provides any sort of speedup. + // Might be useful for preventing edge cases though. + // It would if the power function is much less efficient, which I think it might be. + if (floatEqual(point->tension(), 0, 0.00001)) // I have no idea what epsilon to use, probably doesn't matter in this case though + { + return input; + } + + //return point->dryAmt() * input + (1 - point->dryAmt()) * fastPow(input, point->tensionPower()); + /*if (point->tension() < 0) + return qPow(input, point->tensionPower()); + else + return 1 - qPow(1 - input, point->absTensionPower());*/ + + + // based on a cycloid + + float mult = 0.67502558231353759765625; // yay hard-coded values + + float invInput = 1 - input; + + if (point->tension() < 0) + return point->dryAmt() * input + (1 - point->dryAmt()) * qPow(mult * (qAcos(1 - input / mult) - qSqrt(input * (2 * mult - input))), point->tensionPower()); + else + return point->dryAmt() * input + (1 - point->dryAmt()) * (1 - qPow(mult * (qAcos(1 - invInput / mult) - qSqrt(invInput * (2 * mult - invInput))), point->absTensionPower())); +} + +float VectorGraphModel::calculateSample(float input) +{ + int startIndex = getSectionStartIndex(input); + int endIndex = startIndex + 1; + + VectorGraphPoint * startPoint = getPoint(startIndex); + VectorGraphPoint * endPoint = getPoint(endIndex); + + float sectionNormalizedInput = (input - startPoint->x()) * (1 / (endPoint->x() - startPoint->x())); + float sectionNormalizedOutput = calculateSectionSample(sectionNormalizedInput, startIndex); + float output = sectionNormalizedOutput * (endPoint->y() - startPoint->y()) + startPoint->y(); + return output; +} + +void VectorGraphModel::insertPointAfter(int index, VectorGraphPoint point) +{ + m_points.insert(index + 1, point); +} + +void VectorGraphModel::tryMove(int index, float x, float y) +{ + VectorGraphPoint * currentPoint = getPoint(index); + + if (!currentPoint->isXLocked()) + { + bool checkRight = true; + + if (index + 1 == m_points.size()) + { + checkRight = false; + } + + VectorGraphPoint * leftPoint = getPoint(index - 1); + VectorGraphPoint * rightPoint = getPoint(index); // Fixes a warning, this won't actaully be used. + if (checkRight) + { + rightPoint = getPoint(index + 1); + } + + float potentialXValue; + + if (x < leftPoint->x()) + { + potentialXValue = leftPoint->x(); + } + else if (checkRight && x > rightPoint->x()) + { + potentialXValue = rightPoint->x(); + } + else + { + potentialXValue = x; + } + + if (m_gridSnapEnabled) + { + int snappedGridLineIndex = qRound(potentialXValue * m_numGridLines); + float potentialSnappedValue = snappedGridLineIndex / (float)m_numGridLines; + if (potentialSnappedValue < leftPoint->x()) + { + currentPoint->setX((snappedGridLineIndex + 1) / (float)m_numGridLines); + } + else if (checkRight && potentialSnappedValue > rightPoint->x()) + { + currentPoint->setX((snappedGridLineIndex - 1) / (float)m_numGridLines); + } + else + { + currentPoint->setX(potentialSnappedValue); + } + } + else + { + currentPoint->setX(potentialXValue); + } + } + + if (!currentPoint->isYLocked()) + { + if (y > 1) + { + currentPoint->setY(1); + } + else if (y < 0) + { + currentPoint->setY(0); + } + else + { + if (m_gridSnapEnabled) + { + currentPoint->setY(qRound(y * m_numGridLines) / (float)m_numGridLines); + } + else + { + currentPoint->setY(y); + } + } + } +} + +int VectorGraphModel::getPointIndexFromCoords(int x, int y, int canvasWidth, int canvasHeight) +{ + for (int i = 0; i < m_points.size(); i++) + { + VectorGraphPoint * point = getPoint(i); + if (point->isXLocked() && point->isYLocked()) + { + continue; + } + if (arePointsWithinDistance(x, point->x() * canvasWidth, y, point->y() * canvasHeight, getPointSize() + 2)) + { + return i; + } + } + return -1; +} + +int VectorGraphModel::getPointIndexFromTensionHandleCoords(int x, int y, int canvasWidth, int canvasHeight) +{ + for (int i = 1; i < m_points.size(); i++) + { + VectorGraphPoint * startPoint = getPoint(i - 1); + VectorGraphPoint * endPoint = getPoint(i); + if (endPoint->tensionType() == gui::VectorGraph::TensionType::Hold) + return -1; + + float tensionHandleCenterX; + float tensionHandleCenterY; + + if (endPoint->tensionType() == gui::VectorGraph::TensionType::DoubleCurve || + endPoint->tensionType() == gui::VectorGraph::TensionType::Pulse || + endPoint->tensionType() == gui::VectorGraph::TensionType::Stairs || + endPoint->tensionType() == gui::VectorGraph::TensionType::Wave) + { + tensionHandleCenterX = ((startPoint->x() + endPoint->x())/2) * canvasWidth; + tensionHandleCenterY = ((startPoint->y() + endPoint->y())/2) * canvasHeight; + } + else + { + tensionHandleCenterX = ((startPoint->x() + endPoint->x()) / 2) * canvasWidth; + tensionHandleCenterY = calculateSample(tensionHandleCenterX / canvasWidth) * canvasHeight; + } + if (arePointsWithinDistance(x, tensionHandleCenterX, y, tensionHandleCenterY, getTensionHandleSize() + 2)) + { + return i; + } + } + return -1; +} + +void VectorGraphModel::deletePoint(int index) +{ + m_points.removeAt(index); +} + +void VectorGraphModel::setTensionTypeOnPoint(int index, gui::VectorGraph::TensionType type) +{ + getPoint(index)->setTensionType(type); +} + + + + + +VectorGraphPoint::VectorGraphPoint(float x, float y, float tension, gui::VectorGraph::TensionType type) +{ + m_x = x; + m_y = y; + setTension(tension); + m_tensionType = type; + m_isXLocked = false; + m_isYLocked = false; + m_isXPermaLocked = false; + m_isYPermaLocked = false; + m_canBeDeleted = true; +} + +VectorGraphPoint::VectorGraphPoint(VectorGraphPoint * point) +{ + m_x = point->m_x; + m_y = point->m_y; + m_tension = point->m_tension; + m_tensionPower = point->m_tensionPower; + m_absTensionPower = point->m_absTensionPower; + m_invTensionPower = point->m_invTensionPower; + m_invAbsTensionPower = point->m_invAbsTensionPower; + m_dryAmt = point->m_dryAmt; + m_tensionType = point->m_tensionType; + m_isXLocked = point->m_isXLocked; + m_isYLocked = point->m_isYLocked; + m_isXPermaLocked = point->m_isXPermaLocked; + m_isYPermaLocked = point->m_isYPermaLocked; + m_canBeDeleted = point->m_canBeDeleted; +} + +VectorGraphPoint::VectorGraphPoint() +{ + m_x = 0; + m_y = 0; + setTension(0); + m_tensionType = gui::VectorGraph::TensionType::SingleCurve; + m_isXLocked = false; + m_isYLocked = false; + m_isXPermaLocked = false; + m_isYPermaLocked = false; + m_canBeDeleted = true; +} + +} // namespace lmms