mirror of
https://github.com/LMMS/lmms.git
synced 2026-05-18 19:55:00 -04:00
Changes AutomationPattern to use nodes instead of raw float values (#5712)
This commit is contained in:
BIN
data/themes/classic/edit_draw_outvalue.png
Normal file
BIN
data/themes/classic/edit_draw_outvalue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 832 B |
@@ -20,7 +20,8 @@ AutomationEditor {
|
||||
background-color: rgb(0, 0, 0);
|
||||
color: #e0e0e0;
|
||||
qproperty-backgroundShade: rgba(255, 255, 255, 15);
|
||||
qproperty-vertexColor: #ff77af;
|
||||
qproperty-nodeInValueColor: rgba(255, 119, 175, 150);
|
||||
qproperty-nodeOutValueColor: rgba(129, 231, 181, 150);
|
||||
qproperty-crossColor: rgb( 255, 51, 51 );
|
||||
/* Grid colors */
|
||||
qproperty-lineColor: rgba(128, 128, 128, 80);
|
||||
|
||||
BIN
data/themes/default/edit_draw_outvalue.png
Normal file
BIN
data/themes/default/edit_draw_outvalue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 763 B |
@@ -55,7 +55,8 @@ AutomationEditor {
|
||||
color: #ffffff;
|
||||
background-color: #141616;
|
||||
qproperty-backgroundShade: rgba(255, 255, 255, 15);
|
||||
qproperty-vertexColor: #6749C2;
|
||||
qproperty-nodeInValueColor: rgba(103, 73, 194, 150);
|
||||
qproperty-nodeOutValueColor: rgba(125, 40, 40, 150);
|
||||
qproperty-crossColor: rgba(215, 210, 254, 150);
|
||||
/* Grid colors */
|
||||
qproperty-lineColor: #292929;
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#ifndef AUTOMATION_EDITOR_H
|
||||
#define AUTOMATION_EDITOR_H
|
||||
|
||||
#include <QtCore/QMutex>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
@@ -52,14 +51,15 @@ class TimeLineWidget;
|
||||
class AutomationEditor : public QWidget, public JournallingObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QColor barLineColor READ barLineColor WRITE setBarLineColor)
|
||||
Q_PROPERTY(QColor beatLineColor READ beatLineColor WRITE setBeatLineColor)
|
||||
Q_PROPERTY(QColor lineColor READ lineColor WRITE setLineColor)
|
||||
Q_PROPERTY(QColor vertexColor READ vertexColor WRITE setVertexColor)
|
||||
Q_PROPERTY(QBrush scaleColor READ scaleColor WRITE setScaleColor)
|
||||
Q_PROPERTY(QBrush graphColor READ graphColor WRITE setGraphColor)
|
||||
Q_PROPERTY(QColor crossColor READ crossColor WRITE setCrossColor)
|
||||
Q_PROPERTY(QColor backgroundShade READ backgroundShade WRITE setBackgroundShade)
|
||||
Q_PROPERTY(QColor barLineColor MEMBER m_barLineColor)
|
||||
Q_PROPERTY(QColor beatLineColor MEMBER m_beatLineColor)
|
||||
Q_PROPERTY(QColor lineColor MEMBER m_lineColor)
|
||||
Q_PROPERTY(QColor nodeInValueColor MEMBER m_nodeInValueColor)
|
||||
Q_PROPERTY(QColor nodeOutValueColor MEMBER m_nodeOutValueColor)
|
||||
Q_PROPERTY(QBrush scaleColor MEMBER m_scaleColor)
|
||||
Q_PROPERTY(QBrush graphColor MEMBER m_graphColor)
|
||||
Q_PROPERTY(QColor crossColor MEMBER m_crossColor)
|
||||
Q_PROPERTY(QColor backgroundShade MEMBER m_backgroundShade)
|
||||
public:
|
||||
void setCurrentPattern(AutomationPattern * new_pattern);
|
||||
|
||||
@@ -80,30 +80,11 @@ public:
|
||||
return "automationeditor";
|
||||
}
|
||||
|
||||
// qproperty access methods
|
||||
QColor barLineColor() const;
|
||||
void setBarLineColor(const QColor & c);
|
||||
QColor beatLineColor() const;
|
||||
void setBeatLineColor(const QColor & c);
|
||||
QColor lineColor() const;
|
||||
void setLineColor(const QColor & c);
|
||||
QBrush graphColor() const;
|
||||
void setGraphColor(const QBrush & c);
|
||||
QColor vertexColor() const;
|
||||
void setVertexColor(const QColor & c);
|
||||
QBrush scaleColor() const;
|
||||
void setScaleColor(const QBrush & c);
|
||||
QColor crossColor() const;
|
||||
void setCrossColor(const QColor & c);
|
||||
QColor backgroundShade() const;
|
||||
void setBackgroundShade(const QColor & c);
|
||||
|
||||
enum EditModes
|
||||
{
|
||||
DRAW,
|
||||
ERASE,
|
||||
SELECT,
|
||||
MOVE
|
||||
DRAW_OUTVALUES
|
||||
};
|
||||
|
||||
public slots:
|
||||
@@ -126,13 +107,11 @@ protected:
|
||||
float getLevel( int y );
|
||||
int xCoordOfTick( int tick );
|
||||
float yCoordOfLevel( float level );
|
||||
inline void drawLevelTick( QPainter & p, int tick, float value);// bool is_selected ); //NEEDS Change in CSS
|
||||
void removeSelection();
|
||||
void selectAll();
|
||||
void getSelectedValues(timeMap & selected_values );
|
||||
inline void drawLevelTick(QPainter & p, int tick, float value);
|
||||
|
||||
timeMap::iterator getNodeAt(int x, int y, bool outValue = false, int r = 5);
|
||||
|
||||
void drawLine( int x0, float y0, int x1, float y1 );
|
||||
void removePoints( int x0, int x1 );
|
||||
|
||||
protected slots:
|
||||
void play();
|
||||
@@ -148,11 +127,6 @@ protected slots:
|
||||
void setProgressionType(int type);
|
||||
void setTension();
|
||||
|
||||
void copySelectedValues();
|
||||
void cutSelectedValues();
|
||||
void pasteValues();
|
||||
void deleteSelectedValues();
|
||||
|
||||
void updatePosition( const TimePos & t );
|
||||
|
||||
void zoomingXChanged();
|
||||
@@ -167,8 +141,10 @@ private:
|
||||
{
|
||||
NONE,
|
||||
MOVE_VALUE,
|
||||
SELECT_VALUES,
|
||||
MOVE_SELECTION
|
||||
ERASE_VALUES,
|
||||
MOVE_OUTVALUE,
|
||||
RESET_OUTVALUES,
|
||||
DRAW_LINE
|
||||
} ;
|
||||
|
||||
// some constants...
|
||||
@@ -187,7 +163,7 @@ private:
|
||||
|
||||
static QPixmap * s_toolDraw;
|
||||
static QPixmap * s_toolErase;
|
||||
static QPixmap * s_toolSelect;
|
||||
static QPixmap * s_toolDrawOut;
|
||||
static QPixmap * s_toolMove;
|
||||
static QPixmap * s_toolYFlip;
|
||||
static QPixmap * s_toolXFlip;
|
||||
@@ -200,7 +176,6 @@ private:
|
||||
|
||||
FloatModel * m_tensionModel;
|
||||
|
||||
QMutex m_patternMutex;
|
||||
AutomationPattern * m_pattern;
|
||||
float m_minLevel;
|
||||
float m_maxLevel;
|
||||
@@ -219,13 +194,6 @@ private:
|
||||
|
||||
Actions m_action;
|
||||
|
||||
tick_t m_selectStartTick;
|
||||
tick_t m_selectedTick;
|
||||
float m_selectStartLevel;
|
||||
float m_selectedLevels;
|
||||
|
||||
float m_moveStartLevel;
|
||||
tick_t m_moveStartTick;
|
||||
int m_moveXOffset;
|
||||
|
||||
float m_drawLastLevel;
|
||||
@@ -235,9 +203,8 @@ private:
|
||||
int m_y_delta;
|
||||
bool m_y_auto;
|
||||
|
||||
timeMap m_valuesToCopy;
|
||||
timeMap m_selValuesForMove;
|
||||
|
||||
// Time position (key) of automation node whose outValue is being dragged
|
||||
int m_draggedOutValueKey;
|
||||
|
||||
EditModes m_editMode;
|
||||
|
||||
@@ -255,7 +222,8 @@ private:
|
||||
QColor m_beatLineColor;
|
||||
QColor m_lineColor;
|
||||
QBrush m_graphColor;
|
||||
QColor m_vertexColor;
|
||||
QColor m_nodeInValueColor;
|
||||
QColor m_nodeOutValueColor;
|
||||
QBrush m_scaleColor;
|
||||
QColor m_crossColor;
|
||||
QColor m_backgroundShade;
|
||||
|
||||
153
include/AutomationNode.h
Normal file
153
include/AutomationNode.h
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* AutomationNode.h - Declaration of class AutomationNode, which contains
|
||||
* all information about an automation node
|
||||
*
|
||||
* Copyright (c) 2020 Ian Caio <iancaio_dev/at/hotmail.com>
|
||||
*
|
||||
* 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 AUTOMATION_NODE_H
|
||||
#define AUTOMATION_NODE_H
|
||||
|
||||
// MACROs to help handling automation nodes
|
||||
#define INVAL(x) ((x).value().getInValue())
|
||||
#define OUTVAL(x) ((x).value().getOutValue())
|
||||
#define OFFSET(x) ((x).value().getValueOffset())
|
||||
#define INTAN(x) ((x).value().getInTangent())
|
||||
#define OUTTAN(x) ((x).value().getOutTangent())
|
||||
#define POS(x) ((x).key())
|
||||
|
||||
class AutomationPattern;
|
||||
|
||||
|
||||
// Note: We use the default copy-assignment on the AutomationPattern constructor. It's
|
||||
// fine for now as we don't have dynamic allocated members, but if any are added we should
|
||||
// have an user-defined one to perform a deep-copy.
|
||||
class AutomationNode
|
||||
{
|
||||
public:
|
||||
AutomationNode(); // Dummy constructor for the QMap
|
||||
AutomationNode(AutomationPattern* pat, float value, int pos);
|
||||
AutomationNode(AutomationPattern* pat, float inValue, float outValue, int pos);
|
||||
|
||||
AutomationNode& operator+=(float f)
|
||||
{
|
||||
m_inValue += f;
|
||||
m_outValue += f;
|
||||
return *this;
|
||||
}
|
||||
AutomationNode& operator-=(float f)
|
||||
{
|
||||
m_inValue -= f;
|
||||
m_outValue -= f;
|
||||
return *this;
|
||||
}
|
||||
AutomationNode& operator*=(float f)
|
||||
{
|
||||
m_inValue *= f;
|
||||
m_outValue *= f;
|
||||
return *this;
|
||||
}
|
||||
AutomationNode& operator/=(float f)
|
||||
{
|
||||
m_inValue /= f;
|
||||
m_outValue /= f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline const float getInValue() const
|
||||
{
|
||||
return m_inValue;
|
||||
}
|
||||
void setInValue(float value);
|
||||
|
||||
inline const float getOutValue() const
|
||||
{
|
||||
return m_outValue;
|
||||
}
|
||||
void setOutValue(float value);
|
||||
void resetOutValue();
|
||||
|
||||
/**
|
||||
* @brief Gets the offset between inValue and outValue
|
||||
* @return Float representing the offset between inValue and outValue
|
||||
*/
|
||||
inline const float getValueOffset() const
|
||||
{
|
||||
return m_outValue - m_inValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the tangent of the left side of the node
|
||||
* @return Float with the tangent from the inValue side
|
||||
*/
|
||||
inline const float getInTangent() const
|
||||
{
|
||||
return m_inTangent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the tangent of the left side of the node
|
||||
* @param Float with the tangent for the inValue side
|
||||
*/
|
||||
inline void setInTangent(float tangent)
|
||||
{
|
||||
m_inTangent = tangent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the tangent of the right side of the node
|
||||
* @return Float with the tangent from the outValue side
|
||||
*/
|
||||
inline const float getOutTangent() const
|
||||
{
|
||||
return m_outTangent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the tangent of the right side of the node
|
||||
* @param Float with the tangent for the outValue side
|
||||
*/
|
||||
inline void setOutTangent(float tangent)
|
||||
{
|
||||
m_outTangent = tangent;
|
||||
}
|
||||
|
||||
private:
|
||||
// Pattern that this node belongs to
|
||||
AutomationPattern* m_pattern;
|
||||
|
||||
// Time position of this node (matches the timeMap key)
|
||||
int m_pos;
|
||||
|
||||
// Values of this node
|
||||
float m_inValue;
|
||||
float m_outValue;
|
||||
|
||||
// Slope at each point for calculating spline
|
||||
// We might have discrete jumps between curves, so we possibly have
|
||||
// two different tangents for each side of the curve. If inValue and
|
||||
// outValue are equal, inTangent and outTangent are equal too.
|
||||
float m_inTangent;
|
||||
float m_outTangent;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QPointer>
|
||||
|
||||
#include "AutomationNode.h"
|
||||
#include "TrackContentObject.h"
|
||||
|
||||
|
||||
@@ -49,8 +50,8 @@ public:
|
||||
CubicHermiteProgression
|
||||
} ;
|
||||
|
||||
typedef QMap<int, float> timeMap;
|
||||
typedef QVector<QPointer<AutomatableModel> > objectVector;
|
||||
typedef QMap<int, AutomationNode> timeMap;
|
||||
typedef QVector<QPointer<AutomatableModel>> objectVector;
|
||||
|
||||
AutomationPattern( AutomationTrack * _auto_track );
|
||||
AutomationPattern( const AutomationPattern & _pat_to_copy );
|
||||
@@ -77,12 +78,25 @@ public:
|
||||
TimePos timeMapLength() const;
|
||||
void updateLength();
|
||||
|
||||
TimePos putValue( const TimePos & time,
|
||||
const float value,
|
||||
const bool quantPos = true,
|
||||
const bool ignoreSurroundingPoints = true );
|
||||
TimePos putValue(
|
||||
const TimePos & time,
|
||||
const float value,
|
||||
const bool quantPos = true,
|
||||
const bool ignoreSurroundingPoints = true
|
||||
);
|
||||
|
||||
void removeValue( const TimePos & time );
|
||||
TimePos putValues(
|
||||
const TimePos & time,
|
||||
const float inValue,
|
||||
const float outValue,
|
||||
const bool quantPos = true,
|
||||
const bool ignoreSurroundingPoints = true
|
||||
);
|
||||
|
||||
void removeNode(const TimePos & time);
|
||||
void removeNodes(const int tick0, const int tick1);
|
||||
|
||||
void resetNodes(const int tick0, const int tick1);
|
||||
|
||||
void recordValue(TimePos time, float value);
|
||||
|
||||
@@ -109,16 +123,6 @@ public:
|
||||
return m_timeMap;
|
||||
}
|
||||
|
||||
inline const timeMap & getTangents() const
|
||||
{
|
||||
return m_tangents;
|
||||
}
|
||||
|
||||
inline timeMap & getTangents()
|
||||
{
|
||||
return m_tangents;
|
||||
}
|
||||
|
||||
inline float getMin() const
|
||||
{
|
||||
return firstObject()->minValue<float>();
|
||||
@@ -170,21 +174,26 @@ public slots:
|
||||
private:
|
||||
void cleanObjects();
|
||||
void generateTangents();
|
||||
void generateTangents( timeMap::const_iterator it, int numToGenerate );
|
||||
void generateTangents(timeMap::iterator it, int numToGenerate);
|
||||
float valueAt( timeMap::const_iterator v, int offset ) const;
|
||||
|
||||
// Mutex to make methods involving automation patterns thread safe
|
||||
// Mutable so we can lock it from const objects
|
||||
mutable QMutex m_patternMutex;
|
||||
|
||||
AutomationTrack * m_autoTrack;
|
||||
QVector<jo_id_t> m_idsToResolve;
|
||||
objectVector m_objects;
|
||||
timeMap m_timeMap; // actual values
|
||||
timeMap m_oldTimeMap; // old values for storing the values before setDragValue() is called.
|
||||
timeMap m_tangents; // slope at each point for calculating spline
|
||||
float m_tension;
|
||||
bool m_hasAutomation;
|
||||
ProgressionTypes m_progressionType;
|
||||
|
||||
bool m_dragging;
|
||||
|
||||
bool m_dragKeepOutValue; // Should we keep the current dragged node's outValue?
|
||||
float m_dragOutValue; // The outValue of the dragged node's
|
||||
|
||||
bool m_isRecording;
|
||||
float m_lastRecordedValue;
|
||||
|
||||
@@ -194,6 +203,7 @@ private:
|
||||
static const float DEFAULT_MAX_VALUE;
|
||||
|
||||
friend class AutomationPatternView;
|
||||
friend class AutomationNode;
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ private:
|
||||
void upgrade_1_2_0_rc3();
|
||||
void upgrade_1_3_0();
|
||||
void upgrade_noHiddenClipNames();
|
||||
void upgrade_automationNodes();
|
||||
|
||||
// List of all upgrade methods
|
||||
static const std::vector<UpgradeMethod> UPGRADE_METHODS;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#ifndef INLINE_AUTOMATION_H
|
||||
#define INLINE_AUTOMATION_H
|
||||
|
||||
#include "AutomationNode.h"
|
||||
#include "AutomationPattern.h"
|
||||
#include "shared_object.h"
|
||||
|
||||
@@ -53,12 +54,17 @@ public:
|
||||
{
|
||||
if( m_autoPattern != NULL && m_autoPattern->getTimeMap().isEmpty() == false )
|
||||
{
|
||||
// prevent saving inline automation if there's just one value which equals value
|
||||
// of model which is going to be saved anyways
|
||||
if( isAtInitValue() &&
|
||||
m_autoPattern->getTimeMap().size() == 1 &&
|
||||
m_autoPattern->getTimeMap().keys().first() == 0 &&
|
||||
m_autoPattern->getTimeMap().values().first() == value() )
|
||||
// Prevent saving inline automation if there's just one node at the beginning of
|
||||
// the pattern, which has a InValue equal to the value of model (which is going
|
||||
// to be saved anyways) and no offset between the InValue and OutValue
|
||||
AutomationPattern::timeMap::const_iterator firstNode =
|
||||
m_autoPattern->getTimeMap().begin();
|
||||
|
||||
if (isAtInitValue()
|
||||
&& m_autoPattern->getTimeMap().size() == 1
|
||||
&& POS(firstNode) == 0
|
||||
&& INVAL(firstNode) == value()
|
||||
&& OFFSET(firstNode) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
109
src/core/AutomationNode.cpp
Normal file
109
src/core/AutomationNode.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* AutomationPattern.cpp - Implementation of class AutomationNode which
|
||||
* holds information on a single automation pattern node
|
||||
*
|
||||
* Copyright (c) 2020 Ian Caio <iancaio_dev/at/hotmail.com>
|
||||
*
|
||||
* 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 "AutomationNode.h"
|
||||
#include "AutomationPattern.h"
|
||||
|
||||
|
||||
// Dummy constructor for the QMap
|
||||
AutomationNode::AutomationNode() :
|
||||
m_pattern(nullptr),
|
||||
m_pos(0),
|
||||
m_inValue(0),
|
||||
m_outValue(0),
|
||||
m_inTangent(0),
|
||||
m_outTangent(0)
|
||||
{
|
||||
}
|
||||
|
||||
AutomationNode::AutomationNode(AutomationPattern* pat, float value, int pos) :
|
||||
m_pattern(pat),
|
||||
m_pos(pos),
|
||||
m_inValue(value),
|
||||
m_outValue(value),
|
||||
m_inTangent(0),
|
||||
m_outTangent(0)
|
||||
{
|
||||
}
|
||||
|
||||
AutomationNode::AutomationNode(AutomationPattern* pat, float inValue, float outValue, int pos) :
|
||||
m_pattern(pat),
|
||||
m_pos(pos),
|
||||
m_inValue(inValue),
|
||||
m_outValue(outValue),
|
||||
m_inTangent(0),
|
||||
m_outTangent(0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the inValue of an automation node
|
||||
* @param Float value to be assigned
|
||||
*/
|
||||
void AutomationNode::setInValue(float value)
|
||||
{
|
||||
m_inValue = value;
|
||||
|
||||
// Recalculate the tangents from neighbor nodes
|
||||
AutomationPattern::timeMap & tm = m_pattern->getTimeMap();
|
||||
|
||||
// Get an iterator pointing to this node
|
||||
AutomationPattern::timeMap::iterator it = tm.lowerBound(m_pos);
|
||||
// If it's not the first node, get the one immediately behind it
|
||||
if (it != tm.begin()) { --it; }
|
||||
|
||||
// Generate tangents from the previously, current and next nodes
|
||||
m_pattern->generateTangents(it, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the outValue of an automation node
|
||||
* @param Float value to be assigned
|
||||
*/
|
||||
void AutomationNode::setOutValue(float value)
|
||||
{
|
||||
m_outValue = value;
|
||||
|
||||
// Recalculate the tangents from neighbor nodes
|
||||
AutomationPattern::timeMap & tm = m_pattern->getTimeMap();
|
||||
|
||||
// Get an iterator pointing to this node
|
||||
AutomationPattern::timeMap::iterator it = tm.lowerBound(m_pos);
|
||||
// If it's not the first node, get the one immediately behind it
|
||||
if (it != tm.begin()) { --it; }
|
||||
|
||||
// Generate tangents from the previously, current and next nodes
|
||||
m_pattern->generateTangents(it, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the outValue so it matches inValue
|
||||
*/
|
||||
void AutomationNode::resetOutValue()
|
||||
{
|
||||
// Calls setOutValue so it also takes care of generating
|
||||
// the tangents
|
||||
setOutValue(m_inValue);
|
||||
}
|
||||
@@ -26,12 +26,13 @@
|
||||
|
||||
#include "AutomationPattern.h"
|
||||
|
||||
#include "AutomationNode.h"
|
||||
#include "AutomationPatternView.h"
|
||||
#include "AutomationTrack.h"
|
||||
#include "BBTrackContainer.h"
|
||||
#include "LocaleHelper.h"
|
||||
#include "Note.h"
|
||||
#include "ProjectJournal.h"
|
||||
#include "BBTrackContainer.h"
|
||||
#include "Song.h"
|
||||
|
||||
#include <cmath>
|
||||
@@ -43,6 +44,7 @@ const float AutomationPattern::DEFAULT_MAX_VALUE = 1;
|
||||
|
||||
AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) :
|
||||
TrackContentObject( _auto_track ),
|
||||
m_patternMutex(QMutex::Recursive),
|
||||
m_autoTrack( _auto_track ),
|
||||
m_objects(),
|
||||
m_tension( 1.0 ),
|
||||
@@ -74,16 +76,21 @@ AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) :
|
||||
|
||||
AutomationPattern::AutomationPattern( const AutomationPattern & _pat_to_copy ) :
|
||||
TrackContentObject( _pat_to_copy.m_autoTrack ),
|
||||
m_patternMutex(QMutex::Recursive),
|
||||
m_autoTrack( _pat_to_copy.m_autoTrack ),
|
||||
m_objects( _pat_to_copy.m_objects ),
|
||||
m_tension( _pat_to_copy.m_tension ),
|
||||
m_progressionType( _pat_to_copy.m_progressionType )
|
||||
{
|
||||
// Locks the mutex of the copied AutomationPattern to make sure it
|
||||
// doesn't change while it's being copied
|
||||
QMutexLocker m(&_pat_to_copy.m_patternMutex);
|
||||
|
||||
for( timeMap::const_iterator it = _pat_to_copy.m_timeMap.begin();
|
||||
it != _pat_to_copy.m_timeMap.end(); ++it )
|
||||
{
|
||||
m_timeMap[it.key()] = it.value();
|
||||
m_tangents[it.key()] = _pat_to_copy.m_tangents[it.key()];
|
||||
// Copies the automation node (in/out values and in/out tangents)
|
||||
m_timeMap[POS(it)] = it.value();
|
||||
}
|
||||
switch( getTrack()->trackContainer()->type() )
|
||||
{
|
||||
@@ -101,6 +108,8 @@ AutomationPattern::AutomationPattern( const AutomationPattern & _pat_to_copy ) :
|
||||
|
||||
bool AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup )
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
if( _search_dup && m_objects.contains(_obj) )
|
||||
{
|
||||
return false;
|
||||
@@ -130,6 +139,8 @@ bool AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup )
|
||||
void AutomationPattern::setProgressionType(
|
||||
ProgressionTypes _new_progression_type )
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
if ( _new_progression_type == DiscreteProgression ||
|
||||
_new_progression_type == LinearProgression ||
|
||||
_new_progression_type == CubicHermiteProgression )
|
||||
@@ -144,6 +155,8 @@ void AutomationPattern::setProgressionType(
|
||||
|
||||
void AutomationPattern::setTension( QString _new_tension )
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
bool ok;
|
||||
float nt = LocaleHelper::toFloat(_new_tension, & ok);
|
||||
|
||||
@@ -158,18 +171,22 @@ void AutomationPattern::setTension( QString _new_tension )
|
||||
|
||||
const AutomatableModel * AutomationPattern::firstObject() const
|
||||
{
|
||||
AutomatableModel * m;
|
||||
if( !m_objects.isEmpty() && ( m = m_objects.first() ) != NULL )
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
AutomatableModel* model;
|
||||
if (!m_objects.isEmpty() && (model = m_objects.first()) != nullptr)
|
||||
{
|
||||
return m;
|
||||
return model;
|
||||
}
|
||||
|
||||
static FloatModel _fm( 0, DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, 0.001 );
|
||||
return &_fm;
|
||||
static FloatModel fm(0, DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, 0.001);
|
||||
return &fm;
|
||||
}
|
||||
|
||||
const AutomationPattern::objectVector& AutomationPattern::objects() const
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
return m_objects;
|
||||
}
|
||||
|
||||
@@ -178,11 +195,13 @@ const AutomationPattern::objectVector& AutomationPattern::objects() const
|
||||
|
||||
TimePos AutomationPattern::timeMapLength() const
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
TimePos one_bar = TimePos(1, 0);
|
||||
if (m_timeMap.isEmpty()) { return one_bar; }
|
||||
|
||||
timeMap::const_iterator it = m_timeMap.end();
|
||||
tick_t last_tick = static_cast<tick_t>((it-1).key());
|
||||
tick_t last_tick = static_cast<tick_t>(POS(it - 1));
|
||||
// if last_tick is 0 (single item at tick 0)
|
||||
// return length as a whole bar to prevent disappearing TCO
|
||||
if (last_tick == 0) { return one_bar; }
|
||||
@@ -202,34 +221,48 @@ void AutomationPattern::updateLength()
|
||||
|
||||
|
||||
|
||||
TimePos AutomationPattern::putValue( const TimePos & time,
|
||||
const float value,
|
||||
const bool quantPos,
|
||||
const bool ignoreSurroundingPoints )
|
||||
/**
|
||||
* @brief Puts an automation node on the timeMap with the given value.
|
||||
* The inValue and outValue of the created node will be the same.
|
||||
* @param TimePos time to add the node to
|
||||
* @param Float inValue and outValue of the node
|
||||
* @param Boolean True to quantize the position (defaults to true)
|
||||
* @param Boolean True to ignore unquantized surrounding nodes (defaults to true)
|
||||
* @return TimePos of the recently added automation node
|
||||
*/
|
||||
TimePos AutomationPattern::putValue(
|
||||
const TimePos & time,
|
||||
const float value,
|
||||
const bool quantPos,
|
||||
const bool ignoreSurroundingPoints
|
||||
)
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
cleanObjects();
|
||||
|
||||
TimePos newTime = quantPos ?
|
||||
Note::quantized( time, quantization() ) :
|
||||
time;
|
||||
TimePos newTime = quantPos ? Note::quantized(time, quantization()) : time;
|
||||
|
||||
m_timeMap[ newTime ] = value;
|
||||
timeMap::const_iterator it = m_timeMap.find( newTime );
|
||||
// Create a node or replace the existing one on newTime
|
||||
m_timeMap[newTime] = AutomationNode(this, value, newTime);
|
||||
|
||||
timeMap::iterator it = m_timeMap.find(newTime);
|
||||
|
||||
// Remove control points that are covered by the new points
|
||||
// quantization value. Control Key to override
|
||||
if( ! ignoreSurroundingPoints )
|
||||
if (!ignoreSurroundingPoints)
|
||||
{
|
||||
for( int i = newTime + 1; i < newTime + quantization(); ++i )
|
||||
// We need to check that to avoid removing nodes from
|
||||
// newTime + 1 to newTime (removing the node we are adding)
|
||||
if (quantization() > 1)
|
||||
{
|
||||
AutomationPattern::removeValue( i );
|
||||
// Remove nodes between the quantization points, them not
|
||||
// being included
|
||||
removeNodes(newTime + 1, newTime + quantization() - 1);
|
||||
}
|
||||
}
|
||||
if( it != m_timeMap.begin() )
|
||||
{
|
||||
--it;
|
||||
}
|
||||
generateTangents( it, 3 );
|
||||
if (it != m_timeMap.begin()) { --it; }
|
||||
generateTangents(it, 3);
|
||||
|
||||
updateLength();
|
||||
|
||||
@@ -241,13 +274,69 @@ TimePos AutomationPattern::putValue( const TimePos & time,
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::removeValue( const TimePos & time )
|
||||
/**
|
||||
* @brief Puts an automation node on the timeMap with the given inValue
|
||||
* and outValue.
|
||||
* @param TimePos time to add the node to
|
||||
* @param Float inValue of the node
|
||||
* @param Float outValue of the node
|
||||
* @param Boolean True to quantize the position (defaults to true)
|
||||
* @param Boolean True to ignore unquantized surrounding nodes (defaults to true)
|
||||
* @return TimePos of the recently added automation node
|
||||
*/
|
||||
TimePos AutomationPattern::putValues(
|
||||
const TimePos & time,
|
||||
const float inValue,
|
||||
const float outValue,
|
||||
const bool quantPos,
|
||||
const bool ignoreSurroundingPoints
|
||||
)
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
cleanObjects();
|
||||
|
||||
TimePos newTime = quantPos ? Note::quantized(time, quantization()) : time;
|
||||
|
||||
// Create a node or replace the existing one on newTime
|
||||
m_timeMap[newTime] = AutomationNode(this, inValue, outValue, newTime);
|
||||
|
||||
timeMap::iterator it = m_timeMap.find(newTime);
|
||||
|
||||
// Remove control points that are covered by the new points
|
||||
// quantization value. Control Key to override
|
||||
if (!ignoreSurroundingPoints)
|
||||
{
|
||||
// We need to check that to avoid removing nodes from
|
||||
// newTime + 1 to newTime (removing the node we are adding)
|
||||
if (quantization() > 1)
|
||||
{
|
||||
// Remove nodes between the quantization points, them not
|
||||
// being included
|
||||
removeNodes(newTime + 1, newTime + quantization() - 1);
|
||||
}
|
||||
}
|
||||
if (it != m_timeMap.begin()) { --it; }
|
||||
generateTangents(it, 3);
|
||||
|
||||
updateLength();
|
||||
|
||||
emit dataChanged();
|
||||
|
||||
return newTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::removeNode(const TimePos & time)
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
cleanObjects();
|
||||
|
||||
m_timeMap.remove( time );
|
||||
m_tangents.remove( time );
|
||||
timeMap::const_iterator it = m_timeMap.lowerBound( time );
|
||||
timeMap::iterator it = m_timeMap.lowerBound(time);
|
||||
if( it != m_timeMap.begin() )
|
||||
{
|
||||
--it;
|
||||
@@ -261,8 +350,72 @@ void AutomationPattern::removeValue( const TimePos & time )
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Removes all automation nodes between the given ticks
|
||||
* @param Int first tick of the range
|
||||
* @param Int second tick of the range
|
||||
*/
|
||||
void AutomationPattern::removeNodes(const int tick0, const int tick1)
|
||||
{
|
||||
if (tick0 == tick1)
|
||||
{
|
||||
removeNode(TimePos(tick0));
|
||||
return;
|
||||
}
|
||||
|
||||
TimePos start = TimePos(qMin(tick0, tick1));
|
||||
TimePos end = TimePos(qMax(tick0, tick1));
|
||||
|
||||
// Make a list of TimePos with nodes to be removed
|
||||
// because we can't simply remove the nodes from
|
||||
// the timeMap while we are iterating it.
|
||||
QVector<TimePos> nodesToRemove;
|
||||
|
||||
for (auto it = m_timeMap.lowerBound(start), endIt = m_timeMap.upperBound(end); it != endIt; ++it)
|
||||
{
|
||||
nodesToRemove.append(POS(it));
|
||||
}
|
||||
|
||||
for (auto node: nodesToRemove)
|
||||
{
|
||||
removeNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Resets the outValues of all automation nodes between the given ticks
|
||||
* @param Int first tick of the range
|
||||
* @param Int second tick of the range
|
||||
*/
|
||||
void AutomationPattern::resetNodes(const int tick0, const int tick1)
|
||||
{
|
||||
if (tick0 == tick1)
|
||||
{
|
||||
auto it = m_timeMap.find(TimePos(tick0));
|
||||
if (it != m_timeMap.end()) { it.value().resetOutValue(); }
|
||||
return;
|
||||
}
|
||||
|
||||
TimePos start = TimePos(qMin(tick0, tick1));
|
||||
TimePos end = TimePos(qMax(tick0, tick1));
|
||||
|
||||
for (auto it = m_timeMap.lowerBound(start), endIt = m_timeMap.upperBound(end); it != endIt; ++it)
|
||||
{
|
||||
it.value().resetOutValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::recordValue(TimePos time, float value)
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
if( value != m_lastRecordedValue )
|
||||
{
|
||||
putValue( time, value, true );
|
||||
@@ -270,7 +423,7 @@ void AutomationPattern::recordValue(TimePos time, float value)
|
||||
}
|
||||
else if( valueAt( time ) != value )
|
||||
{
|
||||
removeValue( time );
|
||||
removeNode(time);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,24 +432,44 @@ void AutomationPattern::recordValue(TimePos time, float value)
|
||||
|
||||
/**
|
||||
* @brief Set the position of the point that is being dragged.
|
||||
* Calling this function will also automatically set m_dragging to true,
|
||||
* which applyDragValue() have to be called to m_dragging.
|
||||
* @param the time(x position) of the point being dragged
|
||||
* @param the value(y position) of the point being dragged
|
||||
* @param true to snip x position
|
||||
* @return
|
||||
* Calling this function will also automatically set m_dragging to true.
|
||||
* When applyDragValue() is called, m_dragging is set back to false.
|
||||
* @param TimePos of the node being dragged
|
||||
* @param Float with the value to assign to the point being dragged
|
||||
* @param Boolean. True to snip x position
|
||||
* @param Boolean. True to ignore unquantized surrounding nodes
|
||||
* @return TimePos with current time of the dragged value
|
||||
*/
|
||||
TimePos AutomationPattern::setDragValue( const TimePos & time,
|
||||
const float value,
|
||||
const bool quantPos,
|
||||
const bool controlKey )
|
||||
TimePos AutomationPattern::setDragValue(
|
||||
const TimePos & time,
|
||||
const float value,
|
||||
const bool quantPos,
|
||||
const bool controlKey
|
||||
)
|
||||
{
|
||||
if( m_dragging == false )
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
if (m_dragging == false)
|
||||
{
|
||||
TimePos newTime = quantPos ?
|
||||
Note::quantized( time, quantization() ) :
|
||||
time;
|
||||
this->removeValue( newTime );
|
||||
TimePos newTime = quantPos ? Note::quantized(time, quantization()) : time;
|
||||
|
||||
// We will keep the same outValue only if it's different from the
|
||||
// inValue
|
||||
m_dragKeepOutValue = false;
|
||||
|
||||
// Check if we already have a node on the position we are dragging
|
||||
// and if we do, store the outValue so the discrete jump can be kept
|
||||
timeMap::iterator it = m_timeMap.find(newTime);
|
||||
if (it != m_timeMap.end())
|
||||
{
|
||||
if (OFFSET(it) != 0)
|
||||
{
|
||||
m_dragKeepOutValue = true;
|
||||
m_dragOutValue = OUTVAL(it);
|
||||
}
|
||||
}
|
||||
|
||||
this->removeNode(newTime);
|
||||
m_oldTimeMap = m_timeMap;
|
||||
m_dragging = true;
|
||||
}
|
||||
@@ -304,13 +477,14 @@ TimePos AutomationPattern::setDragValue( const TimePos & time,
|
||||
//Restore to the state before it the point were being dragged
|
||||
m_timeMap = m_oldTimeMap;
|
||||
|
||||
for( timeMap::const_iterator it = m_timeMap.begin(); it != m_timeMap.end(); ++it )
|
||||
generateTangents();
|
||||
|
||||
if (m_dragKeepOutValue)
|
||||
{
|
||||
generateTangents( it, 3 );
|
||||
return this->putValues(time, value, m_dragOutValue, quantPos, controlKey);
|
||||
}
|
||||
|
||||
return this->putValue( time, value, quantPos, controlKey );
|
||||
|
||||
return this->putValue(time, value, quantPos, controlKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -321,6 +495,8 @@ TimePos AutomationPattern::setDragValue( const TimePos & time,
|
||||
*/
|
||||
void AutomationPattern::applyDragValue()
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
m_dragging = false;
|
||||
}
|
||||
|
||||
@@ -329,19 +505,24 @@ void AutomationPattern::applyDragValue()
|
||||
|
||||
float AutomationPattern::valueAt( const TimePos & _time ) const
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
if( m_timeMap.isEmpty() )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if( m_timeMap.contains( _time ) )
|
||||
// If we have a node at that time, just return its value
|
||||
if (m_timeMap.contains(_time))
|
||||
{
|
||||
return m_timeMap[_time];
|
||||
// When the time is exactly the node's time, we want the inValue
|
||||
return m_timeMap[_time].getInValue();
|
||||
}
|
||||
|
||||
// lowerBound returns next value with greater key, therefore we take
|
||||
// the previous element to get the current value
|
||||
timeMap::ConstIterator v = m_timeMap.lowerBound( _time );
|
||||
// lowerBound returns next value with equal or greater key. Since we already
|
||||
// checked if the key contains a node, we know the returned node has a greater
|
||||
// key than _time. Therefore we take the previous element to calculate the current value
|
||||
timeMap::const_iterator v = m_timeMap.lowerBound(_time);
|
||||
|
||||
if( v == m_timeMap.begin() )
|
||||
{
|
||||
@@ -349,26 +530,37 @@ float AutomationPattern::valueAt( const TimePos & _time ) const
|
||||
}
|
||||
if( v == m_timeMap.end() )
|
||||
{
|
||||
return (v-1).value();
|
||||
// When the time is after the last node, we want the outValue of it
|
||||
return OUTVAL(v - 1);
|
||||
}
|
||||
|
||||
return valueAt( v-1, _time - (v-1).key() );
|
||||
return valueAt(v - 1, _time - POS(v - 1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// This method will get the value at an offset from a node, so we use the outValue of
|
||||
// that node and the inValue of the next node for the calculations.
|
||||
float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const
|
||||
{
|
||||
if( m_progressionType == DiscreteProgression || v == m_timeMap.end() )
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
// We never use it with offset 0, but doesn't hurt to return a correct
|
||||
// value if we do
|
||||
if (offset == 0) { return INVAL(v); }
|
||||
|
||||
if (m_progressionType == DiscreteProgression)
|
||||
{
|
||||
return v.value();
|
||||
return OUTVAL(v);
|
||||
}
|
||||
else if( m_progressionType == LinearProgression )
|
||||
{
|
||||
float slope = ((v+1).value() - v.value()) /
|
||||
((v+1).key() - v.key());
|
||||
return v.value() + offset * slope;
|
||||
float slope =
|
||||
(INVAL(v + 1) - OUTVAL(v))
|
||||
/ (POS(v + 1) - POS(v));
|
||||
|
||||
return OUTVAL(v) + offset * slope;
|
||||
}
|
||||
else /* CubicHermiteProgression */
|
||||
{
|
||||
@@ -380,15 +572,17 @@ float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const
|
||||
// value: y. To make this work we map the values of x that this
|
||||
// segment spans to values of t for t = 0.0 -> 1.0 and scale the
|
||||
// tangents _m1 and _m2
|
||||
int numValues = ((v+1).key() - v.key());
|
||||
int numValues = (POS(v + 1) - POS(v));
|
||||
float t = (float) offset / (float) numValues;
|
||||
float m1 = (m_tangents[v.key()]) * numValues * m_tension;
|
||||
float m2 = (m_tangents[(v+1).key()]) * numValues * m_tension;
|
||||
float m1 = OUTTAN(v) * numValues * m_tension;
|
||||
float m2 = INTAN(v + 1) * numValues * m_tension;
|
||||
|
||||
return ( 2*pow(t,3) - 3*pow(t,2) + 1 ) * v.value()
|
||||
+ ( pow(t,3) - 2*pow(t,2) + t) * m1
|
||||
+ ( -2*pow(t,3) + 3*pow(t,2) ) * (v+1).value()
|
||||
+ ( pow(t,3) - pow(t,2) ) * m2;
|
||||
auto t2 = pow(t, 2);
|
||||
auto t3 = pow(t, 3);
|
||||
return (2 * t3 - 3 * t2 + 1) * OUTVAL(v)
|
||||
+ (t3 - 2 * t2 + t) * m1
|
||||
+ (-2 * t3 + 3 * t2) * INVAL(v + 1)
|
||||
+ (t3 - t2) * m2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,13 +591,15 @@ float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const
|
||||
|
||||
float *AutomationPattern::valuesAfter( const TimePos & _time ) const
|
||||
{
|
||||
timeMap::ConstIterator v = m_timeMap.lowerBound( _time );
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
timeMap::const_iterator v = m_timeMap.lowerBound(_time);
|
||||
if( v == m_timeMap.end() || (v+1) == m_timeMap.end() )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int numValues = (v+1).key() - v.key();
|
||||
int numValues = POS(v + 1) - POS(v);
|
||||
float *ret = new float[numValues];
|
||||
|
||||
for( int i = 0; i < numValues; i++ )
|
||||
@@ -417,36 +613,31 @@ float *AutomationPattern::valuesAfter( const TimePos & _time ) const
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::flipY( int min, int max )
|
||||
void AutomationPattern::flipY(int min, int max)
|
||||
{
|
||||
timeMap tempMap = m_timeMap;
|
||||
timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
|
||||
float tempValue = 0;
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
int numPoints = 0;
|
||||
bool changedTimeMap = false;
|
||||
|
||||
for( int i = 0; ( iterate + i + 1 ) != m_timeMap.end() && ( iterate + i ) != m_timeMap.end() ; i++)
|
||||
for (auto it = m_timeMap.begin(); it != m_timeMap.end(); ++it)
|
||||
{
|
||||
numPoints++;
|
||||
// Get distance from IN/OUT values to max value
|
||||
float inValDist = max - INVAL(it);
|
||||
float outValDist = max - OUTVAL(it);
|
||||
|
||||
// To flip, that will be the new distance between
|
||||
// the IN/OUT values and the min value
|
||||
it.value().setInValue(min + inValDist);
|
||||
it.value().setOutValue(min + outValDist);
|
||||
|
||||
changedTimeMap = true;
|
||||
}
|
||||
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
if (changedTimeMap)
|
||||
{
|
||||
|
||||
if ( min < 0 )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() ) * -1;
|
||||
putValue( TimePos( (iterate + i).key() ) , tempValue, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempValue = max - valueAt( ( iterate + i ).key() );
|
||||
putValue( TimePos( (iterate + i).key() ) , tempValue, false);
|
||||
}
|
||||
generateTangents();
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
generateTangents();
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -460,69 +651,98 @@ void AutomationPattern::flipY()
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::flipX( int length )
|
||||
void AutomationPattern::flipX(int length)
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
timeMap::const_iterator it = m_timeMap.lowerBound(0);
|
||||
|
||||
if (it == m_timeMap.end()) { return; }
|
||||
|
||||
// Temporary map where we will store the flipped version
|
||||
// of our pattern
|
||||
timeMap tempMap;
|
||||
|
||||
timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
|
||||
float tempValue = 0;
|
||||
int numPoints = 0;
|
||||
float tempOutValue = 0;
|
||||
|
||||
for( int i = 0; ( iterate + i + 1 ) != m_timeMap.end() && ( iterate + i ) != m_timeMap.end() ; i++)
|
||||
// We know the QMap isn't empty, making this safe:
|
||||
float realLength = m_timeMap.lastKey();
|
||||
|
||||
// If we have a positive length, we want to flip the area covered by that
|
||||
// length, even if it goes beyond the pattern. A negative length means that
|
||||
// we just want to flip the nodes we have
|
||||
if (length >= 0 && length != realLength)
|
||||
{
|
||||
numPoints++;
|
||||
}
|
||||
|
||||
float realLength = ( iterate + numPoints ).key();
|
||||
|
||||
if ( length != -1 && length != realLength)
|
||||
{
|
||||
if ( realLength < length )
|
||||
// If length to be flipped is bigger than the real length
|
||||
if (realLength < length)
|
||||
{
|
||||
tempValue = valueAt( ( iterate + numPoints ).key() );
|
||||
putValue( TimePos( length ) , tempValue, false);
|
||||
numPoints++;
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
// We are flipping an area that goes beyond the last node. So we add a node to the
|
||||
// beginning of the flipped timeMap representing the value of the end of the area
|
||||
tempValue = valueAt(length);
|
||||
tempMap[0] = AutomationNode(this, tempValue, 0);
|
||||
|
||||
// Now flip the nodes we have in relation to the length
|
||||
do
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
TimePos newTime = TimePos( length - ( iterate + i ).key() );
|
||||
tempMap[newTime] = tempValue;
|
||||
}
|
||||
// We swap the inValue and outValue when flipping horizontally
|
||||
tempValue = OUTVAL(it);
|
||||
tempOutValue = INVAL(it);
|
||||
TimePos newTime = TimePos(length - POS(it));
|
||||
|
||||
tempMap[newTime] = AutomationNode(this, tempValue, tempOutValue, newTime);
|
||||
|
||||
++it;
|
||||
} while (it != m_timeMap.end());
|
||||
}
|
||||
else
|
||||
else // If the length to be flipped is smaller than the real length
|
||||
{
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
do
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
TimePos newTime;
|
||||
|
||||
if ( ( iterate + i ).key() <= length )
|
||||
// Only flips the length to be flipped and keep the remaining values in place
|
||||
// We also only swap the inValue and outValue if we are flipping the node
|
||||
if (POS(it) <= length)
|
||||
{
|
||||
newTime = TimePos( length - ( iterate + i ).key() );
|
||||
newTime = length - POS(it);
|
||||
tempValue = OUTVAL(it);
|
||||
tempOutValue = INVAL(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
newTime = TimePos( ( iterate + i ).key() );
|
||||
newTime = POS(it);
|
||||
tempValue = INVAL(it);
|
||||
tempOutValue = OUTVAL(it);
|
||||
}
|
||||
tempMap[newTime] = tempValue;
|
||||
}
|
||||
|
||||
tempMap[newTime] = AutomationNode(this, tempValue, tempOutValue, newTime);
|
||||
|
||||
++it;
|
||||
} while (it != m_timeMap.end());
|
||||
}
|
||||
}
|
||||
else
|
||||
else // Length to be flipped is the same as the real length
|
||||
{
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
do
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
cleanObjects();
|
||||
TimePos newTime = TimePos( realLength - ( iterate + i ).key() );
|
||||
tempMap[newTime] = tempValue;
|
||||
}
|
||||
// Swap the inValue and outValue
|
||||
tempValue = OUTVAL(it);
|
||||
tempOutValue = INVAL(it);
|
||||
|
||||
TimePos newTime = TimePos(realLength - POS(it));
|
||||
tempMap[newTime] = AutomationNode(this, tempValue, tempOutValue, newTime);
|
||||
|
||||
++it;
|
||||
} while (it != m_timeMap.end());
|
||||
}
|
||||
|
||||
m_timeMap.clear();
|
||||
|
||||
m_timeMap = tempMap;
|
||||
|
||||
cleanObjects();
|
||||
|
||||
generateTangents();
|
||||
emit dataChanged();
|
||||
}
|
||||
@@ -532,6 +752,8 @@ void AutomationPattern::flipX( int length )
|
||||
|
||||
void AutomationPattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
_this.setAttribute( "pos", startPosition() );
|
||||
_this.setAttribute( "len", length() );
|
||||
_this.setAttribute( "name", name() );
|
||||
@@ -548,8 +770,9 @@ void AutomationPattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
it != m_timeMap.end(); ++it )
|
||||
{
|
||||
QDomElement element = _doc.createElement( "time" );
|
||||
element.setAttribute( "pos", it.key() );
|
||||
element.setAttribute( "value", it.value() );
|
||||
element.setAttribute("pos", POS(it));
|
||||
element.setAttribute("value", INVAL(it));
|
||||
element.setAttribute("outValue", OUTVAL(it));
|
||||
_this.appendChild( element );
|
||||
}
|
||||
|
||||
@@ -571,6 +794,8 @@ void AutomationPattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
|
||||
void AutomationPattern::loadSettings( const QDomElement & _this )
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
clear();
|
||||
|
||||
movePosition( _this.attribute( "pos" ).toInt() );
|
||||
@@ -590,8 +815,11 @@ void AutomationPattern::loadSettings( const QDomElement & _this )
|
||||
}
|
||||
if( element.tagName() == "time" )
|
||||
{
|
||||
m_timeMap[element.attribute( "pos" ).toInt()]
|
||||
= LocaleHelper::toFloat(element.attribute("value"));
|
||||
int timeMapPos = element.attribute("pos").toInt();
|
||||
float timeMapInValue = LocaleHelper::toFloat(element.attribute("value"));
|
||||
float timeMapOutValue = LocaleHelper::toFloat(element.attribute("outValue"));
|
||||
|
||||
m_timeMap[timeMapPos] = AutomationNode(this, timeMapInValue, timeMapOutValue, timeMapPos);
|
||||
}
|
||||
else if( element.tagName() == "object" )
|
||||
{
|
||||
@@ -623,6 +851,8 @@ void AutomationPattern::loadSettings( const QDomElement & _this )
|
||||
|
||||
const QString AutomationPattern::name() const
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
if( !TrackContentObject::name().isEmpty() )
|
||||
{
|
||||
return TrackContentObject::name();
|
||||
@@ -639,6 +869,8 @@ const QString AutomationPattern::name() const
|
||||
|
||||
TrackContentObjectView * AutomationPattern::createView( TrackView * _tv )
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
return new AutomationPatternView( this, _tv );
|
||||
}
|
||||
|
||||
@@ -679,8 +911,9 @@ bool AutomationPattern::isAutomated( const AutomatableModel * _m )
|
||||
}
|
||||
|
||||
|
||||
/*! \brief returns a list of all the automation patterns everywhere that are connected to a specific model
|
||||
* \param _m the model we want to look for
|
||||
/**
|
||||
* @brief returns a list of all the automation patterns that are connected to a specific model
|
||||
* @param _m the model we want to look for
|
||||
*/
|
||||
QVector<AutomationPattern *> AutomationPattern::patternsForModel( const AutomatableModel * _m )
|
||||
{
|
||||
@@ -817,8 +1050,9 @@ void AutomationPattern::resolveAllIDs()
|
||||
|
||||
void AutomationPattern::clear()
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
m_timeMap.clear();
|
||||
m_tangents.clear();
|
||||
|
||||
emit dataChanged();
|
||||
}
|
||||
@@ -828,6 +1062,8 @@ void AutomationPattern::clear()
|
||||
|
||||
void AutomationPattern::objectDestroyed( jo_id_t _id )
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
// TODO: distict between temporary removal (e.g. LADSPA controls
|
||||
// when switching samplerate) and real deletions because in the latter
|
||||
// case we had to remove ourselves if we're the global automation
|
||||
@@ -854,6 +1090,8 @@ void AutomationPattern::objectDestroyed( jo_id_t _id )
|
||||
|
||||
void AutomationPattern::cleanObjects()
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
for( objectVector::iterator it = m_objects.begin(); it != m_objects.end(); )
|
||||
{
|
||||
if( *it )
|
||||
@@ -878,12 +1116,18 @@ void AutomationPattern::generateTangents()
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::generateTangents( timeMap::const_iterator it,
|
||||
int numToGenerate )
|
||||
// We have two tangents, one for the left side of the node and one for the right side
|
||||
// of the node (in case we have discrete value jumps in the middle of a curve).
|
||||
// If the inValue and outValue of a node are the same, consequently the inTangent and
|
||||
// outTangent values of the node will be the same too.
|
||||
void AutomationPattern::generateTangents(timeMap::iterator it, int numToGenerate)
|
||||
{
|
||||
QMutexLocker m(&m_patternMutex);
|
||||
|
||||
if( m_timeMap.size() < 2 && numToGenerate > 0 )
|
||||
{
|
||||
m_tangents[it.key()] = 0;
|
||||
it.value().setInTangent(0);
|
||||
it.value().setOutTangent(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -891,26 +1135,48 @@ void AutomationPattern::generateTangents( timeMap::const_iterator it,
|
||||
{
|
||||
if( it == m_timeMap.begin() )
|
||||
{
|
||||
m_tangents[it.key()] =
|
||||
( (it+1).value() - (it).value() ) /
|
||||
( (it+1).key() - (it).key() );
|
||||
// On the first node there's no curve behind it, so we will only calculate the outTangent
|
||||
// and inTangent will be set to 0.
|
||||
float tangent = (INVAL(it + 1) - OUTVAL(it)) / (POS(it + 1) - POS(it));
|
||||
it.value().setInTangent(0);
|
||||
it.value().setOutTangent(tangent);
|
||||
}
|
||||
else if( it+1 == m_timeMap.end() )
|
||||
{
|
||||
m_tangents[it.key()] = 0;
|
||||
// Previously, the last value's tangent was always set to 0. That logic was kept for both tangents
|
||||
// of the last node
|
||||
it.value().setInTangent(0);
|
||||
it.value().setOutTangent(0);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tangents[it.key()] =
|
||||
( (it+1).value() - (it-1).value() ) /
|
||||
( (it+1).key() - (it-1).key() );
|
||||
// When we are in a node that is in the middle of two other nodes, we need to check if we
|
||||
// have a discrete jump at this node. If we do not, then we can calculate the tangents normally.
|
||||
// If we do have a discrete jump, then we have to calculate the tangents differently for each side
|
||||
// of the curve.
|
||||
// TODO: This behavior means that a very small difference between the inValue and outValue can
|
||||
// result in a big change in the curve. In the future, allowing the user to manually adjust
|
||||
// the tangents would be better.
|
||||
float inTangent;
|
||||
float outTangent;
|
||||
if (OFFSET(it) == 0)
|
||||
{
|
||||
inTangent = (INVAL(it + 1) - OUTVAL(it - 1)) / (POS(it + 1) - POS(it - 1));
|
||||
it.value().setInTangent(inTangent);
|
||||
// inTangent == outTangent in this case
|
||||
it.value().setOutTangent(inTangent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calculate the left side of the curve
|
||||
inTangent = (INVAL(it) - OUTVAL(it - 1)) / (POS(it) - POS(it - 1));
|
||||
// Calculate the right side of the curve
|
||||
outTangent = (INVAL(it + 1) - OUTVAL(it)) / (POS(it + 1) - POS(it));
|
||||
it.value().setInTangent(inTangent);
|
||||
it.value().setOutTangent(outTangent);
|
||||
}
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ set(LMMS_SRCS
|
||||
|
||||
core/AutomatableModel.cpp
|
||||
core/AutomationPattern.cpp
|
||||
core/AutomationNode.cpp
|
||||
core/BandLimitedWave.cpp
|
||||
core/base64.cpp
|
||||
core/BBTrackContainer.cpp
|
||||
|
||||
@@ -59,7 +59,8 @@ const std::vector<DataFile::UpgradeMethod> DataFile::UPGRADE_METHODS = {
|
||||
&DataFile::upgrade_0_4_0_beta1 , &DataFile::upgrade_0_4_0_rc2,
|
||||
&DataFile::upgrade_1_0_99 , &DataFile::upgrade_1_1_0,
|
||||
&DataFile::upgrade_1_1_91 , &DataFile::upgrade_1_2_0_rc3,
|
||||
&DataFile::upgrade_1_3_0 , &DataFile::upgrade_noHiddenClipNames
|
||||
&DataFile::upgrade_1_3_0 , &DataFile::upgrade_noHiddenClipNames,
|
||||
&DataFile::upgrade_automationNodes
|
||||
};
|
||||
|
||||
// Vector of all versions that have upgrade routines.
|
||||
@@ -1385,6 +1386,32 @@ void DataFile::upgrade_noHiddenClipNames()
|
||||
}
|
||||
}
|
||||
|
||||
void DataFile::upgrade_automationNodes()
|
||||
{
|
||||
QDomNodeList autoPatterns = elementsByTagName("automationpattern");
|
||||
|
||||
// Go through all automation patterns
|
||||
for (int i = 0; i < autoPatterns.size(); ++i)
|
||||
{
|
||||
QDomElement autoPattern = autoPatterns.item(i).toElement();
|
||||
|
||||
// On each automation pattern, get all <time> elements
|
||||
QDomNodeList times = autoPattern.elementsByTagName("time");
|
||||
|
||||
// Loop through all <time> elements and change what we need
|
||||
for (int j=0; j < times.size(); ++j)
|
||||
{
|
||||
QDomElement el = times.item(j).toElement();
|
||||
|
||||
float value = LocaleHelper::toFloat(el.attribute("value"));
|
||||
|
||||
// inValue will be equal to "value" and outValue will
|
||||
// be set to the same
|
||||
el.setAttribute("outValue", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DataFile::upgrade()
|
||||
{
|
||||
|
||||
@@ -306,47 +306,54 @@ void AutomationPatternView::paintEvent( QPaintEvent * )
|
||||
{
|
||||
if( it+1 == m_pat->getTimeMap().end() )
|
||||
{
|
||||
const float x1 = x_base + it.key() * ppTick;
|
||||
const float x1 = x_base + POS(it) * ppTick;
|
||||
const float x2 = (float)( width() - TCO_BORDER_WIDTH );
|
||||
if( x1 > ( width() - TCO_BORDER_WIDTH ) ) break;
|
||||
// We are drawing the space after the last node, so we use the outValue
|
||||
if( gradient() )
|
||||
{
|
||||
p.fillRect( QRectF( x1, 0.0f, x2 - x1, it.value() ), lin2grad );
|
||||
p.fillRect(QRectF(x1, 0.0f, x2 - x1, OUTVAL(it)), lin2grad);
|
||||
}
|
||||
else
|
||||
{
|
||||
p.fillRect( QRectF( x1, 0.0f, x2 - x1, it.value() ), col );
|
||||
p.fillRect(QRectF(x1, 0.0f, x2 - x1, OUTVAL(it)), col);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
float *values = m_pat->valuesAfter( it.key() );
|
||||
float *values = m_pat->valuesAfter(POS(it));
|
||||
|
||||
// We are creating a path to draw a polygon representing the values between two
|
||||
// nodes. When we have two nodes with discrete progression, we will basically have
|
||||
// a rectangle with the outValue of the first node (that's why nextValue will match
|
||||
// the outValue of the current node). When we have nodes with linear or cubic progression
|
||||
// the value of the end of the shape between the two nodes will be the inValue of
|
||||
// the next node.
|
||||
float nextValue;
|
||||
if( m_pat->progressionType() == AutomationPattern::DiscreteProgression )
|
||||
{
|
||||
nextValue = it.value();
|
||||
nextValue = OUTVAL(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
nextValue = ( it + 1 ).value();
|
||||
nextValue = INVAL(it + 1);
|
||||
}
|
||||
|
||||
QPainterPath path;
|
||||
QPointF origin = QPointF( x_base + it.key() * ppTick, 0.0f );
|
||||
QPointF origin = QPointF(x_base + POS(it) * ppTick, 0.0f);
|
||||
path.moveTo( origin );
|
||||
path.moveTo( QPointF( x_base + it.key() * ppTick,values[0] ) );
|
||||
path.moveTo(QPointF(x_base + POS(it) * ppTick,values[0]));
|
||||
float x;
|
||||
for( int i = it.key() + 1; i < ( it + 1 ).key(); i++ )
|
||||
for (int i = POS(it) + 1; i < POS(it + 1); i++)
|
||||
{
|
||||
x = x_base + i * ppTick;
|
||||
if( x > ( width() - TCO_BORDER_WIDTH ) ) break;
|
||||
float value = values[ i - it.key() ];
|
||||
float value = values[i - POS(it)];
|
||||
path.lineTo( QPointF( x, value ) );
|
||||
|
||||
}
|
||||
path.lineTo( x_base + ( ( it + 1 ).key() ) * ppTick, nextValue );
|
||||
path.lineTo( x_base + ( ( it + 1 ).key() ) * ppTick, 0.0f );
|
||||
path.lineTo(x_base + (POS(it + 1)) * ppTick, nextValue);
|
||||
path.lineTo(x_base + (POS(it + 1)) * ppTick, 0.0f);
|
||||
path.lineTo( origin );
|
||||
|
||||
if( gradient() )
|
||||
@@ -474,18 +481,27 @@ void AutomationPatternView::scaleTimemapToFit( float oldMin, float oldMax )
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Currently when rescaling the timeMap values to fit the new range of values (newMin and newMax)
|
||||
// only the inValue is being considered and the outValue is being reset to the inValue (so discrete jumps
|
||||
// are discarded). Possibly later we will want discrete jumps to be maintained so we will need to upgrade
|
||||
// the logic to account for them.
|
||||
for( AutomationPattern::timeMap::iterator it = m_pat->m_timeMap.begin();
|
||||
it != m_pat->m_timeMap.end(); ++it )
|
||||
{
|
||||
if( *it < oldMin )
|
||||
// If the values are out of the previous range, fix them so they are
|
||||
// between oldMin and oldMax.
|
||||
if (INVAL(it) < oldMin)
|
||||
{
|
||||
*it = oldMin;
|
||||
it.value().setInValue(oldMin);
|
||||
}
|
||||
else if( *it > oldMax )
|
||||
else if (INVAL(it) > oldMax)
|
||||
{
|
||||
*it = oldMax;
|
||||
it.value().setInValue(oldMax);
|
||||
}
|
||||
*it = (*it-oldMin)*(newMax-newMin)/(oldMax-oldMin)+newMin;
|
||||
// Calculate what the value would be proportionally in the new range
|
||||
it.value().setInValue((INVAL(it) - oldMin) * (newMax - newMin) / (oldMax - oldMin) + newMin);
|
||||
// Read earlier TODO comment: For now I'm discarding the discrete jumps during the rescaling
|
||||
it.value().setOutValue(INVAL(it));
|
||||
}
|
||||
|
||||
m_pat->generateTangents();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -987,39 +987,71 @@ void PianoRoll::drawDetuningInfo( QPainter & _p, const Note * _n, int _x,
|
||||
width() - m_whiteKeyWidth,
|
||||
keyAreaBottom() - PR_TOP_MARGIN);
|
||||
|
||||
// Draw lines for the detuning automation, treating cubic hermit curves
|
||||
// as straight lines for now. Also draw discrete jumps.
|
||||
int old_x = 0;
|
||||
int old_y = 0;
|
||||
|
||||
timeMap & map = _n->detuning()->automationPattern()->getTimeMap();
|
||||
for( timeMap::ConstIterator it = map.begin(); it != map.end(); ++it )
|
||||
for (timeMap::const_iterator it = map.begin(); it != map.end(); ++it)
|
||||
{
|
||||
int pos_ticks = it.key();
|
||||
int pos_x = _x + pos_ticks * m_ppb / TimePos::ticksPerBar();
|
||||
// Current node values
|
||||
int cur_ticks = POS(it);
|
||||
int cur_x = _x + cur_ticks * m_ppb / TimePos::ticksPerBar();
|
||||
const float cur_level = INVAL(it);
|
||||
int cur_y = middle_y - cur_level * m_keyLineHeight;
|
||||
|
||||
const float level = it.value();
|
||||
|
||||
int pos_y = middle_y - level * m_keyLineHeight;
|
||||
|
||||
if( old_x != 0 && old_y != 0 )
|
||||
// First line to represent the inValue of the first node
|
||||
if (it == map.begin())
|
||||
{
|
||||
switch( _n->detuning()->automationPattern()->progressionType() )
|
||||
_p.drawLine(cur_x - 1, cur_y, cur_x + 1, cur_y);
|
||||
_p.drawLine(cur_x, cur_y - 1, cur_x, cur_y + 1);
|
||||
}
|
||||
// All subsequent lines will take the outValue of the previous node
|
||||
// and the inValue of the current node. It will also draw a vertical
|
||||
// line if there was a discrete jump (from old_x,old_y to pre_x,pre_y)
|
||||
else
|
||||
{
|
||||
// Previous node values (based on outValue). We just calculate
|
||||
// the y level because the x will be the same as old_x.
|
||||
const float pre_level = OUTVAL(it - 1);
|
||||
int pre_y = middle_y - pre_level * m_keyLineHeight;
|
||||
|
||||
// Draws the line representing the discrete jump if there's one
|
||||
if (old_y != pre_y)
|
||||
{
|
||||
case AutomationPattern::DiscreteProgression:
|
||||
_p.drawLine( old_x, old_y, pos_x, old_y );
|
||||
_p.drawLine( pos_x, old_y, pos_x, pos_y );
|
||||
break;
|
||||
case AutomationPattern::CubicHermiteProgression: /* TODO */
|
||||
case AutomationPattern::LinearProgression:
|
||||
_p.drawLine( old_x, old_y, pos_x, pos_y );
|
||||
break;
|
||||
_p.drawLine(old_x, old_y, old_x, pre_y);
|
||||
}
|
||||
|
||||
// Now draw the lines representing the actual progression from one
|
||||
// node to the other
|
||||
switch (_n->detuning()->automationPattern()->progressionType())
|
||||
{
|
||||
case AutomationPattern::DiscreteProgression:
|
||||
_p.drawLine(old_x, pre_y, cur_x, pre_y);
|
||||
_p.drawLine(cur_x, pre_y, cur_x, cur_y);
|
||||
break;
|
||||
case AutomationPattern::CubicHermiteProgression: /* TODO */
|
||||
case AutomationPattern::LinearProgression:
|
||||
_p.drawLine(old_x, pre_y, cur_x, cur_y);
|
||||
break;
|
||||
}
|
||||
|
||||
// If we are in the last node and there's a discrete jump, we draw a
|
||||
// vertical line representing it
|
||||
if ((it + 1) == map.end())
|
||||
{
|
||||
const float last_level = OUTVAL(it);
|
||||
if (cur_level != last_level)
|
||||
{
|
||||
int last_y = middle_y - last_level * m_keyLineHeight;
|
||||
_p.drawLine(cur_x, cur_y, cur_x, last_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_p.drawLine( pos_x - 1, pos_y, pos_x + 1, pos_y );
|
||||
_p.drawLine( pos_x, pos_y - 1, pos_x, pos_y + 1 );
|
||||
|
||||
old_x = pos_x;
|
||||
old_y = pos_y;
|
||||
old_x = cur_x;
|
||||
old_y = cur_y;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user