Files
lmms/plugins/Xpressive/Xpressive.cpp
Johannes Lorenz a1b355828e Allow sub plugins for instruments aswell
* Move m_key member of Effect into Plugin
* Pass key to Instrument ctors and instantiaters
* Add pluginKeys to all plugin selector widgets, and let them pass the keys
  when instantiating the instruments; or, if the keys must be passed over
  threads, pass the keys to the Engine using `Engine::setDndPluginKey()`
* As instrument plugin libraries now also need to get their key passed, their
  second argument, which was always the same as the first, is now used to pass
  the sub plugin keys. This affects *all* instrument plugins.
* Plugin.h: Add more virtuals to `SubPluginFeatures` in order to draw logos
  and images into instrument selector widgets
* LadspaSubPluginFeatures: Implement the `displayName` virtual because the
  new behaviour to resolve displayNames is to first look at the
  SubPluginFeatures, which, without override, returns the superior plugin's
  name (Plugin.cpp)

Additional:

* PluginFactory.h: Allow setting up search paths without discovering plugins
  yet
* Plugin.h: Add full documentation (should be checked)
2018-12-27 21:24:19 +01:00

889 lines
31 KiB
C++

/*
* Xpressive.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 "Xpressive.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"
#include "plugin_export.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 Xpressive
*
* lmms - plugin
*
***********************************************************************/
#define GRAPH_LENGTH 4096
Xpressive::Xpressive(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("Panning 1")),
m_panning2(-1, -1.0f, 1.0f, 0.01f, this, tr("Panning 2")),
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";
}
Xpressive::~Xpressive() {
}
void Xpressive::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 Xpressive::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 Xpressive::nodeName() const {
return (xpressive_plugin_descriptor.name);
}
void Xpressive::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(),Engine::mixer()->processingSampleRate());//give the "last" function a whole second
ExprFront *exprO2 = new ExprFront(m_outputExpression[1].constData(),Engine::mixer()->processingSampleRate());
auto init_expression_step1 = [this, nph](ExprFront* e) { //lambda function to init exprO1 and exprO2
//add the constants and the variables to the expression.
e->add_constant("key", nph->key());//the key that was pressed.
e->add_constant("bnote", nph->instrumentTrack()->baseNote()); // the base note
e->add_constant("srate", Engine::mixer()->processingSampleRate());// sample rate of the mixer
e->add_constant("v", nph->getVolume() / 255.0); //volume of the note.
e->add_constant("tempo", Engine::getSong()->getTempo());//tempo of the song.
e->add_variable("A1", m_A1);//A1,A2,A3: general purpose input controls.
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());//set interpolation according to the user selection.
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 Xpressive::deleteNotePluginData(NotePlayHandle* nph) {
delete static_cast<ExprSynth *>(nph->m_pluginData);
}
PluginView * Xpressive::instantiateView(QWidget* parent) {
return (new XpressiveView(this, parent));
}
class XpressiveKnob: 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);
}
XpressiveKnob(QWidget * _parent, const QString & _name) :
Knob(knobStyled, _parent,_name) {
setStyle();
}
XpressiveKnob(QWidget * _parent) :
Knob(knobStyled, _parent) {
setStyle();
}
};
XpressiveView::XpressiveView(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 O1"));
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 O2"));
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);
Xpressive *e = castModel<Xpressive>();
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("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("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("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("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("User-defined wave"));
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("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("Square wave"));
m_whiteNoiseWaveBtn = new PixmapButton(this, tr("White noise"));
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("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 XpressiveKnob(this,"A1");
m_generalPurposeKnob[0]->setHintText(tr("General purpose 1:"), "");
m_generalPurposeKnob[0]->move(COL_KNOBS, ROW_KNOBSA1);
m_generalPurposeKnob[1] = new XpressiveKnob(this,"A2");
m_generalPurposeKnob[1]->setHintText(tr("General purpose 2:"), "");
m_generalPurposeKnob[1]->move(COL_KNOBS, ROW_KNOBSA2);
m_generalPurposeKnob[2] = new XpressiveKnob(this,"A3");
m_generalPurposeKnob[2]->setHintText(tr("General purpose 3:"), "");
m_generalPurposeKnob[2]->move(COL_KNOBS, ROW_KNOBSA3);
m_panningKnob[0] = new XpressiveKnob(this,"O1 panning");
m_panningKnob[0]->setHintText(tr("O1 panning:"), "");
m_panningKnob[0]->move(COL_KNOBS, ROW_KNOBSP1);
m_panningKnob[1] = new XpressiveKnob(this,"O2 panning");
m_panningKnob[1]->setHintText(tr("O2 panning:"), "");
m_panningKnob[1]->move(COL_KNOBS, ROW_KNOBSP2);
m_relKnob = new XpressiveKnob(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();
}
XpressiveView::~XpressiveView()
{
}
void XpressiveView::expressionChanged() {
Xpressive * e = castModel<Xpressive>();
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)
{
const unsigned int sample_rate=m_raw_graph->length();
ExprFront expr(text.constData(),sample_rate);
float t=0;
const float f=10,key=5,v=0.5;
unsigned int i;
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 Xpressive::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 XpressiveView::smoothChanged()
{
Xpressive * e = castModel<Xpressive>();
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;
}
Xpressive::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 XpressiveView::graphDrawn()
{
m_raw_graph->setSamples(m_graph->model()->samples());
Xpressive * e = castModel<Xpressive>();
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 XpressiveView::modelChanged() {
Xpressive * b = castModel<Xpressive>();
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 XpressiveView::updateLayout() {
Xpressive * e = castModel<Xpressive>();
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 XpressiveView::sinWaveClicked() {
if (m_output_expr)
m_expressionEditor->appendPlainText("sinew(t*f)");
else
m_expressionEditor->appendPlainText("sinew(t)");
Engine::getSong()->setModified();
}
void XpressiveView::triangleWaveClicked() {
if (m_output_expr)
m_expressionEditor->appendPlainText("trianglew(t*f)");
else
m_expressionEditor->appendPlainText("trianglew(t)");
Engine::getSong()->setModified();
}
void XpressiveView::sawWaveClicked() {
if (m_output_expr)
m_expressionEditor->appendPlainText("saww(t*f)");
else
m_expressionEditor->appendPlainText("saww(t)");
Engine::getSong()->setModified();
}
void XpressiveView::sqrWaveClicked() {
if (m_output_expr)
m_expressionEditor->appendPlainText("squarew(t*f)");
else
m_expressionEditor->appendPlainText("squarew(t)");
Engine::getSong()->setModified();
}
void XpressiveView::noiseWaveClicked() {
m_expressionEditor->appendPlainText("randsv(t*srate,0)");
Engine::getSong()->setModified();
}
void XpressiveView::moogSawWaveClicked()
{
if (m_output_expr)
m_expressionEditor->appendPlainText("moogsaww(t*f)");
else
m_expressionEditor->appendPlainText("moogsaww(t)");
Engine::getSong()->setModified();
}
void XpressiveView::expWaveClicked()
{
if (m_output_expr)
m_expressionEditor->appendPlainText("expw(t*f)");
else
m_expressionEditor->appendPlainText("expw(t)");
Engine::getSong()->setModified();
}
void XpressiveView::usrWaveClicked() {
m_expressionEditor->setPlainText("");
QString fileName = m_raw_graph->setWaveToUser();
smoothChanged();
Engine::getSong()->setModified();
}
QString XpressiveHelpView::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>seed</b> - A random value that remains consistent in the lifetime of a single wave. meant to be used with <b>randsv</b><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. In O1 and O2 it keeps a whole second. Thus the argument n must be in the range [1,srate], 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>randsv(x,seed)</b> - works exactly like randv(x),<br>"
"except that it lets you to select the seed manualy,<br>"
"if you want to try different random values and make it consistent in each evaluation.<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>"
;
XpressiveHelpView::XpressiveHelpView():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 XpressiveView::helpClicked() {
XpressiveHelpView::getInstance()->show();
}
extern "C" {
// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main(Model *m, void *) {
return (new Xpressive(static_cast<InstrumentTrack *>(m)));
}
}