Compare commits
2 Commits
new-i18n
...
feature/be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c21d0b4d8 | ||
|
|
0dfdbc956a |
BIN
data/themes/classic/progression_bezier.png
Normal file
BIN
data/themes/classic/progression_bezier.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 677 B |
BIN
data/themes/default/progression_bezier.png
Normal file
BIN
data/themes/default/progression_bezier.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 409 B |
@@ -56,6 +56,7 @@ class AutomationEditor : public QWidget, public JournallingObject
|
||||
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(QColor controlPointColor READ controlPointColor WRITE setControlPointColor)
|
||||
Q_PROPERTY(QBrush scaleColor READ scaleColor WRITE setScaleColor)
|
||||
Q_PROPERTY(QBrush graphColor READ graphColor WRITE setGraphColor)
|
||||
Q_PROPERTY(QColor crossColor READ crossColor WRITE setCrossColor)
|
||||
@@ -91,6 +92,8 @@ public:
|
||||
void setGraphColor(const QBrush & c);
|
||||
QColor vertexColor() const;
|
||||
void setVertexColor(const QColor & c);
|
||||
QColor controlPointColor() const;
|
||||
void setControlPointColor(const QColor& c);
|
||||
QBrush scaleColor() const;
|
||||
void setScaleColor(const QBrush & c);
|
||||
QColor crossColor() const;
|
||||
@@ -113,6 +116,7 @@ public slots:
|
||||
|
||||
protected:
|
||||
typedef AutomationPattern::timeMap timeMap;
|
||||
typedef AutomationPattern::controlPointTimeMap controlPointTimeMap;
|
||||
|
||||
void keyPressEvent(QKeyEvent * ke) override;
|
||||
void leaveEvent(QEvent * e) override;
|
||||
@@ -167,6 +171,7 @@ private:
|
||||
{
|
||||
NONE,
|
||||
MOVE_VALUE,
|
||||
MOVE_CONTROL_POINT,
|
||||
SELECT_VALUES,
|
||||
MOVE_SELECTION
|
||||
} ;
|
||||
@@ -249,6 +254,7 @@ private:
|
||||
|
||||
void drawCross(QPainter & p );
|
||||
void drawAutomationPoint( QPainter & p, timeMap::iterator it );
|
||||
void drawControlPoint( QPainter & p, controlPointTimeMap::iterator it, float key_y );
|
||||
bool inBBEditor();
|
||||
|
||||
QColor m_barLineColor;
|
||||
@@ -256,6 +262,7 @@ private:
|
||||
QColor m_lineColor;
|
||||
QBrush m_graphColor;
|
||||
QColor m_vertexColor;
|
||||
QColor m_controlPointColor;
|
||||
QBrush m_scaleColor;
|
||||
QColor m_crossColor;
|
||||
QColor m_backgroundShade;
|
||||
@@ -313,6 +320,7 @@ private:
|
||||
QAction* m_discreteAction;
|
||||
QAction* m_linearAction;
|
||||
QAction* m_cubicHermiteAction;
|
||||
QAction* m_bezierAction;
|
||||
|
||||
QAction* m_flipYAction;
|
||||
QAction* m_flipXAction;
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
#ifndef AUTOMATION_PATTERN_H
|
||||
#define AUTOMATION_PATTERN_H
|
||||
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
#include <QPointer>
|
||||
|
||||
#include "TrackContentObject.h"
|
||||
|
||||
@@ -46,10 +47,12 @@ public:
|
||||
{
|
||||
DiscreteProgression,
|
||||
LinearProgression,
|
||||
CubicHermiteProgression
|
||||
CubicHermiteProgression,
|
||||
BezierProgression
|
||||
} ;
|
||||
|
||||
typedef QMap<int, float> timeMap;
|
||||
typedef QMap<int, QPair<int, float> > controlPointTimeMap;
|
||||
typedef QVector<QPointer<AutomatableModel> > objectVector;
|
||||
|
||||
AutomationPattern( AutomationTrack * _auto_track );
|
||||
@@ -82,6 +85,15 @@ public:
|
||||
const bool quantPos = true,
|
||||
const bool ignoreSurroundingPoints = true );
|
||||
|
||||
TimePos putControlPoint( timeMap::const_iterator it,
|
||||
const float _value);
|
||||
|
||||
TimePos putControlPoint(timeMap::const_iterator it,
|
||||
const int time, const float _value);
|
||||
|
||||
TimePos putControlPoint(timeMap::const_iterator it,
|
||||
const int time, const float _value, const bool flip);
|
||||
|
||||
void removeValue( const TimePos & time );
|
||||
|
||||
void recordValue(TimePos time, float value);
|
||||
@@ -91,8 +103,13 @@ public:
|
||||
const bool quantPos = true,
|
||||
const bool controlKey = false );
|
||||
|
||||
TimePos setControlPointDragValue( const TimePos & _time, const float _value, const int _x,
|
||||
const bool _quant_pos = true );
|
||||
|
||||
void applyDragValue();
|
||||
|
||||
void flipControlPoint(bool flip);
|
||||
|
||||
|
||||
bool isDragging() const
|
||||
{
|
||||
@@ -119,6 +136,27 @@ public:
|
||||
return m_tangents;
|
||||
}
|
||||
|
||||
inline const controlPointTimeMap & getControlPoints() const
|
||||
{
|
||||
return m_controlPoints;
|
||||
}
|
||||
|
||||
inline controlPointTimeMap & getControlPoints()
|
||||
{
|
||||
return m_controlPoints;
|
||||
}
|
||||
|
||||
// This is for getting the node of the control point that is being dragged
|
||||
inline const timeMap::ConstIterator & getControlPointNode() const
|
||||
{
|
||||
return m_oldControlPointNode;
|
||||
}
|
||||
|
||||
inline timeMap::ConstIterator & getControlPointNode()
|
||||
{
|
||||
return m_oldControlPointNode;
|
||||
}
|
||||
|
||||
inline float getMin() const
|
||||
{
|
||||
return firstObject()->minValue<float>();
|
||||
@@ -160,6 +198,8 @@ public:
|
||||
static int quantization() { return s_quantization; }
|
||||
static void setQuantization(int q) { s_quantization = q; }
|
||||
|
||||
void clampControlPoints(bool clampVertical=true);
|
||||
|
||||
public slots:
|
||||
void clear();
|
||||
void objectDestroyed( jo_id_t );
|
||||
@@ -169,6 +209,7 @@ public slots:
|
||||
|
||||
private:
|
||||
void cleanObjects();
|
||||
void cleanControlPoints();
|
||||
void generateTangents();
|
||||
void generateTangents( timeMap::const_iterator it, int numToGenerate );
|
||||
float valueAt( timeMap::const_iterator v, int offset ) const;
|
||||
@@ -179,12 +220,20 @@ private:
|
||||
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
|
||||
controlPointTimeMap m_controlPoints; // control points for calculating the bezier curve
|
||||
controlPointTimeMap m_oldControlPoints; // old values for storing the values before setDragValue() is called.
|
||||
// m_oldControlPoints is similar to m_oldTimeMap, since the control points need to be dragged as well or something
|
||||
timeMap::const_iterator m_oldControlPointNode; // Which automation point was the control point connected to?
|
||||
bool m_controlFlip; // If the lefthand control point is grabbed, the value must be flipped around the automation point
|
||||
|
||||
float m_controlPointDragOffset[2];
|
||||
|
||||
float m_tension;
|
||||
bool m_hasAutomation;
|
||||
ProgressionTypes m_progressionType;
|
||||
|
||||
bool m_dragging;
|
||||
|
||||
|
||||
bool m_isRecording;
|
||||
float m_lastRecordedValue;
|
||||
|
||||
|
||||
@@ -132,7 +132,8 @@ void AutomationPattern::setProgressionType(
|
||||
{
|
||||
if ( _new_progression_type == DiscreteProgression ||
|
||||
_new_progression_type == LinearProgression ||
|
||||
_new_progression_type == CubicHermiteProgression )
|
||||
_new_progression_type == CubicHermiteProgression ||
|
||||
_new_progression_type == BezierProgression )
|
||||
{
|
||||
m_progressionType = _new_progression_type;
|
||||
emit dataChanged();
|
||||
@@ -202,6 +203,47 @@ void AutomationPattern::updateLength()
|
||||
|
||||
|
||||
|
||||
TimePos AutomationPattern::putControlPoint( timeMap::const_iterator it,
|
||||
const int time, const float _value, const bool flip )
|
||||
{
|
||||
if (flip)
|
||||
{
|
||||
putControlPoint( it, 2 * it.key() - time, 2 * it.value() - _value );
|
||||
}
|
||||
else
|
||||
{
|
||||
putControlPoint( it, time, _value );
|
||||
}
|
||||
return it.key();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* If we are only given the value and automation point
|
||||
then figure out where to put the control point */
|
||||
TimePos AutomationPattern::putControlPoint(timeMap::const_iterator it,
|
||||
const float _value)
|
||||
{
|
||||
// Insert control point at the automation point
|
||||
return putControlPoint( it, (float)it.key() + 50, _value );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TimePos AutomationPattern::putControlPoint(timeMap::const_iterator it,
|
||||
const int time, const float _value)
|
||||
{
|
||||
m_controlPoints.remove( it.key() );
|
||||
m_controlPoints[it.key()] = {time, _value};
|
||||
clampControlPoints();
|
||||
return it.key();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TimePos AutomationPattern::putValue( const TimePos & time,
|
||||
const float value,
|
||||
const bool quantPos,
|
||||
@@ -225,6 +267,9 @@ TimePos AutomationPattern::putValue( const TimePos & time,
|
||||
AutomationPattern::removeValue( i );
|
||||
}
|
||||
}
|
||||
putControlPoint(it, value);
|
||||
clampControlPoints();
|
||||
|
||||
if( it != m_timeMap.begin() )
|
||||
{
|
||||
--it;
|
||||
@@ -247,6 +292,7 @@ void AutomationPattern::removeValue( const TimePos & time )
|
||||
|
||||
m_timeMap.remove( time );
|
||||
m_tangents.remove( time );
|
||||
m_controlPoints.remove( time );
|
||||
timeMap::const_iterator it = m_timeMap.lowerBound( time );
|
||||
if( it != m_timeMap.begin() )
|
||||
{
|
||||
@@ -291,26 +337,82 @@ TimePos AutomationPattern::setDragValue( const TimePos & time,
|
||||
const bool quantPos,
|
||||
const bool controlKey )
|
||||
{
|
||||
//cleanControlPoints();
|
||||
if( m_dragging == false )
|
||||
{
|
||||
TimePos newTime = quantPos ?
|
||||
Note::quantized( time, quantization() ) :
|
||||
time;
|
||||
|
||||
if ( m_timeMap.contains( newTime ) )
|
||||
{
|
||||
// Set the offset for the control point, so it gets dragged around with the automation point
|
||||
m_controlPointDragOffset[0] = (float)m_controlPoints[newTime].first - (float)newTime;
|
||||
m_controlPointDragOffset[1] = m_controlPoints[newTime].second - m_timeMap[newTime];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_controlPointDragOffset[0] = 50;
|
||||
m_controlPointDragOffset[1] = 0;
|
||||
}
|
||||
|
||||
this->removeValue( newTime );
|
||||
m_oldTimeMap = m_timeMap;
|
||||
m_oldControlPoints = m_controlPoints;
|
||||
m_dragging = true;
|
||||
}
|
||||
|
||||
//Restore to the state before it the point were being dragged
|
||||
m_timeMap = m_oldTimeMap;
|
||||
m_controlPoints = m_oldControlPoints;
|
||||
|
||||
for( timeMap::const_iterator it = m_timeMap.begin(); it != m_timeMap.end(); ++it )
|
||||
{
|
||||
generateTangents( it, 3 );
|
||||
}
|
||||
|
||||
return this->putValue( time, value, quantPos, controlKey );
|
||||
// Put the new automation point down
|
||||
TimePos returnValue = this->putValue( time, value, quantPos, controlKey );
|
||||
// Put a new control point down at an offset
|
||||
m_controlPoints.remove( returnValue );
|
||||
putControlPoint(m_timeMap.find( returnValue ), (float)returnValue + m_controlPointDragOffset[0],
|
||||
value + m_controlPointDragOffset[1]);
|
||||
clampControlPoints();
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TimePos AutomationPattern::setControlPointDragValue( const TimePos & _time, const float _value, const int _x,
|
||||
const bool _quant_pos)
|
||||
{
|
||||
if( m_dragging == false )
|
||||
{
|
||||
TimePos newTime = _quant_pos ?
|
||||
Note::quantized( _time, quantization() ) :
|
||||
_time;
|
||||
m_controlPoints.remove( newTime );
|
||||
m_oldControlPointNode = m_timeMap.find( newTime );
|
||||
m_dragging = true;
|
||||
}
|
||||
|
||||
return this->putControlPoint(m_oldControlPointNode, _x, _value, m_controlFlip);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @breif If the control point grabbed is on the left of the automation point,
|
||||
* be flipped in order to get the control points actual location.
|
||||
* @param should the value be flipped or not
|
||||
*/
|
||||
void AutomationPattern::flipControlPoint(bool flip)
|
||||
{
|
||||
m_controlFlip = flip;
|
||||
}
|
||||
|
||||
|
||||
@@ -370,7 +472,7 @@ float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const
|
||||
((v+1).key() - v.key());
|
||||
return v.value() + offset * slope;
|
||||
}
|
||||
else /* CubicHermiteProgression */
|
||||
else if( m_progressionType == CubicHermiteProgression )
|
||||
{
|
||||
// Implements a Cubic Hermite spline as explained at:
|
||||
// http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Unit_interval_.280.2C_1.29
|
||||
@@ -390,6 +492,55 @@ float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const
|
||||
+ ( -2*pow(t,3) + 3*pow(t,2) ) * (v+1).value()
|
||||
+ ( pow(t,3) - pow(t,2) ) * m2;
|
||||
}
|
||||
else /* BezierProgression */
|
||||
{
|
||||
|
||||
/* Formula goes as such:
|
||||
Automation points: (x0, y0), (x3, y3)
|
||||
Relative control points: (x1, y1), (x2, y2)
|
||||
Where the control points are BETWEEN the automation points.
|
||||
(This isn't the case in this program, so the second control point must be "flipped"
|
||||
around its automation point)
|
||||
|
||||
x = ( (1-t)^3 * x0 ) + ( 3 * (1-t)^2 * t * x1 ) + ( 3 * (1-t) * t^2 * x2 ) + ( t^3 * x3 )
|
||||
y = ( (1-t)^3 * y0 ) + ( 3 * (1-t)^2 * t * y1 ) + ( 3 * (1-t) * t^2 * y2 ) + ( t^3 * y3 )
|
||||
|
||||
0 <= t <= 1
|
||||
*/
|
||||
|
||||
int numValues = (v+1).key() - v.key();
|
||||
|
||||
// The x values are essentially twice the distance from their control points
|
||||
// to make up for their range being limited.
|
||||
int targetX1 = ( m_controlPoints[v.key()].first - v.key() ) * 2;
|
||||
int targetX2 = ( 3 * (v+1).key() - 2 * m_controlPoints[(v+1).key()].first - v.key() );
|
||||
// The y values are the actual y values. Maybe this should be doubled,
|
||||
// but it doesn't seem necessary to me.
|
||||
float targetY1 = m_controlPoints[v.key()].second;
|
||||
float targetY2 = 2*(v+1).value() - m_controlPoints[(v+1).key()].second;
|
||||
|
||||
// To find the y value on the curve at a certain x, we first have to find the t (between 0 and 1) that gives the x
|
||||
float t = 0;
|
||||
float x = 3 * pow((1-t), 2) * t * targetX1 + 3 * (1-t) * pow(t, 2) * targetX2 + pow(t, 3) * numValues;
|
||||
while (offset > x)
|
||||
{
|
||||
t += 0.05;
|
||||
x = 3 * pow((1-t), 2) * t * targetX1 + 3 * (1-t) * pow(t, 2) * targetX2 + pow(t, 3) * numValues;
|
||||
}
|
||||
|
||||
float ratio = x;
|
||||
float y1 = pow((1-t),3) * v.value() + 3 * pow((1-t),2) * t * targetY1 +
|
||||
3 * (1-t) * pow(t,2) * targetY2 + pow(t,3) * (v+1).value();
|
||||
t -= 0.05;
|
||||
float y2 = pow((1-t),3) * v.value() + 3 * pow((1-t),2) * t * targetY1 +
|
||||
3 * (1-t) * pow(t,2) * targetY2 + pow(t,3) * (v+1).value();
|
||||
x = 3 * pow((1-t), 2) * t * targetX1 + 3 * (1-t) * pow(t, 2) * targetX2 + pow(t, 3) * numValues;
|
||||
|
||||
// Ratio is how we get the linear extrapolation between points
|
||||
// We have to get the ratio of how close this point is to its left compared to right
|
||||
ratio = (offset - x) / (ratio - x);
|
||||
return (ratio)*y1 + (1-ratio)*y2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -437,11 +588,15 @@ void AutomationPattern::flipY( int min, int max )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() ) * -1;
|
||||
putValue( TimePos( (iterate + i).key() ) , tempValue, false);
|
||||
tempValue = m_controlPoints[(iterate + i).key()].second * -1;
|
||||
m_controlPoints[(iterate + i).key()].second = tempValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
tempValue = max - valueAt( ( iterate + i ).key() );
|
||||
putValue( TimePos( (iterate + i).key() ) , tempValue, false);
|
||||
tempValue = max - m_controlPoints[(iterate + i).key()].second;
|
||||
m_controlPoints[(iterate + i).key()].second = tempValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,6 +618,7 @@ void AutomationPattern::flipY()
|
||||
void AutomationPattern::flipX( int length )
|
||||
{
|
||||
timeMap tempMap;
|
||||
controlPointTimeMap tempControlPoints;
|
||||
|
||||
timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
|
||||
float tempValue = 0;
|
||||
@@ -486,6 +642,11 @@ void AutomationPattern::flipX( int length )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
TimePos newTime = TimePos( length - ( iterate + i ).key() );
|
||||
|
||||
int newControlPointX = -( iterate + i ).key() + m_controlPoints[( iterate + i ).key()].first + newTime;
|
||||
tempControlPoints[newTime] = {newControlPointX,
|
||||
2*tempValue - m_controlPoints[( iterate + i ).key()].second};
|
||||
|
||||
tempMap[newTime] = tempValue;
|
||||
}
|
||||
}
|
||||
@@ -496,6 +657,10 @@ void AutomationPattern::flipX( int length )
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
TimePos newTime;
|
||||
|
||||
int newControlPointX = -( iterate + i ).key() + m_controlPoints[( iterate + i ).key()].first + newTime;
|
||||
tempControlPoints[newTime] = {newControlPointX,
|
||||
2*tempValue - m_controlPoints[( iterate + i ).key()].second};
|
||||
|
||||
if ( ( iterate + i ).key() <= length )
|
||||
{
|
||||
newTime = TimePos( length - ( iterate + i ).key() );
|
||||
@@ -515,13 +680,19 @@ void AutomationPattern::flipX( int length )
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
cleanObjects();
|
||||
TimePos newTime = TimePos( realLength - ( iterate + i ).key() );
|
||||
int newControlPointX = -( iterate + i ).key() + m_controlPoints[( iterate + i ).key()].first + newTime;
|
||||
|
||||
tempMap[newTime] = tempValue;
|
||||
tempControlPoints[newTime] = {newControlPointX,
|
||||
2*tempValue - m_controlPoints[( iterate + i ).key()].second};
|
||||
}
|
||||
}
|
||||
|
||||
m_timeMap.clear();
|
||||
m_controlPoints.clear();
|
||||
|
||||
m_timeMap = tempMap;
|
||||
m_controlPoints = tempControlPoints;
|
||||
|
||||
generateTangents();
|
||||
emit dataChanged();
|
||||
@@ -553,6 +724,16 @@ void AutomationPattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
_this.appendChild( element );
|
||||
}
|
||||
|
||||
for( controlPointTimeMap::const_iterator it = m_controlPoints.begin();
|
||||
it != m_controlPoints.end(); ++it )
|
||||
{
|
||||
QDomElement element = _doc.createElement( "ctrlpnt" );
|
||||
element.setAttribute( "pos", it.key() );
|
||||
element.setAttribute( "value1", it.value().first );
|
||||
element.setAttribute( "value2", it.value().second );
|
||||
_this.appendChild( element );
|
||||
}
|
||||
|
||||
for( objectVector::const_iterator it = m_objects.begin();
|
||||
it != m_objects.end(); ++it )
|
||||
{
|
||||
@@ -593,6 +774,11 @@ void AutomationPattern::loadSettings( const QDomElement & _this )
|
||||
m_timeMap[element.attribute( "pos" ).toInt()]
|
||||
= LocaleHelper::toFloat(element.attribute("value"));
|
||||
}
|
||||
else if( element.tagName() == "ctrlpnt" )
|
||||
{
|
||||
m_controlPoints[element.attribute( "pos" ).toInt()] = {element.attribute( "value1" ).toInt(),
|
||||
element.attribute( "value2" ).toFloat()};
|
||||
}
|
||||
else if( element.tagName() == "object" )
|
||||
{
|
||||
m_idsToResolve << element.attribute( "id" ).toInt();
|
||||
@@ -616,6 +802,9 @@ void AutomationPattern::loadSettings( const QDomElement & _this )
|
||||
changeLength( len );
|
||||
}
|
||||
generateTangents();
|
||||
|
||||
// Very important for reading older files
|
||||
cleanControlPoints();
|
||||
}
|
||||
|
||||
|
||||
@@ -637,6 +826,62 @@ const QString AutomationPattern::name() const
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::clampControlPoints(bool clampVertical)
|
||||
{
|
||||
timeMap::const_iterator it;
|
||||
for (it = m_timeMap.begin(); it != m_timeMap.end(); it++)
|
||||
{
|
||||
int new_x = m_controlPoints[it.key()].first;
|
||||
float new_y = m_controlPoints[it.key()].second;
|
||||
// Clamp X positions
|
||||
// If the control point x is less than its automation point
|
||||
if ( it.key() > new_x )
|
||||
{
|
||||
new_x = it.key();
|
||||
}
|
||||
// The control point x must not pass the midpoints of its automation point and the automation points
|
||||
// its left and right
|
||||
else if ( it != m_timeMap.begin() && it.key() * 2 - new_x < ( (it-1).key() + it.key() ) / 2 )
|
||||
{
|
||||
new_x = it.key() * 2 - ( (it-1).key() + it.key() )/2;
|
||||
}
|
||||
else if ( it+1 != m_timeMap.end() && new_x > ( (it+1).key() + it.key() )/2 )
|
||||
{
|
||||
new_x = ( (it+1).key() + it.key() )/2;
|
||||
}
|
||||
|
||||
if (clampVertical)
|
||||
{
|
||||
// Clamp y positions between the top and bottom of the screen
|
||||
// Clamp the right control point (keep in mind the last control point isn't clamped)
|
||||
if ( it+1 != m_timeMap.end() && new_y > getMax() )
|
||||
{
|
||||
new_y = getMax();
|
||||
}
|
||||
else if ( it+1 != m_timeMap.end() && new_y < getMin() )
|
||||
{
|
||||
new_y = getMin();
|
||||
}
|
||||
// Clamp the left control point (keep in mind the first control point isn't clamped)
|
||||
if ( it != m_timeMap.begin() && 2 * it.value() - new_y > getMax() )
|
||||
{
|
||||
new_y = 2 * it.value() - getMax();
|
||||
}
|
||||
else if ( it != m_timeMap.begin() && 2 * it.value() - new_y < getMin() )
|
||||
{
|
||||
new_y = 2 * it.value() - getMin();
|
||||
}
|
||||
}
|
||||
|
||||
m_controlPoints.remove( it.key() );
|
||||
|
||||
m_controlPoints[it.key()] = {new_x, new_y};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TrackContentObjectView * AutomationPattern::createView( TrackView * _tv )
|
||||
{
|
||||
return new AutomationPatternView( this, _tv );
|
||||
@@ -818,6 +1063,7 @@ void AutomationPattern::resolveAllIDs()
|
||||
void AutomationPattern::clear()
|
||||
{
|
||||
m_timeMap.clear();
|
||||
m_controlPoints.clear();
|
||||
m_tangents.clear();
|
||||
|
||||
emit dataChanged();
|
||||
@@ -870,6 +1116,40 @@ void AutomationPattern::cleanObjects()
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::cleanControlPoints()
|
||||
{
|
||||
// If there's any control points that aren't connected to an automation point then destroy it
|
||||
for( controlPointTimeMap::iterator it = m_controlPoints.begin(); it != m_controlPoints.end(); )
|
||||
{
|
||||
if(m_timeMap.contains( (int)it.key()) )
|
||||
{
|
||||
it++;
|
||||
}
|
||||
else
|
||||
{
|
||||
it = m_controlPoints.erase( it );
|
||||
}
|
||||
}
|
||||
|
||||
// If there's any automation points without a control point then insert control points
|
||||
for( timeMap::iterator it = m_timeMap.begin(); it != m_timeMap.end(); )
|
||||
{
|
||||
if(m_controlPoints.contains( (int)it.key()) )
|
||||
{
|
||||
it++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_controlPoints[it.key()] = {it.key() + 50, it.value()};
|
||||
}
|
||||
}
|
||||
|
||||
clampControlPoints(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::generateTangents()
|
||||
{
|
||||
generateTangents(m_timeMap.begin(), m_timeMap.size());
|
||||
|
||||
@@ -106,6 +106,7 @@ AutomationEditor::AutomationEditor() :
|
||||
m_lineColor( 0, 0, 0 ),
|
||||
m_graphColor( Qt::SolidPattern ),
|
||||
m_vertexColor( 0,0,0 ),
|
||||
m_controlPointColor( 0xFF,0xFF,0x25 ),
|
||||
m_scaleColor( Qt::SolidPattern ),
|
||||
m_crossColor( 0, 0, 0 ),
|
||||
m_backgroundShade( 0, 0, 0 )
|
||||
@@ -283,6 +284,12 @@ QColor AutomationEditor::vertexColor() const
|
||||
void AutomationEditor::setVertexColor( const QColor & c )
|
||||
{ m_vertexColor = c; }
|
||||
|
||||
QColor AutomationEditor::controlPointColor() const
|
||||
{ return m_controlPointColor; }
|
||||
|
||||
void AutomationEditor::setControlPointColor( const QColor & c )
|
||||
{ m_controlPointColor = c; }
|
||||
|
||||
QBrush AutomationEditor::scaleColor() const
|
||||
{ return m_scaleColor; }
|
||||
|
||||
@@ -515,14 +522,48 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent )
|
||||
|
||||
// get time map of current pattern
|
||||
timeMap & time_map = m_pattern->getTimeMap();
|
||||
controlPointTimeMap & control_points = m_pattern->getControlPoints();
|
||||
|
||||
// will be our iterator in the following loop
|
||||
timeMap::iterator it = time_map.begin();
|
||||
|
||||
// If there is a control point at the mouse
|
||||
int controlPoint = 0;
|
||||
|
||||
// loop through whole time-map...
|
||||
while( it != time_map.end() )
|
||||
{
|
||||
// and check whether the user clicked on an
|
||||
// If this automation point has its control point at the mouse
|
||||
// And the progression type is bezier
|
||||
// P.S. I'm not entirely sure how the automation clicks work below
|
||||
// so I'm just doing it my own way
|
||||
if ( m_pattern->progressionType() == AutomationPattern::BezierProgression &&
|
||||
mouseEvent->button() == Qt::LeftButton &&
|
||||
m_editMode == DRAW &&
|
||||
yCoordOfLevel(level) <= yCoordOfLevel(control_points[it.key()].second) + 16 &&
|
||||
yCoordOfLevel(level) >= yCoordOfLevel(control_points[it.key()].second) - 16 &&
|
||||
xCoordOfTick(pos_ticks) <= xCoordOfTick(control_points[it.key()].first) + 16 &&
|
||||
xCoordOfTick(pos_ticks) >= xCoordOfTick(control_points[it.key()].first) - 16 )
|
||||
{
|
||||
controlPoint = true;
|
||||
m_pattern->flipControlPoint(false);
|
||||
break;
|
||||
}
|
||||
// This is to get the left control point
|
||||
else if ( m_pattern->progressionType() == AutomationPattern::BezierProgression &&
|
||||
mouseEvent->button() == Qt::LeftButton &&
|
||||
m_editMode == DRAW &&
|
||||
yCoordOfLevel(level) <= yCoordOfLevel(2 * it.value() - control_points[it.key()].second) + 16 &&
|
||||
yCoordOfLevel(level) >= yCoordOfLevel(2 * it.value() - control_points[it.key()].second) - 16 &&
|
||||
xCoordOfTick(pos_ticks) <= xCoordOfTick(2 * it.key() - control_points[it.key()].first) + 16 &&
|
||||
xCoordOfTick(pos_ticks) >= xCoordOfTick(2 * it.key() - control_points[it.key()].first) - 16 )
|
||||
{
|
||||
controlPoint = true;
|
||||
m_pattern->flipControlPoint(true);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check whether the user clicked on an
|
||||
// existing value
|
||||
if( pos_ticks >= it.key() &&
|
||||
( it+1==time_map.end() ||
|
||||
@@ -560,9 +601,10 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent )
|
||||
m_drawLastTick = pos_ticks;
|
||||
m_drawLastLevel = level;
|
||||
|
||||
m_action = MOVE_VALUE;
|
||||
// did it reach end of map because
|
||||
// there's no value??
|
||||
if( it == time_map.end() )
|
||||
// there's no value?? (And no control point)
|
||||
if( it == time_map.end() && !controlPoint)
|
||||
{
|
||||
// then set new value
|
||||
TimePos value_pos( pos_ticks );
|
||||
@@ -573,6 +615,24 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent )
|
||||
mouseEvent->modifiers() &
|
||||
Qt::ControlModifier );
|
||||
|
||||
|
||||
// reset it so that it can be used for
|
||||
// ops (move, resize) after this
|
||||
// code-block
|
||||
it = time_map.find( new_time );
|
||||
}
|
||||
else if ( controlPoint )
|
||||
{
|
||||
// then set new value
|
||||
TimePos value_pos( pos_ticks );
|
||||
|
||||
TimePos new_time =
|
||||
m_pattern->setControlPointDragValue( it.key(),
|
||||
level, value_pos );
|
||||
|
||||
m_action = MOVE_CONTROL_POINT;
|
||||
|
||||
|
||||
// reset it so that it can be used for
|
||||
// ops (move, resize) after this
|
||||
// code-block
|
||||
@@ -580,7 +640,7 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent )
|
||||
}
|
||||
|
||||
// move it
|
||||
m_action = MOVE_VALUE;
|
||||
|
||||
int aligned_x = (int)( (float)( (
|
||||
it.key() -
|
||||
m_currentPosition ) *
|
||||
@@ -676,7 +736,7 @@ void AutomationEditor::mouseReleaseEvent(QMouseEvent * mouseEvent )
|
||||
|
||||
if( m_editMode == DRAW )
|
||||
{
|
||||
if( m_action == MOVE_VALUE )
|
||||
if( m_action == MOVE_VALUE || m_action == MOVE_CONTROL_POINT )
|
||||
{
|
||||
m_pattern->applyDragValue();
|
||||
}
|
||||
@@ -773,6 +833,23 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent )
|
||||
mouseEvent->modifiers() &
|
||||
Qt::ControlModifier );
|
||||
}
|
||||
else if( m_action == MOVE_CONTROL_POINT )
|
||||
{
|
||||
// moving value
|
||||
if( pos_ticks < 0 )
|
||||
{
|
||||
pos_ticks = 0;
|
||||
}
|
||||
|
||||
m_drawLastTick = pos_ticks;
|
||||
m_drawLastLevel = level;
|
||||
// we moved the value so the value has to be
|
||||
// moved properly according to new starting-
|
||||
// time in the time map of pattern
|
||||
m_pattern->setControlPointDragValue(TimePos( pos_ticks ),
|
||||
level, TimePos( pos_ticks ));
|
||||
m_pattern->clampControlPoints();
|
||||
}
|
||||
|
||||
Engine::getSong()->setModified();
|
||||
|
||||
@@ -1132,6 +1209,28 @@ inline void AutomationEditor::drawAutomationPoint( QPainter & p, timeMap::iterat
|
||||
|
||||
|
||||
|
||||
inline void AutomationEditor::drawControlPoint( QPainter & p, controlPointTimeMap::iterator it , float key_y )
|
||||
{
|
||||
// The x and y of the "real" point (to the right of the automation point)
|
||||
int x = xCoordOfTick( it.value().first );
|
||||
int y = yCoordOfLevel( it.value().second );
|
||||
// The x and y of the "fake" point (to the left of the automation point)
|
||||
const int outerRadius = qBound( 2, ( m_ppb * AutomationPattern::quantization() ) / 576, 5 );
|
||||
p.setPen( QPen( controlPointColor().lighter( 200 ) ) );
|
||||
p.setBrush( QBrush( controlPointColor() ) );
|
||||
p.drawEllipse( x - outerRadius, y - outerRadius, outerRadius * 2, outerRadius * 2 );
|
||||
p.setPen( controlPointColor() );
|
||||
p.drawLine( x, y, xCoordOfTick( it.key() ), yCoordOfLevel( key_y ) );
|
||||
// Oh look! An easy way to draw the line out the other side of the control point!
|
||||
int reflected_x = 2*xCoordOfTick( it.key() ) - x;
|
||||
int reflected_y = 2*yCoordOfLevel( key_y ) - y;
|
||||
p.drawLine( reflected_x, reflected_y, xCoordOfTick( it.key() ), yCoordOfLevel( key_y ) );
|
||||
p.drawEllipse( reflected_x - outerRadius, reflected_y - outerRadius, outerRadius * 2, outerRadius * 2 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationEditor::paintEvent(QPaintEvent * pe )
|
||||
{
|
||||
QMutexLocker m( &m_patternMutex );
|
||||
@@ -1355,6 +1454,7 @@ void AutomationEditor::paintEvent(QPaintEvent * pe )
|
||||
//NEEDS Change in CSS
|
||||
//int len_ticks = 4;
|
||||
timeMap & time_map = m_pattern->getTimeMap();
|
||||
controlPointTimeMap & controlPoint_time_map = m_pattern->getControlPoints();
|
||||
|
||||
//Don't bother doing/rendering anything if there is no automation points
|
||||
if( time_map.size() > 0 )
|
||||
@@ -1443,6 +1543,38 @@ void AutomationEditor::paintEvent(QPaintEvent * pe )
|
||||
// Draw circle(the last one)
|
||||
drawAutomationPoint(p, it);
|
||||
}
|
||||
|
||||
//Don't bother doing/rendering anything if there is no control points or if it's not a bezier curve
|
||||
if( controlPoint_time_map.size() > 0 && m_pattern->progressionType() == AutomationPattern::BezierProgression)
|
||||
{
|
||||
// Now we've drawn the automation points, we should draw the control points
|
||||
controlPointTimeMap::iterator it = controlPoint_time_map.begin();
|
||||
while( it+1 != controlPoint_time_map.end() )
|
||||
{
|
||||
// skip this section if it occurs completely before the
|
||||
// visible area
|
||||
int next_x = xCoordOfTick( (it+1).key() );
|
||||
if( next_x < 0 )
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
int x = xCoordOfTick( it.key() );
|
||||
if( x > width() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Draw circle
|
||||
drawControlPoint( p, it, time_map[it.key()] );
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
// Draw circle(the last one)
|
||||
drawControlPoint(p, it, time_map[it.key()] );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2297,6 +2429,8 @@ AutomationEditorWindow::AutomationEditorWindow() :
|
||||
embed::getIconPixmap("progression_linear"), tr("Linear progression"));
|
||||
m_cubicHermiteAction = progression_type_group->addAction(
|
||||
embed::getIconPixmap("progression_cubic_hermite"), tr( "Cubic Hermite progression"));
|
||||
m_bezierAction = progression_type_group->addAction(
|
||||
embed::getIconPixmap("progression_bezier"), tr( "Bezier progression"));
|
||||
|
||||
connect(progression_type_group, SIGNAL(triggered(int)), m_editor, SLOT(setProgressionType(int)));
|
||||
|
||||
@@ -2311,6 +2445,7 @@ AutomationEditorWindow::AutomationEditorWindow() :
|
||||
interpolationActionsToolBar->addAction(m_discreteAction);
|
||||
interpolationActionsToolBar->addAction(m_linearAction);
|
||||
interpolationActionsToolBar->addAction(m_cubicHermiteAction);
|
||||
interpolationActionsToolBar->addAction(m_bezierAction);
|
||||
interpolationActionsToolBar->addSeparator();
|
||||
interpolationActionsToolBar->addWidget( new QLabel( tr("Tension: "), interpolationActionsToolBar ));
|
||||
interpolationActionsToolBar->addWidget( m_tensionKnob );
|
||||
@@ -2463,6 +2598,10 @@ void AutomationEditorWindow::setCurrentPattern(AutomationPattern* pattern)
|
||||
m_cubicHermiteAction->setChecked(true);
|
||||
m_tensionKnob->setEnabled(true);
|
||||
break;
|
||||
case AutomationPattern::BezierProgression:
|
||||
m_bezierAction->setChecked(true);
|
||||
m_tensionKnob->setEnabled(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Connect new pattern
|
||||
|
||||
@@ -1003,6 +1003,7 @@ void PianoRoll::drawDetuningInfo( QPainter & _p, const Note * _n, int _x,
|
||||
_p.drawLine( pos_x, old_y, pos_x, pos_y );
|
||||
break;
|
||||
case AutomationPattern::CubicHermiteProgression: /* TODO */
|
||||
case AutomationPattern::BezierProgression: /* TODO */
|
||||
case AutomationPattern::LinearProgression:
|
||||
_p.drawLine( old_x, old_y, pos_x, pos_y );
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user