Xpressive plugin (#3259)

* First Preview of the X-Pressive Plugin
(exprtk.hpp is not included, get it from my exprtk fork in the branch
internal_data_functions)
available keys:
f- note's frequency. available only in the output expressions
t- time in seconds. in the Waves (W1,W2,W3) it's in the range [0,1)
key- the note's keyboard key. available only in the output expressions.
v- the note's velocity (divided by 255.0 so it is in the range [0,1]).
available only in the output expressions.
rel- gives 0 while the key is holded, and 1 after the key release.
available only in the output expressions.
A1,A2,A3- general purpose knobs (you can control them with the
automations). available only in the output expressions.
W1,W2,W3- precalculated wave forms. can be also load from file. you can
use them only in the output expressions
available functions:
cent(x)- gives pow(2,x/1200)
rand()- random number generator. in range [-1,1). each call gives other
value.
randv(i)- random vector (with pseudo infinite integer cells). the values
are in range [-1,1). it's stays consistent only across the note
playback. so each note playback will get other vector (even on the same
key).
sinew- sine wave with period of 1.
saww- saw wave with period of 1.
squarew- square wave with period of 1.
trianglew- triangle wave with period of 1.
expw- exponent wave with period of 1.
expnw- another exponent wave with period of 1.
moogw- moog wave with period of 1.
moogsaww- moog-saw wave with period of 1.
you can use * % ^ / + - pow sin log pi etc.

* Xpressive Plug-In:
Added Release transition knob that control the "rel" variable. (the
duration of transit from 0 to 1)
Fixed some problems in the displays. (update display when changing
A1,A2,A3, clear display with invalid expression.

* X-Pressive Plug-In: Few more fixes
Changed the callbacks in exprfront.cpp to be templated.
Added help.
Added ExprTk.hpp.
some bug fixes (inf issues).
Added integrate function.

* Special version of ExprTk with modified license (BSL) for the LMMS project https://github.com/LMMS/lmms

* Xpressive Plug-In- fixed some building errors.
Added the "e" euler's constant.

* Xpressive Plug-In - fix mingw64 issues

* X-Pressive Plug-in:
Added "trel" (time since release) variable.
The integrate function can now have unlimited usage.
Added selective interpolation per wave.
Improved a little the random vector function.
Some other improvements, code cleaning, etc...

* Xpressive Plug-In:
move clearGraph definition into Graph.cpp.
fixed compilation errors. (oops..)

* X-Pressive plug-in: updated presets names

* X-Pressive plug-in
added semitone function, added sample-rate variable

* X-Pressive plug-in, code cleaning, changed the rendering function to
achieve performace gain.

* X-Pressive plug-in - fix the string counting function

* X-Pressive plug-in - until somebody will find a better solution,
exprtk.hpp is patched under the name exprtk.patched.hpp ...

* X-Pressive plug-in - fix compiling errors.

* X-Pressive plug-in - added patch file for exprtk.hpp,
added last function that gives last calculated samples.
moved ExprSynth to be with ExprFront for performance reasons.

* X-Pressive plugin - moved the patched file back to the source tree, added .gitignore file..

* X-Pressive plugin - fix compilation error. (isnan isinf)

* X-Pressive plugin - tried to fix embed.cpp problem,
added new variable to the parser (tempo)

* X-Pressive plugin - fixed cmake script

* X-Pressive plugin - updated the license and the diff file.

* Updates to ExprTk

* Added return statement enable/disable via parser settings

Added exprtk_disable_return_statement macro for disabling return statements and associated exceptions at the source code level.

* X-Pressive plugin - updated CMakeLists.txt to use the correct flags on each platform.
also added exprtk.hpp as a dependency for the patch command.
Updated the exprtk diff file.

* X-Pressive plugin - moved the enhanced features flag to the WIN64 installation.

* X-Pressive plugin - another fix for CMakeLists.txt

* Minor updates to ExprTk

Updated multi-sub expression operator to return final sub-expression type.
Updates to exprtk_disable_return_statement macro for disabling return statements and associated exceptions at the source code level.

* X-Pressive plug-in - added try-block around exprtk calls and enabled the
-fexceptions flag, so patch file is no longer needed.

* X-Pressive plug-in - small fix in CMakeLists.txt

* Update ExprTk to tip of branch.

* X-Pressive plugin - added graph drawing feature..

* Updating exprtk.hpp to the latest upstream version
This commit is contained in:
gnudles
2017-06-26 20:45:59 +03:00
committed by Oskar Wallgren
parent d8dc29e675
commit dff76b2e83
48 changed files with 40575 additions and 28 deletions

View File

@@ -9,7 +9,7 @@ MACRO(BUILD_PLUGIN PLUGIN_NAME)
CMAKE_PARSE_ARGUMENTS(PLUGIN "" "" "MOCFILES;EMBEDDED_RESOURCES;UICFILES;LINK" ${ARGN})
SET(PLUGIN_SOURCES ${PLUGIN_UNPARSED_ARGUMENTS})
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/src/gui)
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/include)
ADD_DEFINITIONS(-DPLUGIN_NAME=${PLUGIN_NAME})

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -79,7 +79,8 @@ public:
update();
}
signals:
void drawn();
protected:
virtual void paintEvent( QPaintEvent * _pe );
virtual void dropEvent( QDropEvent * _de );
@@ -145,6 +146,8 @@ public:
return( m_samples.data() );
}
void convolve(const float *convolution, const int convolutionLength, const int centerOffset);
public slots:
void setRange( float _min, float _max );
@@ -165,6 +168,7 @@ public slots:
void normalize();
void invert();
void shiftPhase( int _deg );
void clear();
signals:
void lengthChanged();

View File

@@ -80,6 +80,7 @@ IF("${PLUGIN_LIST}" STREQUAL "")
watsyn
waveshaper
vibed
xpressive
zynaddsubfx
)

2
plugins/xpressive/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
#ignore the patched file
exprtk.patched.hpp

View File

@@ -0,0 +1,10 @@
INCLUDE(BuildPlugin)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dexprtk_disable_sc_andor -Dexprtk_disable_return_statement -Dexprtk_disable_break_continue -Dexprtk_disable_comments -Dexprtk_disable_string_capabilities -Dexprtk_disable_rtl_io_file -Dexprtk_disable_rtl_vecops ${WERROR_FLAGS} -fexceptions")
IF(WIN32)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj -Dexprtk_disable_enhanced_features")
ENDIF()
BUILD_PLUGIN(xpressive expressive_plugin.cpp exprsynth.cpp expressive_plugin.h exprtk.hpp MOCFILES expressive_plugin.h EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png")

17
plugins/xpressive/MIT Normal file
View File

@@ -0,0 +1,17 @@
Copyrights for exprtk.hpp
Permission is hereby granted, free of charge,
to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -0,0 +1,888 @@
/*
* expressive_plugin.cpp - instrument which uses a mathematical formula parser
*
* Copyright (c) 2016-2017 Orr Dvori
*
* 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 "expressive_plugin.h"
#include <QDomElement>
#include "Engine.h"
#include "Graph.h"
#include "GuiApplication.h"
#include "InstrumentTrack.h"
#include "Knob.h"
#include "LedCheckbox.h"
#include "MainWindow.h"
#include "Mixer.h"
#include "NotePlayHandle.h"
#include "Oscillator.h"
#include "PixmapButton.h"
#include "Song.h"
#include "SubWindow.h"
#include "ToolTip.h"
#include "base64.h"
#include "lmms_constants.h"
#include "embed.h"
#include "exprsynth.h"
extern "C" {
Plugin::Descriptor PLUGIN_EXPORT xpressive_plugin_descriptor = { STRINGIFY(
PLUGIN_NAME), "X-Pressive", QT_TRANSLATE_NOOP("pluginBrowser",
"Mathematical expression parser"), "Orr Dvori", 0x0100,
Plugin::Instrument, new PluginPixmapLoader("logo"), NULL, NULL };
}
/*
* nice test:
O1 -> trianglew(2t*f)*(0.5+0.5sinew(12*A1*t+0.5))+sinew(t*f)*(0.5+0.5sinew(12*A1*t))
O2 -> trianglew(2t*f)*(0.5+0.5sinew(12*A1*t))+sinew(t*f)*(0.5+0.5sinew(12*A1*t+0.5))
*/
/***********************************************************************
*
* class Expressive
*
* lmms - plugin
*
***********************************************************************/
#define GRAPH_LENGTH 4096
Expressive::Expressive(InstrumentTrack* instrument_track) :
Instrument(instrument_track, &xpressive_plugin_descriptor),
m_graphO1(-1.0f, 1.0f, 360, this),
m_graphO2(-1.0f, 1.0f, 360, this),
m_graphW1(-1.0f, 1.0f, GRAPH_LENGTH, this),
m_graphW2(-1.0f, 1.0f, GRAPH_LENGTH, this),
m_graphW3(-1.0f, 1.0f, GRAPH_LENGTH, this),
m_rawgraphW1(-1.0f, 1.0f, GRAPH_LENGTH, this),
m_rawgraphW2(-1.0f, 1.0f, GRAPH_LENGTH, this),
m_rawgraphW3(-1.0f, 1.0f, GRAPH_LENGTH, this),
m_selectedGraph(0, 0, 6, this, tr("Selected graph")),
m_parameterA1(1, -1.0f, 1.0f, 0.01f, this, tr("A1")),
m_parameterA2(1, -1.0f, 1.0f, 0.01f, this, tr("A2")),
m_parameterA3(1, -1.0f, 1.0f, 0.01f, this, tr("A3")),
m_smoothW1(0, 0.0f, 70.0f, 1.0f, this, tr("W1 smoothing")),
m_smoothW2(0, 0.0f, 70.0f, 1.0f, this, tr("W2 smoothing")),
m_smoothW3(0, 0.0f, 70.0f, 1.0f, this, tr("W3 smoothing")),
m_interpolateW1(false, this),
m_interpolateW2(false, this),
m_interpolateW3(false, this),
m_panning1( 1, -1.0f, 1.0f, 0.01f, this, tr("PAN1")),
m_panning2(-1, -1.0f, 1.0f, 0.01f, this, tr("PAN2")),
m_relTransition(50.0f, 0.0f, 500.0f, 1.0f, this, tr("REL TRANS")),
m_W1(GRAPH_LENGTH),
m_W2(GRAPH_LENGTH),
m_W3(GRAPH_LENGTH),
m_exprValid(false, this)
{
m_outputExpression[0]="sinew(integrate(f*(1+0.05sinew(12t))))*(2^(-(1.1+A2)*t)*(0.4+0.1(1+A3)+0.4sinew((2.5+2A1)t))^2)";
m_outputExpression[1]="expw(integrate(f*atan(500t)*2/pi))*0.5+0.12";
}
Expressive::~Expressive() {
}
void Expressive::saveSettings(QDomDocument & _doc, QDomElement & _this) {
// Save plugin version
_this.setAttribute("version", "0.1");
_this.setAttribute("O1", QString(m_outputExpression[0]));
_this.setAttribute("O2", QString(m_outputExpression[1]));
_this.setAttribute("W1", QString(m_wavesExpression[0]));
// Save sample shape base64-encoded
QString sampleString;
base64::encode( (const char*)m_rawgraphW1.samples(),
m_rawgraphW1.length() * sizeof(float), sampleString );
_this.setAttribute( "W1sample", sampleString );
_this.setAttribute("W2", QString(m_wavesExpression[1]));
base64::encode( (const char*)m_rawgraphW2.samples(),
m_rawgraphW2.length() * sizeof(float), sampleString );
_this.setAttribute( "W2sample", sampleString );
_this.setAttribute("W3", QString(m_wavesExpression[2]));
base64::encode( (const char*)m_rawgraphW3.samples(),
m_rawgraphW3.length() * sizeof(float), sampleString );
_this.setAttribute( "W3sample", sampleString );
m_smoothW1.saveSettings(_doc,_this,"smoothW1");
m_smoothW2.saveSettings(_doc,_this,"smoothW2");
m_smoothW3.saveSettings(_doc,_this,"smoothW3");
m_interpolateW1.saveSettings(_doc,_this,"interpolateW1");
m_interpolateW2.saveSettings(_doc,_this,"interpolateW2");
m_interpolateW3.saveSettings(_doc,_this,"interpolateW3");
m_parameterA1.saveSettings(_doc,_this,"A1");
m_parameterA2.saveSettings(_doc,_this,"A2");
m_parameterA3.saveSettings(_doc,_this,"A3");
m_panning1.saveSettings(_doc,_this,"PAN1");
m_panning2.saveSettings(_doc,_this,"PAN2");
m_relTransition.saveSettings(_doc,_this,"RELTRANS");
}
void Expressive::loadSettings(const QDomElement & _this) {
m_outputExpression[0]=_this.attribute( "O1").toLatin1();
m_outputExpression[1]=_this.attribute( "O2").toLatin1();
m_wavesExpression[0]=_this.attribute( "W1").toLatin1();
m_wavesExpression[1]=_this.attribute( "W2").toLatin1();
m_wavesExpression[2]=_this.attribute( "W3").toLatin1();
m_smoothW1.loadSettings(_this,"smoothW1");
m_smoothW2.loadSettings(_this,"smoothW2");
m_smoothW3.loadSettings(_this,"smoothW3");
m_interpolateW1.loadSettings(_this,"interpolateW1");
m_interpolateW2.loadSettings(_this,"interpolateW2");
m_interpolateW3.loadSettings(_this,"interpolateW3");
m_parameterA1.loadSettings(_this,"A1");
m_parameterA2.loadSettings(_this,"A2");
m_parameterA3.loadSettings(_this,"A3");
m_panning1.loadSettings(_this,"PAN1");
m_panning2.loadSettings(_this,"PAN2");
m_relTransition.loadSettings(_this,"RELTRANS");
int size = 0;
char * dst = 0;
base64::decode( _this.attribute( "W1sample"), &dst, &size );
m_rawgraphW1.setSamples( (float*) dst );
delete[] dst;
base64::decode( _this.attribute( "W2sample"), &dst, &size );
m_rawgraphW2.setSamples( (float*) dst );
delete[] dst;
base64::decode( _this.attribute( "W3sample"), &dst, &size );
m_rawgraphW3.setSamples( (float*) dst );
delete[] dst;
smooth(m_smoothW1.value(),&m_rawgraphW1,&m_graphW1);
smooth(m_smoothW2.value(),&m_rawgraphW2,&m_graphW2);
smooth(m_smoothW3.value(),&m_rawgraphW3,&m_graphW3);
m_W1.copyFrom(&m_graphW1);
m_W2.copyFrom(&m_graphW2);
m_W3.copyFrom(&m_graphW3);
}
QString Expressive::nodeName() const {
return (xpressive_plugin_descriptor.name);
}
void Expressive::playNote(NotePlayHandle* nph, sampleFrame* working_buffer) {
m_A1=m_parameterA1.value();
m_A2=m_parameterA2.value();
m_A3=m_parameterA3.value();
if (nph->totalFramesPlayed() == 0 || nph->m_pluginData == NULL) {
ExprFront *exprO1 = new ExprFront(m_outputExpression[0].constData());
ExprFront *exprO2 = new ExprFront(m_outputExpression[1].constData());
auto init_expression_step1 = [this, nph](ExprFront* e) {
e->add_constant("key", nph->key());
e->add_constant("bnote", nph->instrumentTrack()->baseNote());
e->add_constant("srate", Engine::mixer()->processingSampleRate());
e->add_constant("v", nph->getVolume() / 255.0);
e->add_constant("tempo", Engine::getSong()->getTempo());
e->add_variable("A1", m_A1);
e->add_variable("A2", m_A2);
e->add_variable("A3", m_A3);
};
init_expression_step1(exprO1);
init_expression_step1(exprO2);
m_W1.setInterpolate(m_interpolateW1.value());
m_W2.setInterpolate(m_interpolateW2.value());
m_W3.setInterpolate(m_interpolateW3.value());
nph->m_pluginData = new ExprSynth(&m_W1, &m_W2, &m_W3, exprO1, exprO2, nph,
Engine::mixer()->processingSampleRate(), &m_panning1, &m_panning2, m_relTransition.value());
}
ExprSynth *ps = static_cast<ExprSynth*>(nph->m_pluginData);
const fpp_t frames = nph->framesLeftForCurrentPeriod();
const f_cnt_t offset = nph->noteOffset();
ps->renderOutput(frames, working_buffer + offset);
instrumentTrack()->processAudioBuffer(working_buffer, frames + offset, nph);
}
void Expressive::deleteNotePluginData(NotePlayHandle* nph) {
delete static_cast<ExprSynth *>(nph->m_pluginData);
}
PluginView * Expressive::instantiateView(QWidget* parent) {
return (new expressiveView(this, parent));
}
class expressiveKnob: public Knob {
public:
void setStyle()
{
setFixedSize(29, 29);
setCenterPointX(14.5);
setCenterPointY(14.5);
setInnerRadius(4);
setOuterRadius(9);
setOuterColor(QColor(0x519fff));
setTotalAngle(300.0);
setLineWidth(3);
}
expressiveKnob(QWidget * _parent, const QString & _name) :
Knob(knobStyled, _parent,_name) {
setStyle();
}
expressiveKnob(QWidget * _parent) :
Knob(knobStyled, _parent) {
setStyle();
}
};
expressiveView::expressiveView(Instrument * _instrument, QWidget * _parent) :
InstrumentView(_instrument, _parent)
{
const int COL_KNOBS = 194;
const int ROW_KNOBSA1 = 26;
const int ROW_KNOBSA2 = 26 + 32;
const int ROW_KNOBSA3 = 26 + 64;
const int ROW_KNOBSP1 = 126;
const int ROW_KNOBSP2 = 126 + 32;
const int ROW_KNOBREL = 126 + 64;
const int ROW_WAVEBTN = 234;
setAutoFillBackground(true);
QPalette pal;
pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork"));
setPalette(pal);
m_graph = new Graph(this, Graph::LinearStyle, 180, 81);
m_graph->move(9, 27);
m_graph->setAutoFillBackground(true);
m_graph->setGraphColor(QColor(255, 255, 255));
m_graph->setEnabled(false);
/*ToolTip::add(m_graph, tr("Draw your own waveform here "
"by dragging your mouse on this graph."));*/
pal = QPalette();
pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("wavegraph"));
m_graph->setPalette(pal);
PixmapButton * m_w1Btn;
PixmapButton * m_w2Btn;
PixmapButton * m_w3Btn;
PixmapButton * m_o1Btn;
PixmapButton * m_o2Btn;
PixmapButton * m_helpBtn;
m_w1Btn = new PixmapButton(this, NULL);
m_w1Btn->move(9, 111);
m_w1Btn->setActiveGraphic(PLUGIN_NAME::getIconPixmap("w1_active"));
m_w1Btn->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("w1_inactive"));
ToolTip::add(m_w1Btn, tr("Select oscillator W1"));
m_w2Btn = new PixmapButton(this, NULL);
m_w2Btn->move(32, 111);
m_w2Btn->setActiveGraphic(PLUGIN_NAME::getIconPixmap("w2_active"));
m_w2Btn->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("w2_inactive"));
ToolTip::add(m_w2Btn, tr("Select oscillator W2"));
m_w3Btn = new PixmapButton(this, NULL);
m_w3Btn->move(55, 111);
m_w3Btn->setActiveGraphic(PLUGIN_NAME::getIconPixmap("w3_active"));
m_w3Btn->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("w3_inactive"));
ToolTip::add(m_w3Btn, tr("Select oscillator W3"));
m_o1Btn = new PixmapButton(this, NULL);
m_o1Btn->move(85, 111);
m_o1Btn->setActiveGraphic(PLUGIN_NAME::getIconPixmap("o1_active"));
m_o1Btn->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("o1_inactive"));
ToolTip::add(m_o1Btn, tr("Select OUTPUT 1"));
m_o2Btn = new PixmapButton(this, NULL);
m_o2Btn->move(107, 111);
m_o2Btn->setActiveGraphic(PLUGIN_NAME::getIconPixmap("o2_active"));
m_o2Btn->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("o2_inactive"));
ToolTip::add(m_o2Btn, tr("Select OUTPUT 2"));
m_helpBtn = new PixmapButton(this, NULL);
m_helpBtn->move(139, 111);
m_helpBtn->setActiveGraphic(PLUGIN_NAME::getIconPixmap("help_active"));
m_helpBtn->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("help_inactive"));
ToolTip::add(m_helpBtn, tr("Open help window"));
m_selectedGraphGroup = new automatableButtonGroup(this);
m_selectedGraphGroup->addButton(m_w1Btn);
m_selectedGraphGroup->addButton(m_w2Btn);
m_selectedGraphGroup->addButton(m_w3Btn);
m_selectedGraphGroup->addButton(m_o1Btn);
m_selectedGraphGroup->addButton(m_o2Btn);
Expressive *e = castModel<Expressive>();
m_selectedGraphGroup->setModel(&e->selectedGraph());
m_sinWaveBtn = new PixmapButton(this, tr("Sine wave"));
m_sinWaveBtn->move(10, ROW_WAVEBTN);
m_sinWaveBtn->setActiveGraphic(embed::getIconPixmap("sin_wave_active"));
m_sinWaveBtn->setInactiveGraphic(embed::getIconPixmap("sin_wave_inactive"));
ToolTip::add(m_sinWaveBtn, tr("Click for a sine-wave."));
m_moogWaveBtn = new PixmapButton(this, tr("Moog-Saw wave"));
m_moogWaveBtn->move(10, ROW_WAVEBTN-14);
m_moogWaveBtn->setActiveGraphic(
embed::getIconPixmap( "moog_saw_wave_active" ) );
m_moogWaveBtn->setInactiveGraphic(embed::getIconPixmap("moog_saw_wave_inactive"));
ToolTip::add(m_moogWaveBtn, tr("Click for a Moog-Saw-wave."));
m_expWaveBtn = new PixmapButton(this, tr("Exponential wave"));
m_expWaveBtn->move(10 +14, ROW_WAVEBTN-14);
m_expWaveBtn->setActiveGraphic(embed::getIconPixmap( "exp_wave_active" ) );
m_expWaveBtn->setInactiveGraphic(embed::getIconPixmap( "exp_wave_inactive" ) );
ToolTip::add(m_expWaveBtn, tr("Click for an exponential wave."));
m_sawWaveBtn = new PixmapButton(this, tr("Saw wave"));
m_sawWaveBtn->move(10 + 14 * 2, ROW_WAVEBTN-14);
m_sawWaveBtn->setActiveGraphic(embed::getIconPixmap("saw_wave_active"));
m_sawWaveBtn->setInactiveGraphic(embed::getIconPixmap("saw_wave_inactive"));
ToolTip::add(m_sawWaveBtn, tr("Click here for a saw-wave."));
m_usrWaveBtn = new PixmapButton(this, tr("User defined wave"));
m_usrWaveBtn->move(10 + 14 * 3, ROW_WAVEBTN-14);
m_usrWaveBtn->setActiveGraphic(embed::getIconPixmap("usr_wave_active"));
m_usrWaveBtn->setInactiveGraphic(embed::getIconPixmap("usr_wave_inactive"));
ToolTip::add(m_usrWaveBtn, tr("Click here for a user-defined shape."));
m_triangleWaveBtn = new PixmapButton(this, tr("Triangle wave"));
m_triangleWaveBtn->move(10 + 14, ROW_WAVEBTN);
m_triangleWaveBtn->setActiveGraphic(
embed::getIconPixmap("triangle_wave_active"));
m_triangleWaveBtn->setInactiveGraphic(
embed::getIconPixmap("triangle_wave_inactive"));
ToolTip::add(m_triangleWaveBtn, tr("Click here for a triangle-wave."));
m_sqrWaveBtn = new PixmapButton(this, tr("Square wave"));
m_sqrWaveBtn->move(10 + 14 * 2, ROW_WAVEBTN);
m_sqrWaveBtn->setActiveGraphic(embed::getIconPixmap("square_wave_active"));
m_sqrWaveBtn->setInactiveGraphic(
embed::getIconPixmap("square_wave_inactive"));
ToolTip::add(m_sqrWaveBtn, tr("Click here for a square-wave."));
m_whiteNoiseWaveBtn = new PixmapButton(this, tr("White noise wave"));
m_whiteNoiseWaveBtn->move(10 + 14 * 3, ROW_WAVEBTN);
m_whiteNoiseWaveBtn->setActiveGraphic(
embed::getIconPixmap("white_noise_wave_active"));
m_whiteNoiseWaveBtn->setInactiveGraphic(
embed::getIconPixmap("white_noise_wave_inactive"));
ToolTip::add(m_whiteNoiseWaveBtn, tr("Click here for white-noise."));
m_waveInterpolate = new LedCheckBox("Interpolate", this, tr("WaveInterpolate"),
LedCheckBox::Green);
m_waveInterpolate->move(120, 230);
m_expressionValidToggle = new LedCheckBox("", this, tr("ExpressionValid"),
LedCheckBox::Red);
m_expressionValidToggle->move(174, 216);
m_expressionValidToggle->setEnabled( false );
m_expressionEditor = new QPlainTextEdit(this);
m_expressionEditor->move(9, 128);
m_expressionEditor->resize(180, 90);
m_generalPurposeKnob[0] = new expressiveKnob(this,"A1");
m_generalPurposeKnob[0]->setHintText(tr("General purpose 1:"), "");
m_generalPurposeKnob[0]->move(COL_KNOBS, ROW_KNOBSA1);
m_generalPurposeKnob[1] = new expressiveKnob(this,"A2");
m_generalPurposeKnob[1]->setHintText(tr("General purpose 2:"), "");
m_generalPurposeKnob[1]->move(COL_KNOBS, ROW_KNOBSA2);
m_generalPurposeKnob[2] = new expressiveKnob(this,"A3");
m_generalPurposeKnob[2]->setHintText(tr("General purpose 3:"), "");
m_generalPurposeKnob[2]->move(COL_KNOBS, ROW_KNOBSA3);
m_panningKnob[0] = new expressiveKnob(this,"O1 panning");
m_panningKnob[0]->setHintText(tr("O1 panning:"), "");
m_panningKnob[0]->move(COL_KNOBS, ROW_KNOBSP1);
m_panningKnob[1] = new expressiveKnob(this,"O2 panning");
m_panningKnob[1]->setHintText(tr("O2 panning:"), "");
m_panningKnob[1]->move(COL_KNOBS, ROW_KNOBSP2);
m_relKnob = new expressiveKnob(this,"Release transition");
m_relKnob->setHintText(tr("Release transition:"), "ms");
m_relKnob->move(COL_KNOBS, ROW_KNOBREL);
m_smoothKnob=new Knob(this,"Smoothness");
m_smoothKnob->setHintText(tr("Smoothness"), "");
m_smoothKnob->move(80, 220);
connect(m_generalPurposeKnob[0], SIGNAL(sliderMoved(float)), this,
SLOT(expressionChanged()));
connect(m_generalPurposeKnob[1], SIGNAL(sliderMoved(float)), this,
SLOT(expressionChanged()));
connect(m_generalPurposeKnob[2], SIGNAL(sliderMoved(float)), this,
SLOT(expressionChanged()));
connect(m_expressionEditor, SIGNAL(textChanged()), this,
SLOT(expressionChanged()));
connect(m_smoothKnob, SIGNAL(sliderMoved(float)), this,
SLOT(smoothChanged()));
connect(m_graph, SIGNAL(drawn()), this,
SLOT(graphDrawn()));
connect(m_sinWaveBtn, SIGNAL(clicked()), this, SLOT(sinWaveClicked()));
connect(m_triangleWaveBtn, SIGNAL(clicked()), this,
SLOT(triangleWaveClicked()));
connect(m_expWaveBtn, SIGNAL(clicked()), this, SLOT(expWaveClicked()));
connect(m_moogWaveBtn, SIGNAL(clicked()), this,
SLOT(moogSawWaveClicked()));
connect(m_sawWaveBtn, SIGNAL(clicked()), this, SLOT(sawWaveClicked()));
connect(m_sqrWaveBtn, SIGNAL(clicked()), this, SLOT(sqrWaveClicked()));
connect(m_whiteNoiseWaveBtn, SIGNAL(clicked()), this,
SLOT(noiseWaveClicked()));
connect(m_usrWaveBtn, SIGNAL(clicked()), this, SLOT(usrWaveClicked()));
connect(m_helpBtn, SIGNAL(clicked()), this, SLOT(helpClicked()));
connect(m_w1Btn, SIGNAL(clicked()), this, SLOT(updateLayout()));
connect(m_w2Btn, SIGNAL(clicked()), this, SLOT(updateLayout()));
connect(m_w3Btn, SIGNAL(clicked()), this, SLOT(updateLayout()));
connect(m_o1Btn, SIGNAL(clicked()), this, SLOT(updateLayout()));
connect(m_o2Btn, SIGNAL(clicked()), this, SLOT(updateLayout()));
updateLayout();
}
expressiveView::~expressiveView()
{
}
void expressiveView::expressionChanged() {
Expressive * e = castModel<Expressive>();
QByteArray text = m_expressionEditor->toPlainText().toLatin1();
switch (m_selectedGraphGroup->model()->value()) {
case W1_EXPR:
e->wavesExpression(0) = text;
break;
case W2_EXPR:
e->wavesExpression(1) = text;
break;
case W3_EXPR:
e->wavesExpression(2) = text;
break;
case O1_EXPR:
e->outputExpression(0) = text;
break;
case O2_EXPR:
e->outputExpression(1) = text;
break;
}
if (m_wave_expr)
m_graph->setEnabled(m_smoothKnob->model()->value() == 0 && text.size() == 0);
if (text.size()>0)
{
ExprFront expr(text.constData());
float t=0;
const float f=10,key=5,v=0.5;
unsigned int i;
const unsigned int sample_rate=m_raw_graph->length();
expr.add_variable("t", t);
if (m_output_expr)
{
expr.add_constant("f", f);
expr.add_constant("key", key);
expr.add_constant("rel", 0);
expr.add_constant("trel", 0);
expr.add_constant("bnote",e->instrumentTrack()->baseNote());
expr.add_constant("v", v);
expr.add_constant("tempo", Engine::getSong()->getTempo());
expr.add_constant("A1", e->parameterA1().value());
expr.add_constant("A2", e->parameterA2().value());
expr.add_constant("A3", e->parameterA3().value());
expr.add_cyclic_vector("W1",e->graphW1().samples(),e->graphW1().length());
expr.add_cyclic_vector("W2",e->graphW2().samples(),e->graphW2().length());
expr.add_cyclic_vector("W3",e->graphW3().samples(),e->graphW3().length());
}
expr.setIntegrate(&i,sample_rate);
expr.add_constant("srate",sample_rate);
const bool parse_ok=expr.compile();
if (parse_ok) {
e->exprValid().setValue(0);
const int length = m_raw_graph->length();
float * const samples = new float[length];
for (i = 0; i < length; i++) {
t = i / (float) length;
samples[i] = expr.evaluate();
if (std::isinf(samples[i]) != 0 || std::isnan(samples[i]) != 0)
samples[i] = 0;
}
m_raw_graph->setSamples(samples);
delete[] samples;
if (m_wave_expr)
{
smoothChanged();
}
else
{
Engine::getSong()->setModified();
}
}
else
{
e->exprValid().setValue(1);
if (m_output_expr)
m_raw_graph->clear();
}
}
else
{
e->exprValid().setValue(0);
if (m_output_expr)
m_raw_graph->clear();
}
}
void Expressive::smooth(float smoothness,const graphModel * in,graphModel * out)
{
out->setSamples(in->samples());
if (smoothness>0)
{
const int guass_size = (int)(smoothness * 5) | 1;
const int guass_center = guass_size/2;
const float delta = smoothness;
const float a= 1.0f / (sqrtf(2.0f * F_PI) * delta);
float * const guassian = new float [guass_size];
float sum = 0.0f;
float temp = 0.0f;
int i;
for (i = 0; i < guass_size; i++ )
{
temp = (i - guass_center) / delta;
sum += guassian[i] = a * powf(F_E, -0.5f * temp * temp);
}
for (i = 0; i < guass_size; i++ )
{
guassian[i] = guassian[i] / sum;
}
out->convolve(guassian, guass_size, guass_center);
delete [] guassian;
}
}
void expressiveView::smoothChanged()
{
Expressive * e = castModel<Expressive>();
float smoothness=0;
switch (m_selectedGraphGroup->model()->value()) {
case W1_EXPR:
smoothness=e->smoothW1().value();
break;
case W2_EXPR:
smoothness=e->smoothW2().value();
break;
case W3_EXPR:
smoothness=e->smoothW3().value();
break;
}
Expressive::smooth(smoothness,m_raw_graph,m_graph->model());
switch (m_selectedGraphGroup->model()->value()) {
case W1_EXPR:
e->W1().copyFrom(m_graph->model());
break;
case W2_EXPR:
e->W2().copyFrom(m_graph->model());
break;
case W3_EXPR:
e->W3().copyFrom(m_graph->model());
break;
}
Engine::getSong()->setModified();
m_graph->setEnabled(m_smoothKnob->model()->value() == 0 && m_expressionEditor->toPlainText().size() == 0);
}
void expressiveView::graphDrawn()
{
m_raw_graph->setSamples(m_graph->model()->samples());
Expressive * e = castModel<Expressive>();
switch (m_selectedGraphGroup->model()->value()) {
case W1_EXPR:
e->W1().copyFrom(m_graph->model());
break;
case W2_EXPR:
e->W2().copyFrom(m_graph->model());
break;
case W3_EXPR:
e->W3().copyFrom(m_graph->model());
break;
}
Engine::getSong()->setModified();
}
void expressiveView::modelChanged() {
Expressive * b = castModel<Expressive>();
m_expressionValidToggle->setModel( &b->exprValid() );
m_generalPurposeKnob[0]->setModel( &b->parameterA1() );
m_generalPurposeKnob[1]->setModel( &b->parameterA2() );
m_generalPurposeKnob[2]->setModel( &b->parameterA3() );
m_panningKnob[0]->setModel( &b->panning1() );
m_panningKnob[1]->setModel( &b->panning2() );
m_relKnob->setModel( &b->relTransition() );
m_selectedGraphGroup->setModel( &b->selectedGraph() );
updateLayout();
}
void expressiveView::updateLayout() {
Expressive * e = castModel<Expressive>();
m_output_expr=false;
m_wave_expr=false;
switch (m_selectedGraphGroup->model()->value()) {
case W1_EXPR:
m_wave_expr=true;
m_graph->setModel(&e->graphW1(), true);
m_raw_graph=&(e->rawgraphW1());
m_expressionEditor->setPlainText(e->wavesExpression(0));
m_smoothKnob->setModel(&e->smoothW1());
m_graph->setEnabled((e->smoothW1().value() == 0 && e->wavesExpression(0).size() == 0));
m_waveInterpolate->setModel(&e->interpolateW1());
m_smoothKnob->show();
m_usrWaveBtn->show();
m_waveInterpolate->show();
break;
case W2_EXPR:
m_wave_expr=true;
m_graph->setModel(&e->graphW2(), true);
m_raw_graph=&(e->rawgraphW2());
m_expressionEditor->setPlainText(e->wavesExpression(1));
m_smoothKnob->setModel(&e->smoothW2());
m_graph->setEnabled((e->smoothW2().value() == 0 && e->wavesExpression(1).size() == 0));
m_waveInterpolate->setModel(&e->interpolateW2());
m_smoothKnob->show();
m_usrWaveBtn->show();
m_waveInterpolate->show();
break;
case W3_EXPR:
m_wave_expr=true;
m_graph->setModel(&e->graphW3(), true);
m_raw_graph=&(e->rawgraphW3());
m_expressionEditor->setPlainText(e->wavesExpression(2));
m_smoothKnob->setModel(&e->smoothW3());
m_graph->setEnabled((e->smoothW3().value() == 0 && e->wavesExpression(2).size() == 0));
m_waveInterpolate->setModel(&e->interpolateW3());
m_smoothKnob->show();
m_usrWaveBtn->show();
m_waveInterpolate->show();
break;
case O1_EXPR:
m_output_expr=true;
m_graph->setModel(&e->graphO1(), true);
m_raw_graph=&(e->graphO1());
m_expressionEditor->setPlainText(e->outputExpression(0));
m_smoothKnob->hide();
m_graph->setEnabled(false);
m_usrWaveBtn->hide();
m_waveInterpolate->hide();
break;
case O2_EXPR:
m_output_expr=true;
m_graph->setModel(&e->graphO2(), true);
m_raw_graph=&(e->graphO2());
m_expressionEditor->setPlainText(e->outputExpression(1));
m_smoothKnob->hide();
m_graph->setEnabled(false);
m_usrWaveBtn->hide();
m_waveInterpolate->hide();
break;
}
}
void expressiveView::sinWaveClicked() {
if (m_output_expr)
m_expressionEditor->appendPlainText("sinew(t*f)");
else
m_expressionEditor->appendPlainText("sinew(t)");
Engine::getSong()->setModified();
}
void expressiveView::triangleWaveClicked() {
if (m_output_expr)
m_expressionEditor->appendPlainText("trianglew(t*f)");
else
m_expressionEditor->appendPlainText("trianglew(t)");
Engine::getSong()->setModified();
}
void expressiveView::sawWaveClicked() {
if (m_output_expr)
m_expressionEditor->appendPlainText("saww(t*f)");
else
m_expressionEditor->appendPlainText("saww(t)");
Engine::getSong()->setModified();
}
void expressiveView::sqrWaveClicked() {
if (m_output_expr)
m_expressionEditor->appendPlainText("squarew(t*f)");
else
m_expressionEditor->appendPlainText("squarew(t)");
Engine::getSong()->setModified();
}
void expressiveView::noiseWaveClicked() {
m_expressionEditor->appendPlainText("rand");
Engine::getSong()->setModified();
}
void expressiveView::moogSawWaveClicked()
{
if (m_output_expr)
m_expressionEditor->appendPlainText("moogsaww(t*f)");
else
m_expressionEditor->appendPlainText("moogsaww(t)");
Engine::getSong()->setModified();
}
void expressiveView::expWaveClicked()
{
if (m_output_expr)
m_expressionEditor->appendPlainText("expw(t*f)");
else
m_expressionEditor->appendPlainText("expw(t)");
Engine::getSong()->setModified();
}
void expressiveView::usrWaveClicked() {
m_expressionEditor->setPlainText("");
QString fileName = m_raw_graph->setWaveToUser();
smoothChanged();
Engine::getSong()->setModified();
}
expressiveHelpView* expressiveHelpView::s_instance=0;
QString expressiveHelpView::s_helpText=
"<b>O1, O2</b> - Two output waves. Panning is controled by PN1 and PN2.<br>"
"<b>W1, W2, W3</b> - Wave samples evaluated by expression. In these samples, t variable ranges [0,1).<br>"
"These waves can be used as functions inside the output waves (O1, O2). The wave period is 1.<br>"
"<h4>Available variables:</h4><br>"
"<b>t</b> - Time in seconds.<br>"
"<b>f</b> - Note's pitched frequency. Available only in the output expressions.<br>"
"<b>key</b> - Note's keyboard key. 0 denotes C0, 48 denotes C4, 96 denotes C8. Available only in the output expressions.<br>"
"<b>bnote</b> - Base note. By default it is 57 which means A5, unless you change it.<br>"
"<b>srate</b> - Sample rate. In wave expression it returns the wave's number of samples.<br>"
"<b>tempo</b> - Song's Tempo. Available only in the output expressions.<br>"
"<b>v</b> - Note's volume. Note that the output is already multiplied by the volume. Available only in the output expressions.<br>"
"<b>rel</b> - Gives 0.0 while the key is holded, and 1.0 after the key release. Available only in the output expressions.<br>"
"<b>trel</b> - Time after release. While the note is holded, it gives 0.0. Afterwards, it start counting seconds.<br>"
"The time it takes to shift from 0.0 to 1.0 after key release is determined by the REL knob<br>"
"<b>A1, A2, A3</b> - General purpose knobs. You can reference them only in O1 and O2. In range [-1,1].<br>"
"<h4>Available functions:</h4><br>"
"<b>W1, W2, W3</b> - As mentioned before. You can reference them only in O1 and O2.<br>"
"<b>cent(x)</b> - Gives pow(2,x/1200), so you can multiply it with the f variable to pitch the frequency.<br>"
"100 cents equals one semitone<br>"
"<b>semitone(x)</b> - Gives pow(2,x/12), so you can multiply it with the f variable to pitch the frequency.<br>"
"<b>last(n)</b> - Gives you the last n'th evaluated sample. The argument n must be in the range [1,500], or else, it will return 0.<br>"
"<b>integrate(x)</b> - Integrates x by delta t (It sums values and divides them by sample rate).<br>"
"If you use notes with automated frequency, you should use:<br>"
"sinew(integrate(f)) instead of sinew(t*f)<br>"
"<b>randv(x)</b> - A random vector. Each cell is reference by an integer index in the range [0,2^31]<br>"
"Each evaluation of an expression results in different random vector.<br>"
"Although, it remains consistent in the lifetime of a single wave.<br>"
"If you want a single random values you can use randv(0),randv(1)... <br>"
"and every reference to randv(a) will give you the same value."
"If you want a random wave you can use randv(t*srate).<br>"
"Each random value is in the range [-1,1).<br>"
"<b>sinew(x)</b> - A sine wave with period of 1 (In contrast to real sine wave which have a period of 2*pi).<br>"
"<b>trianglew(x)</b> - A triangle wave with period of 1.<br>"
"<b>squarew(x)</b> - A square wave with period of 1.<br>"
"<b>saww(x)</b> - A saw wave with period of 1.<br>"
"<b>clamp(min_val,x,max_val)</b> - If x is in range of (min_val,max_val) it returns x. Otherwise if it's greater than max_val it returns max_val, else returns min_val.<br>"
"<b>abs, sin, cos, tan, cot, asin, acos, atan, atan2, sinh, cosh, tanh, asinh, acosh, atanh, sinc, "
"hypot, exp, log, log2, log10, logn, pow, sqrt, min, max, floor, ceil, round, trunc, frac, "
"avg, sgn, mod, etc. are also available.</b><br>"
"<b>Operands + - * / % ^ &gt; &lt; &gt;= &lt;= == != &amp; | are also available.</b><br>"
"<b>Amplitude Modulation</b> - W1(t*f)*(1+W2(t*f))<br>"
"<b>Ring Modulation</b> - W1(t * f)*W2(t * f)<br>"
"<b>Mix Modulation</b> - 0.5*( W1(t * f) + W2(t * f) )<br>"
"<b>Frequency Modulation</b> - [vol1]*W1( integrate( f + srate*[vol2]*W2( integrate(f) ) ) )<br>"
"<b>Phase Modulation</b> - [vol1]*W1( integrate(f) + [vol2]*W2( integrate(f) ) )<br>"
;
expressiveHelpView::expressiveHelpView():QTextEdit(s_helpText)
{
setWindowTitle ( "X-Pressive Help" );
setTextInteractionFlags ( Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse );
gui->mainWindow()->addWindowedWidget( this );
parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false );
parentWidget()->setWindowIcon( PLUGIN_NAME::getIconPixmap( "logo" ) );
parentWidget()->setFixedSize( 300, 500);
}
void expressiveView::helpClicked() {
expressiveHelpView::getInstance()->show();
}
__attribute__((destructor)) static void module_destroy()
{
expressiveHelpView::finalize();
}
extern "C" {
// necessary for getting instance out of shared lib
Plugin * PLUGIN_EXPORT lmms_plugin_main(Model *, void * _data) {
return (new Expressive(static_cast<InstrumentTrack *>(_data)));
}
}

View File

@@ -0,0 +1,227 @@
/*
* expressive_plugin.h - instrument which uses a mathematical formula
*
* Copyright (c) 2016-2017 Orr Dvori
*
* 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 EXPRESSIVE_PLUGIN_H
#define EXPRESSIVE_PLUGIN_H
#include <QPlainTextEdit>
#include "Graph.h"
#include "Instrument.h"
#include "InstrumentView.h"
#include "Knob.h"
#include "LedCheckbox.h"
#include "PixmapButton.h"
#include "exprsynth.h"
class oscillator;
class expressiveView;
const int W1_EXPR = 0;
const int W2_EXPR = 1;
const int W3_EXPR = 2;
const int O1_EXPR = 3;
const int O2_EXPR = 4;
const int NUM_EXPRS = 5;
class ExprFront;
class SubWindow;
class Expressive : public Instrument
{
Q_OBJECT
public:
Expressive(InstrumentTrack* instrument_track );
virtual ~Expressive();
virtual void playNote(NotePlayHandle* nph,
sampleFrame* working_buffer );
virtual void deleteNotePluginData( NotePlayHandle* nph );
virtual void saveSettings( QDomDocument& _doc,
QDomElement& _this );
virtual void loadSettings( const QDomElement& _this );
virtual QString nodeName() const;
virtual PluginView* instantiateView( QWidget * parent );
graphModel& graphO1() { return m_graphO1; }
graphModel& graphO2() { return m_graphO2; }
graphModel& graphW1() { return m_graphW1; }
graphModel& graphW2() { return m_graphW2; }
graphModel& graphW3() { return m_graphW3; }
graphModel& rawgraphW1() { return m_rawgraphW1; }
graphModel& rawgraphW2() { return m_rawgraphW2; }
graphModel& rawgraphW3() { return m_rawgraphW3; }
IntModel& selectedGraph() { return m_selectedGraph; }
QByteArray& wavesExpression(int i) { return m_wavesExpression[i]; }
QByteArray& outputExpression(int i) { return m_outputExpression[i]; }
FloatModel& parameterA1() { return m_parameterA1; }
FloatModel& parameterA2() { return m_parameterA2; }
FloatModel& parameterA3() { return m_parameterA3; }
FloatModel& smoothW1() { return m_smoothW1; }
FloatModel& smoothW2() { return m_smoothW2; }
FloatModel& smoothW3() { return m_smoothW3; }
BoolModel& interpolateW1() { return m_interpolateW1; }
BoolModel& interpolateW2() { return m_interpolateW2; }
BoolModel& interpolateW3() { return m_interpolateW3; }
FloatModel& panning1() { return m_panning1; }
FloatModel& panning2() { return m_panning2; }
FloatModel& relTransition() { return m_relTransition; }
WaveSample& W1() { return m_W1; }
WaveSample& W2() { return m_W2; }
WaveSample& W3() { return m_W3; }
BoolModel& exprValid() { return m_exprValid; }
static void smooth(float smoothness,const graphModel* in,graphModel* out);
protected:
protected slots:
private:
graphModel m_graphO1;
graphModel m_graphO2;
graphModel m_graphW1;
graphModel m_graphW2;
graphModel m_graphW3;
graphModel m_rawgraphW1;
graphModel m_rawgraphW2;
graphModel m_rawgraphW3;
IntModel m_selectedGraph;
QByteArray m_wavesExpression[3];
QByteArray m_outputExpression[2];
FloatModel m_parameterA1;
FloatModel m_parameterA2;
FloatModel m_parameterA3;
FloatModel m_smoothW1;
FloatModel m_smoothW2;
FloatModel m_smoothW3;
BoolModel m_interpolateW1;
BoolModel m_interpolateW2;
BoolModel m_interpolateW3;
FloatModel m_panning1;
FloatModel m_panning2;
FloatModel m_relTransition;
float m_A1,m_A2,m_A3;
WaveSample m_W1, m_W2, m_W3;
BoolModel m_exprValid;
} ;
class expressiveView : public InstrumentView
{
Q_OBJECT
public:
expressiveView( Instrument* _instrument,
QWidget* _parent );
virtual ~expressiveView();
protected:
protected slots:
void updateLayout();
void sinWaveClicked();
void triangleWaveClicked();
void sqrWaveClicked();
void sawWaveClicked();
void noiseWaveClicked();
void moogSawWaveClicked();
void expWaveClicked();
void usrWaveClicked();
void helpClicked();
void expressionChanged( );
void smoothChanged( );
void graphDrawn( );
private:
virtual void modelChanged();
Knob *m_generalPurposeKnob[3];
Knob *m_panningKnob[2];
Knob *m_relKnob;
Knob *m_smoothKnob;
QPlainTextEdit * m_expressionEditor;
automatableButtonGroup *m_selectedGraphGroup;
PixmapButton *m_w1Btn;
PixmapButton *m_w2Btn;
PixmapButton *m_w3Btn;
PixmapButton *m_o1Btn;
PixmapButton *m_o2Btn;
PixmapButton *m_sinWaveBtn;
PixmapButton *m_triangleWaveBtn;
PixmapButton *m_sqrWaveBtn;
PixmapButton *m_sawWaveBtn;
PixmapButton *m_whiteNoiseWaveBtn;
PixmapButton *m_usrWaveBtn;
PixmapButton *m_moogWaveBtn;
PixmapButton *m_expWaveBtn;
static QPixmap *s_artwork;
Graph *m_graph;
graphModel *m_raw_graph;
LedCheckBox *m_expressionValidToggle;
LedCheckBox *m_waveInterpolate;
bool m_output_expr;
bool m_wave_expr;
} ;
class expressiveHelpView: public QTextEdit
{
Q_OBJECT
public:
static expressiveHelpView* getInstance()
{
if (!s_instance)
{
s_instance = new expressiveHelpView();
}
return s_instance;
}
static void finalize()
{
if (s_instance) { delete s_instance; }
}
private:
expressiveHelpView();
static expressiveHelpView *s_instance;
static QString s_helpText;
};
#endif

View File

@@ -0,0 +1,792 @@
/*
* exprfront.cpp - implementation of a Frontend to ExprTk
*
* Copyright (c) 2016-2017 Orr Dvori
*
* 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 "exprsynth.h"
#include <string>
#include <vector>
#include <math.h>
#include <cstdlib>
#include <random>
#include "expressive_plugin.h"
#include "interpolation.h"
#include "lmms_math.h"
#include "NotePlayHandle.h"
#include "exprtk.hpp"
#define WARN_EXPRTK qWarning("ExprTk exception")
typedef exprtk::symbol_table<float> symbol_table_t;
typedef exprtk::expression<float> expression_t;
typedef exprtk::parser<float> parser_t;
template <typename T,typename Functor,bool optimize>
struct freefunc0 : public exprtk::ifunction<T>
{
using exprtk::ifunction<T>::operator();
freefunc0() : exprtk::ifunction<T>(0) {
if (optimize) { exprtk::disable_has_side_effects(*this); }
}
inline T operator()()
{ return Functor::process(); }
};
template <typename T,typename Functor,bool optimize>
struct freefunc1 : public exprtk::ifunction<T>
{
using exprtk::ifunction<T>::operator();
freefunc1() : exprtk::ifunction<T>(1) {
if (optimize) { exprtk::disable_has_side_effects(*this); }
}
inline T operator()(const T& x)
{ return Functor::process(x); }
};
template <typename T>
struct IntegrateFunction : public exprtk::ifunction<T>
{
using exprtk::ifunction<T>::operator();
virtual ~IntegrateFunction()
{
delete [] m_counters;
}
IntegrateFunction(const unsigned int* frame, unsigned int sample_rate,unsigned int max_counters) :
exprtk::ifunction<T>(1),
m_frame(frame),
m_sample_rate(sample_rate),
m_max_counters(max_counters),
m_nCounters(0),
m_nCountersCalls(0),
m_cc(0)
{
m_counters=new double[max_counters];
clearArray(m_counters,max_counters);
}
inline T operator()(const T& x)
{
if (*m_frame == 0)
{
++m_nCountersCalls;
if (m_nCountersCalls > m_max_counters)
{
return 0;
}
m_cc = m_nCounters;
++m_nCounters;
}
T res = 0;
if (m_cc < m_nCounters)
{
res = m_counters[m_cc];
m_counters[m_cc] += x;
}
m_cc = (m_cc + 1) % m_nCountersCalls;
return res / m_sample_rate;
}
const unsigned int* const m_frame;
const unsigned int m_sample_rate;
const unsigned int m_max_counters;
unsigned int m_nCounters;
unsigned int m_nCountersCalls;
unsigned int m_cc;
double *m_counters;
};
template <typename T>
struct LastSampleFunction : public exprtk::ifunction<T>
{
using exprtk::ifunction<T>::operator();
virtual ~LastSampleFunction()
{
delete [] m_samples;
}
LastSampleFunction(unsigned int history_size) :
exprtk::ifunction<T>(1),
m_history_size(history_size),
m_pivot_last(history_size - 1)
{
m_samples = new T[history_size];
clearArray(m_samples, history_size);
}
inline T operator()(const T& x)
{
if (!std::isnan(x) && !std::isinf(x))
{
const int ix=(int)x;
if (ix>=1 && ix<=m_history_size)
{
return m_samples[(ix + m_pivot_last) % m_history_size];
}
}
return 0;
}
void setLastSample(const T& sample)
{
if (!std::isnan(sample) && !std::isinf(sample))
{
m_samples[m_pivot_last] = sample;
}
if (m_pivot_last == 0)
{
m_pivot_last = m_history_size - 1;
}
else {
--m_pivot_last;
}
}
unsigned int m_history_size;
unsigned int m_pivot_last;
T *m_samples;
};
template <typename T>
struct WaveValueFunction : public exprtk::ifunction<T>
{
using exprtk::ifunction<T>::operator();
WaveValueFunction(const T* v, std::size_t s)
: exprtk::ifunction<T>(1),
m_vec(v),
m_size(s)
{}
inline T operator()(const T& index)
{
return m_vec[(int) ( positiveFraction(index) * m_size )];
}
const T *m_vec;
const std::size_t m_size;
};
template <typename T>
struct WaveValueFunctionInterpolate : public exprtk::ifunction<T>
{
using exprtk::ifunction<T>::operator();
WaveValueFunctionInterpolate(const T* v, std::size_t s)
: exprtk::ifunction<T>(1),
m_vec(v),
m_size(s)
{}
inline T operator()(const T& index)
{
const T x = positiveFraction(index) * m_size;
const int ix = (int)x;
const float xfrc = fraction(x);
return linearInterpolate(m_vec[ix], m_vec[(ix + 1) % m_size], xfrc);
}
const T *m_vec;
const std::size_t m_size;
};
static const unsigned int random_data[257]={
0xd76a33ec, 0x4a767724, 0xb34ebd08 ,0xf4024196,
0x17b426e2, 0x8dc6389a, 0x1b5dcb93 ,0xa771bd3f,
0x078d502e, 0x8980988a, 0x1f64f846 ,0xb5b48ed7,
0xf0742cfb, 0xe7c66303, 0xc9472876 ,0x6c7494a5,
0x5c2203a1, 0x23986344, 0x7d344fa0 ,0x4f39474a,
0x28ac8b2b, 0x10f779b2, 0x6e79e659 ,0x32e44c52,
0xf790aa55, 0x98b05083, 0xb5d44f1c ,0xe553da04,
0xa884c6d2, 0x43274953, 0xbcb57404 ,0x43f7d32a,
0xf1890f8b, 0x019f4dce, 0x5c4ede33 ,0x2dec1a7e,
0x0f3eab09, 0x2197c93c, 0xae933f42 ,0x80d4b111,
0x6e5bd30a, 0x17139c70, 0xe15f7eb0 ,0x1798f893,
0xe1c6be1c, 0xe21edf9b, 0x4702e081 ,0x8a2cb85a,
0xbf3c1f15, 0x147f4685, 0x9221d731 ,0x3c7580f3,
0xc1c08498, 0x8e198b35, 0xf821c15a ,0x4d3cd2d4,
0xad89a3b7, 0xd48f915f, 0xcaf893f0 ,0xa64a4b8e,
0x20715f54, 0x1ba4de0a, 0x17ac6e91 ,0xd82ea8c0,
0x638a0ba5, 0xe7a76c0f, 0x486c5476 ,0x334bbd0a,
0xffe29c55, 0x7247efaf, 0x15f98e83 ,0x7a4a79ac,
0x350cd175, 0xc7107908, 0xa85c67f7 ,0x9c5002c4,
0x3cf27d2c, 0x314d8450, 0x05552886 ,0x87a73642,
0x827832e4, 0x9412cc67, 0x261979e6 ,0xb31da27f,
0x3e6bbafb, 0x663f1968, 0xd84274e2, 0xdd91d982,
0xd25c4805, 0x9567f860, 0xab99675c, 0x2254733b,
0x18799dd7, 0xee328916, 0xb9419a1b, 0x01b7a66f,
0xbcdc05e1, 0x788de4ae, 0x366e77cf, 0x81a1ebd2,
0x97be859a, 0x17d4b533, 0x22dab3a9, 0xc99871ea,
0xc7502c91, 0x4474b65f, 0x655d059d, 0x0ddc1348,
0x8325909b, 0x4873c155, 0x9fa30438, 0x7250b7a8,
0x90db2715, 0xf65e1cef, 0x41b74cf4, 0x38fba01c,
0xe9eefb40, 0x9e5524ea, 0x1d3fc077, 0x04ec39db,
0x1c0d501c, 0xb93f26d9, 0xf9f910b9, 0x806fce99,
0x5691ffdf, 0x1e63b27a, 0xf2035d42, 0xd3218a0b,
0x12eae6db, 0xeba372a9, 0x6f975260, 0xc514ae91,
0xebddb8ad, 0xc53207c0, 0xdbda57dc, 0x8fb38ef4,
0xfaa4f1bc, 0x40caf49f, 0xcb394b41, 0x424fc698,
0xb79a9ece, 0x331202d6, 0xc604ed4d, 0x5e85819f,
0x67222eda, 0xd976ba71, 0x7d083ec6, 0x040c819e,
0xc762c934, 0xa6684333, 0x2eaccc54, 0x69dc04b9,
0x0499cf36, 0x6351f438, 0x6db2dc34, 0x787ae036,
0x11b5c6ac, 0x552b7227, 0x32a4c993, 0xf7f4c49d,
0x7ac9e2d9, 0xf3d32020, 0x4ff01f89, 0x6f0e60bb,
0x3c6ed445, 0x7ca01986, 0x96901ecf, 0xe10df188,
0x62a6da6d, 0x8deee09f, 0x5347cb66, 0x5249f452,
0x22704d4d, 0x6221555f, 0x6aa0ea90, 0xe1f7bae3,
0xd106626f, 0x6365a9db, 0x1989bb81, 0xfc2daa73,
0x303c60b3, 0xcd867baa, 0x7c5787c2, 0x60082b30,
0xa68d3a81, 0x15a10f5d, 0x81b21c8a, 0x4bfb82e2,
0xff01c176, 0xff3c8b65, 0x8cc6bd29, 0xc678d6ff,
0x99b86508, 0x3c47e314, 0x766ecc05, 0xba186cb0,
0x42f57199, 0x5ef524f4, 0xb8419750, 0x6ae2a9d0,
0x291eaa18, 0x4e64b189, 0x506eb1d3, 0x78361d46,
0x6a2fcb7e, 0xbc0a46de, 0xb557badf, 0xad3de958,
0xa2901279, 0x491decbf, 0x257383df, 0x94dd19d1,
0xd0cfbbe2, 0x9063d36d, 0x81e44c3b, 0x973e9cc9,
0xfbe34690, 0x4eee3034, 0x1c413676, 0xf6735b8f,
0xf2991aca, 0x0ec85159, 0x6ce00ade, 0xad49de57,
0x025edf30, 0x42722b67, 0x30cfa6b2, 0x32df8676,
0x387d4500, 0x97fa67fd, 0x027c994a, 0x77c71d0c,
0x478eb75a, 0x898370a6, 0x73e7cca3, 0x34ace0ad,
0xc8ecb388, 0x5375c3aa, 0x9c194d87, 0x1b65246d,
0xca000bcf, 0x8a0fb13d, 0x81b957b0, 0xac627bfb,
0xc0fe47e5, 0xf3db0ad8, 0x1c605c7d, 0x5f579884,
0x63e079b5, 0x3d96f7cf, 0x3edd46e9, 0xc347c61e,
0x7b2b2a0e, 0x63dfcf51, 0x596781dd, 0x80304c4d,
0xa66f8b47
};
inline unsigned int rotateLeft(unsigned int x, const int b)
{
if (b > -32 && b < 32 && b != 0)
{
if (b < 0)
{
x= ( x >> (-b) ) | ( x << (32 + b) );
}
else
{
x= ( x << b ) | ( x >> (32 - b) );
}
}
return x;
}
struct RandomVectorFunction : public exprtk::ifunction<float>
{
using exprtk::ifunction<float>::operator();
RandomVectorFunction(const unsigned int seed) :
exprtk::ifunction<float>(1),
m_rseed(seed)
{ exprtk::disable_has_side_effects(*this); }
inline float operator()(const float& index)
{
if (index < 0 || std::isnan(index) || std::isinf(index))
{
return 0;
}
const unsigned int xi = (unsigned int)index;
const unsigned int si = m_rseed % data_size;
const unsigned int sa = m_rseed / data_size;
unsigned int res=rotateLeft(random_data[(xi + si) % data_size] ^ random_data[(xi / data_size + sa) % data_size],sa % 31 + 1);
res ^= rotateLeft(random_data[(3 * xi + si) % data_size] ^ random_data[(xi / data_size + 2 * sa) % data_size],xi % 31 + 1);
return static_cast<int>(res) / (float)(1 << 31);
}
const int data_size=sizeof(random_data)/sizeof(int);
const unsigned int m_rseed;
};
namespace SimpleRandom {
std::mt19937 generator (17); // mt19937 is a standard mersenne_twister_engine
std::uniform_real_distribution<float> dist(-1.0f, 1.0f);
struct float_random_with_engine
{
static inline float process()
{
return dist(generator);
}
};
}
static freefunc0<float,SimpleRandom::float_random_with_engine,false> simple_rand;
class ExprFrontData
{
public:
ExprFrontData():
m_rand_vec(SimpleRandom::generator()),
m_integ_func(NULL),
m_last_func(500)
{}
~ExprFrontData()
{
for (int i = 0; i < m_cyclics.size() ; ++i)
{
delete m_cyclics[i];
}
for (int i = 0; i < m_cyclics_interp.size() ; ++i)
{
delete m_cyclics_interp[i];
}
if (m_integ_func)
{
delete m_integ_func;
}
}
symbol_table_t m_symbol_table;
expression_t m_expression;
std::string m_expression_string;
std::vector<WaveValueFunction<float>* > m_cyclics;
std::vector<WaveValueFunctionInterpolate<float>* > m_cyclics_interp;
RandomVectorFunction m_rand_vec;
IntegrateFunction<float> *m_integ_func;
LastSampleFunction<float> m_last_func;
};
struct sin_wave
{
static inline float process(float x)
{
x = positiveFraction(x);
return sinf(x * F_2PI);
}
};
static freefunc1<float,sin_wave,true> sin_wave_func;
struct square_wave
{
static inline float process(float x)
{
x = positiveFraction(x);
if (x >= 0.5f) { return -1.0f; }
else { return 1.0f; }
}
};
static freefunc1<float,square_wave,true> square_wave_func;
struct triangle_wave
{
static inline float process(float x)
{
x=positiveFraction(x);
if (x < 0.25f)
{
return x * 4.0f;
}
else
{
if (x < 0.75f) { return 2.0f - x * 4.0f; }
else { return x * 4.0f - 4.0f; }
}
}
};
static freefunc1<float,triangle_wave,true> triangle_wave_func;
struct saw_wave
{
static inline float process(float x)
{
x=positiveFraction(x);
return 2.0f * x - 1.0f;
}
};
static freefunc1<float,saw_wave,true> saw_wave_func;
struct moogsaw_wave
{
static inline float process(float x)
{
x = positiveFraction(x);
if( x < 0.5f )
{
return -1.0f + x * 4.0f;
}
return 1.0f - 2.0f * x;
}
};
static freefunc1<float,moogsaw_wave,true> moogsaw_wave_func;
struct moog_wave
{
static inline float process(float x)
{
x = positiveFraction(x);
if( x > 0.5f )
{
x = 1.0f - x;
return -1.0f + 4.0f * x * x;
}
return -1.0f + 8.0f * x * x;
}
};
static freefunc1<float,moog_wave,true> moog_wave_func;
struct exp_wave
{
static inline float process(float x)
{
x = positiveFraction(x);
if( x > 0.5f )
{
x = 1.0f - x;
}
return -1.0f + 8.0f * x * x;
}
};
static freefunc1<float,exp_wave,true> exp_wave_func;
struct exp2_wave
{
static inline float process(float x)
{
x = positiveFraction(x);
if( x > 0.5f )
{
return -1.0f + 8.0f * (1.0f-x) * x;
}
return -1.0f + 8.0f * x * x;
}
};
static freefunc1<float,exp2_wave,true> exp2_wave_func;
struct harmonic_cent
{
static inline float process(float x)
{
return powf(2, x / 1200);
}
};
static freefunc1<float,harmonic_cent,true> harmonic_cent_func;
struct harmonic_semitone
{
static inline float process(float x)
{
return powf(2, x / 12);
}
};
static freefunc1<float,harmonic_semitone,true> harmonic_semitone_func;
ExprFront::ExprFront(const char * expr)
{
m_valid = false;
try
{
m_data = new ExprFrontData();
m_data->m_expression_string = expr;
m_data->m_symbol_table.add_pi();
m_data->m_symbol_table.add_constant("e", F_E);
m_data->m_symbol_table.add_function("sinew", sin_wave_func);
m_data->m_symbol_table.add_function("squarew", square_wave_func);
m_data->m_symbol_table.add_function("trianglew", triangle_wave_func);
m_data->m_symbol_table.add_function("saww", saw_wave_func);
m_data->m_symbol_table.add_function("moogsaww", moogsaw_wave_func);
m_data->m_symbol_table.add_function("moogw", moog_wave_func);
m_data->m_symbol_table.add_function("expw", exp_wave_func);
m_data->m_symbol_table.add_function("expnw", exp2_wave_func);
m_data->m_symbol_table.add_function("cent", harmonic_cent_func);
m_data->m_symbol_table.add_function("semitone", harmonic_semitone_func);
m_data->m_symbol_table.add_function("rand", simple_rand);
m_data->m_symbol_table.add_function("randv", m_data->m_rand_vec);
m_data->m_symbol_table.add_function("last", m_data->m_last_func);
}
catch(...)
{
WARN_EXPRTK;
}
}
ExprFront::~ExprFront()
{
try
{
delete m_data;
}
catch(...)
{
WARN_EXPRTK;
}
}
bool ExprFront::compile()
{
m_valid = false;
try
{
m_data->m_expression.register_symbol_table(m_data->m_symbol_table);
parser_t::settings_store sstore;
sstore.disable_all_logic_ops();
sstore.disable_all_assignment_ops();
sstore.disable_all_control_structures();
parser_t parser(sstore);
m_valid=parser.compile(m_data->m_expression_string, m_data->m_expression);
}
catch(...)
{
WARN_EXPRTK;
}
return m_valid;
}
float ExprFront::evaluate()
{
try
{
if (!m_valid) return 0;
float res = m_data->m_expression.value();
m_data->m_last_func.setLastSample(res);
return res;
}
catch(...)
{
WARN_EXPRTK;
}
return 0;
}
bool ExprFront::add_variable(const char* name, float& ref)
{
try
{
return m_data->m_symbol_table.add_variable(name, ref);
}
catch(...)
{
WARN_EXPRTK;
}
return false;
}
bool ExprFront::add_constant(const char* name, float ref)
{
try
{
return m_data->m_symbol_table.add_constant(name, ref);
}
catch(...)
{
WARN_EXPRTK;
}
return false;
}
bool ExprFront::add_cyclic_vector(const char* name, const float* data, size_t length, bool interp)
{
try
{
if (interp)
{
WaveValueFunctionInterpolate<float> *wvf = new WaveValueFunctionInterpolate<float>(data, length);
m_data->m_cyclics_interp.push_back(wvf);
return m_data->m_symbol_table.add_function(name, *wvf);
}
else
{
WaveValueFunction<float> *wvf = new WaveValueFunction<float>(data, length);
m_data->m_cyclics.push_back(wvf);
return m_data->m_symbol_table.add_function(name, *wvf);
}
}
catch(...)
{
WARN_EXPRTK;
}
return false;
}
size_t find_occurances(const std::string& haystack, const char* const needle)
{
size_t last_pos = 0;
size_t count = 0;
const size_t len = strlen(needle);
if (len > 0)
{
while (last_pos + len <= haystack.length())
{
last_pos = haystack.find(needle, last_pos);
if (last_pos == std::string::npos)
break;
++count;
last_pos += len;
}
}
return count;
}
void ExprFront::setIntegrate(const unsigned int* const frameCounter, const unsigned int sample_rate)
{
if (m_data->m_integ_func == NULL)
{
const unsigned int ointeg = find_occurances(m_data->m_expression_string,"integrate");
if ( ointeg > 0 )
{
m_data->m_integ_func = new IntegrateFunction<float>(frameCounter,sample_rate,ointeg);
try
{
m_data->m_symbol_table.add_function("integrate",*m_data->m_integ_func);
}
catch(...)
{
WARN_EXPRTK;
}
}
}
}
ExprSynth::ExprSynth(const WaveSample *gW1, const WaveSample *gW2, const WaveSample *gW3,
ExprFront *exprO1, ExprFront *exprO2,
NotePlayHandle *nph, const sample_rate_t sample_rate,
const FloatModel* pan1, const FloatModel* pan2, float rel_trans):
m_exprO1(exprO1),
m_exprO2(exprO2),
m_W1(gW1),
m_W2(gW2),
m_W3(gW3),
m_nph(nph),
m_sample_rate(sample_rate),
m_pan1(pan1),
m_pan2(pan2),
m_rel_transition(rel_trans)
{
m_note_sample = 0;
m_note_rel_sample = 0;
m_note_rel_sec = 0;
m_note_sample_sec = 0;
m_released = 0;
m_frequency = m_nph->frequency();
m_rel_inc = 1000.0 / (m_sample_rate * m_rel_transition);//rel_transition in ms. compute how much increment in each frame
auto init_expression_step2 = [this](ExprFront * e) {
e->add_cyclic_vector("W1", m_W1->m_samples,m_W1->m_length, m_W1->m_interpolate);
e->add_cyclic_vector("W2", m_W2->m_samples,m_W2->m_length, m_W2->m_interpolate);
e->add_cyclic_vector("W3", m_W3->m_samples,m_W3->m_length, m_W3->m_interpolate);
e->add_variable("t", m_note_sample_sec);
e->add_variable("f", m_frequency);
e->add_variable("rel",m_released);
e->add_variable("trel",m_note_rel_sec);
e->setIntegrate(&m_note_sample,m_sample_rate);
e->compile();
};
init_expression_step2(m_exprO1);
init_expression_step2(m_exprO2);
}
ExprSynth::~ExprSynth()
{
if (m_exprO1)
{
delete m_exprO1;
}
if (m_exprO2)
{
delete m_exprO2;
}
}
void ExprSynth::renderOutput(fpp_t frames, sampleFrame *buf)
{
try
{
bool o1_valid = m_exprO1->isValid();
bool o2_valid = m_exprO2->isValid();
if (!o1_valid && !o2_valid)
{
return;
}
float o1 = 0, o2 = 0;
float pn1 = m_pan1->value() * 0.5;
float pn2 = m_pan2->value() * 0.5;
const float new_freq = m_nph->frequency();
const float freq_inc = (new_freq - m_frequency) / frames;
const bool is_released = m_nph->isReleased();
expression_t *o1_rawExpr = &(m_exprO1->getData()->m_expression);
expression_t *o2_rawExpr = &(m_exprO2->getData()->m_expression);
LastSampleFunction<float> * last_func1 = &m_exprO1->getData()->m_last_func;
LastSampleFunction<float> * last_func2 = &m_exprO2->getData()->m_last_func;
if (is_released && m_note_rel_sample == 0)
{
m_note_rel_sample = m_note_sample;
}
if (o1_valid && o2_valid)
{
for (fpp_t frame = 0; frame < frames ; ++frame)
{
if (is_released && m_released < 1)
{
m_released = fmin(m_released+m_rel_inc, 1);
}
o1 = o1_rawExpr->value();
o2 = o2_rawExpr->value();
last_func1->setLastSample(o1);
last_func2->setLastSample(o2);
buf[frame][0] = (-pn1 + 0.5) * o1 + (-pn2 + 0.5) * o2;
buf[frame][1] = ( pn1 + 0.5) * o1 + ( pn2 + 0.5) * o2;
m_note_sample++;
m_note_sample_sec = m_note_sample / (float)m_sample_rate;
if (is_released)
{
m_note_rel_sec = (m_note_sample - m_note_rel_sample) / (float)m_sample_rate;
}
m_frequency += freq_inc;
}
}
else
{
if (o2_valid)
{
o1_rawExpr = o2_rawExpr;
last_func1 = last_func2;
pn1 = pn2;
}
for (fpp_t frame = 0; frame < frames ; ++frame)
{
if (is_released && m_released < 1)
{
m_released = fmin(m_released+m_rel_inc, 1);
}
o1 = o1_rawExpr->value();
last_func1->setLastSample(o1);
buf[frame][0] = (-pn1 + 0.5) * o1;
buf[frame][1] = ( pn1 + 0.5) * o1;
m_note_sample++;
m_note_sample_sec = m_note_sample / (float)m_sample_rate;
if (is_released)
{
m_note_rel_sec = (m_note_sample - m_note_rel_sample) / (float)m_sample_rate;
}
m_frequency += freq_inc;
}
}
m_frequency = new_freq;
}
catch(...)
{
WARN_EXPRTK;
}
}

View File

@@ -0,0 +1,141 @@
/*
* exprfront.h - header file to a Frontend to ExprTk
*
* Copyright (c) 2016-2017 Orr Dvori
*
* 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 EXPRFRONT_H_
#define EXPRFRONT_H_
#include <cmath>
#include <cstddef>
#include "AutomatableModel.h"
#include "Graph.h"
#include "Instrument.h"
#include "MemoryManager.h"
class ExprFrontData;
class ExprFront
{
public:
typedef float (*ff1data_functor)(void*, float);
ExprFront(const char* expr);
~ExprFront();
bool compile();
inline bool isValid() { return m_valid; }
float evaluate();
bool add_variable(const char* name, float & ref);
bool add_constant(const char* name, float ref);
bool add_cyclic_vector(const char* name, const float* data, size_t length, bool interp = false);
void setIntegrate(const unsigned int* frameCounter, unsigned int sample_rate);
ExprFrontData* getData() { return m_data; }
private:
ExprFrontData *m_data;
bool m_valid;
};
class WaveSample
{
public:
WaveSample(int length)
{
m_length = length;
m_samples = new float[m_length];
for(int i = 0 ; i < m_length ; ++i)
m_samples[i] = 0;
}
WaveSample(const graphModel * graph)
{
m_length = graph->length();
m_samples = new float[m_length];
memcpy(m_samples, graph->samples(), m_length * sizeof(float));
}
inline void copyFrom(const graphModel * graph)
{
memcpy(m_samples, graph->samples(), m_length * sizeof(float));
}
~WaveSample()
{
delete [] m_samples;
}
inline void setInterpolate(bool _interpolate) { m_interpolate = _interpolate; }
float *m_samples;
int m_length;
bool m_interpolate;
};
class ExprSynth
{
MM_OPERATORS
public:
ExprSynth(const WaveSample* gW1, const WaveSample* gW2, const WaveSample* gW3, ExprFront* exprO1, ExprFront* exprO2, NotePlayHandle* nph,
const sample_rate_t sample_rate, const FloatModel* pan1, const FloatModel* pan2, float rel_trans);
virtual ~ExprSynth();
void renderOutput(fpp_t frames, sampleFrame* buf );
private:
ExprFront *m_exprO1, *m_exprO2;
const WaveSample *m_W1, *m_W2, *m_W3;
unsigned int m_note_sample;
unsigned int m_note_rel_sample;
float m_note_sample_sec;
float m_note_rel_sec;
float m_frequency;
float m_released;
NotePlayHandle* m_nph;
const sample_rate_t m_sample_rate;
const FloatModel *m_pan1,*m_pan2;
float m_rel_transition;
float m_rel_inc;
} ;
inline float positiveFraction(float x)
{
if (std::isnan(x) || std::isinf(x))
return 0;
if (x<0)
{
x+=static_cast<int>(1-x);
}
return x-static_cast<int>(x);
}
template <typename T>
inline void clearArray(T* arr,unsigned int size)
{
const T* const arr_end = arr + size;
while(arr < arr_end)
{
*arr=0;
++arr;
}
}
#endif /* EXPRFRONT_H_ */

37969
plugins/xpressive/exprtk.hpp Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

BIN
plugins/xpressive/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -130,14 +130,8 @@ void Graph::mouseMoveEvent ( QMouseEvent * _me )
x = qMax( 2, qMin( x, width()-3 ) );
y = qMax( 2, qMin( y, height()-3 ) );
if( qAbs( diff ) > 1 )
{
drawLineAt( x, y, m_lastCursorX );
}
else
{
changeSampleAt( x, y );
}
drawLineAt( x, y, m_lastCursorX );
// update mouse
if( diff != 0 )
@@ -209,25 +203,47 @@ void Graph::drawLineAt( int _x, int _y, int _lastx )
float range = minVal - maxVal;
float val = ( _y*range/( height()-5 ) ) + maxVal;
float lastval = model() -> m_samples[ (int)( _lastx * xscale ) ];
// calculate line drawing variables
int linelen = qAbs( _x - _lastx ) + 1;
int xstep = _x > _lastx ? -1 : 1;
float ystep = ( lastval - val ) / linelen;
int start = INT_MAX;
int end = 0;
// draw a line
for ( int i = 0; i < linelen; i++ )
int sample_begin, sample_end;
float lastval;
float val_begin, val_end;
if (_lastx > _x)
{
int x = (_x + (i * xstep)) * xscale; // get x value
model()->drawSampleAt( x, val + (i * ystep));
start = qMin( start, x );
end = qMax( end, x );
sample_begin = (int)((_x) * xscale);
sample_end = (int)ceil((_lastx+1) * xscale);
lastval = model() -> m_samples[ (int)( sample_end - 1 ) ];
val_begin = val;
val_end = lastval;
}
else
{
sample_begin = (int)(_lastx * xscale);
sample_end = (int)ceil((_x+1) * xscale);
lastval = model() -> m_samples[ (int)( sample_begin ) ];
val_begin = lastval;
val_end = val;
}
model()->samplesChanged( start, end );
// calculate line drawing variables
int linelen = sample_end - sample_begin;
if (linelen == 1)
{
val_begin = val;
}
//int xstep = _x > _lastx ? -1 : 1;
float ystep = ( val_end - val_begin ) / linelen;
// draw a line
for ( int i = 0 ; i < linelen; i++ )
{
model()->drawSampleAt( sample_begin + i , val_begin + ((i ) * ystep));
}
model()->samplesChanged( sample_begin, sample_end );
}
void Graph::changeSampleAt( int _x, int _y )
@@ -265,6 +281,7 @@ void Graph::mouseReleaseEvent( QMouseEvent * _me )
m_mouseDown = false;
setCursor( Qt::CrossCursor );
update();
emit drawn();
}
}
@@ -633,7 +650,24 @@ void graphModel::smoothNonCyclic()
emit samplesChanged(0, length()-1);
}
//makes a cyclic convolution.
void graphModel::convolve(const float *convolution, const int convolutionLength, const int centerOffset)
{
// store values in temporary array
QVector<float> temp = m_samples;
const int graphLength = length();
float sum;
for ( int i = 0; i < graphLength; i++ )
{
sum = 0;
for ( int j = 0; j < convolutionLength; j++ )
{
sum += convolution[j] * temp[( i + j ) % graphLength];
}
m_samples[( i + centerOffset ) % graphLength] = sum;
}
emit samplesChanged(0, graphLength - 1);
}
void graphModel::normalize()
{
@@ -676,7 +710,7 @@ void graphModel::invert()
void graphModel::shiftPhase( int _deg )
{
// calculate offset in samples
int offset = ( _deg * length() ) / 360; //multiply first because integers
const int offset = ( _deg * length() ) / 360; //multiply first because integers
// store values in temporary array
QVector<float> temp = m_samples;
@@ -692,6 +726,14 @@ void graphModel::shiftPhase( int _deg )
emit samplesChanged( 0, length()-1 );
}
void graphModel::clear()
{
const int graph_length = length();
for( int i = 0; i < graph_length; i++ )
m_samples[i] = 0;
emit samplesChanged( 0, graph_length - 1 );
}
void graphModel::drawSampleAt( int x, float val )