Add bezier progression type to automation tracks

This commit is contained in:
codythecoder
2016-04-19 09:59:26 +10:00
committed by Hyunjin Song
parent d3cd704396
commit 0dfdbc956a
7 changed files with 489 additions and 10 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

View File

@@ -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;

View File

@@ -46,10 +46,12 @@ public:
{
DiscreteProgression,
LinearProgression,
CubicHermiteProgression
CubicHermiteProgression,
BezierProgression
} ;
typedef QMap<int, float> timeMap;
typedef QMap<int, QVector<float> > controlPointTimeMap;
typedef QVector<QPointer<AutomatableModel> > objectVector;
AutomationPattern( AutomationTrack * _auto_track );
@@ -82,6 +84,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 +102,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 +135,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 +197,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 +208,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 +219,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;

View File

@@ -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,48 @@ 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()].insert( 0, time );
m_controlPoints[it.key()].insert( 1, _value );
clampControlPoints();
return it.key();
}
TimePos AutomationPattern::putValue( const TimePos & time,
const float value,
const bool quantPos,
@@ -225,6 +268,9 @@ TimePos AutomationPattern::putValue( const TimePos & time,
AutomationPattern::removeValue( i );
}
}
putControlPoint(it, value);
clampControlPoints();
if( it != m_timeMap.begin() )
{
--it;
@@ -247,6 +293,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 +338,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][0] - (float)newTime;
m_controlPointDragOffset[1] = m_controlPoints[newTime][1] - 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 +473,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 +493,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()].at(0) - v.key() ) * 2;
int targetX2 = ( 3 * (v+1).key() - 2 * m_controlPoints[(v+1).key()].at(0) - 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()].at(1);
float targetY2 = 2*(v+1).value() - m_controlPoints[(v+1).key()].at(1);
// 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 +589,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()][1] * -1;
m_controlPoints[(iterate + i).key()][1] = tempValue;
}
else
{
tempValue = max - valueAt( ( iterate + i ).key() );
putValue( TimePos( (iterate + i).key() ) , tempValue, false);
tempValue = max - m_controlPoints[(iterate + i).key()][1];
m_controlPoints[(iterate + i).key()][1] = tempValue;
}
}
@@ -463,6 +619,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 +643,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()][0] + newTime;
tempControlPoints[newTime].insert( 0, newControlPointX );
tempControlPoints[newTime].insert( 1, 2*tempValue - m_controlPoints[( iterate + i ).key()][1] );
tempMap[newTime] = tempValue;
}
}
@@ -496,6 +658,10 @@ void AutomationPattern::flipX( int length )
tempValue = valueAt( ( iterate + i ).key() );
TimePos newTime;
int newControlPointX = -( iterate + i ).key() + m_controlPoints[( iterate + i ).key()][0] + newTime;
tempControlPoints[newTime].insert( 0, newControlPointX );
tempControlPoints[newTime].insert( 1, 2*tempValue - m_controlPoints[( iterate + i ).key()][1] );
if ( ( iterate + i ).key() <= length )
{
newTime = TimePos( length - ( iterate + i ).key() );
@@ -515,13 +681,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()][0] + newTime;
tempMap[newTime] = tempValue;
tempControlPoints[newTime].insert( 0, newControlPointX );
tempControlPoints[newTime].insert( 1, 2*tempValue - m_controlPoints[( iterate + i ).key()][1] );
}
}
m_timeMap.clear();
m_controlPoints.clear();
m_timeMap = tempMap;
m_controlPoints = tempControlPoints;
generateTangents();
emit dataChanged();
@@ -553,6 +725,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()[0] );
element.setAttribute( "value2", it.value()[1] );
_this.appendChild( element );
}
for( objectVector::const_iterator it = m_objects.begin();
it != m_objects.end(); ++it )
{
@@ -593,6 +775,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()].insert( 0, element.attribute( "value1" ).toInt() );
m_controlPoints[element.attribute( "pos" ).toInt()].insert( 1, element.attribute( "value2" ).toFloat() );
}
else if( element.tagName() == "object" )
{
m_idsToResolve << element.attribute( "id" ).toInt();
@@ -616,6 +803,9 @@ void AutomationPattern::loadSettings( const QDomElement & _this )
changeLength( len );
}
generateTangents();
// Very important for reading older files
cleanControlPoints();
}
@@ -637,6 +827,63 @@ 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()][0];
float new_y = m_controlPoints[it.key()][1];
// Clamp X positions
// If the control point x is less than its automation point
if ( it.key() > m_controlPoints[it.key()][0] )
{
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 - m_controlPoints[it.key()][0] < ( (it-1).key() + it.key() ) / 2 )
{
new_x = it.key() * 2 - ( (it-1).key() + it.key() )/2;
}
else if ( it+1 != m_timeMap.end() && m_controlPoints[it.key()][0] > ( (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() && m_controlPoints[it.key()][1] > getMax() )
{
new_y = getMax();
}
else if ( it+1 != m_timeMap.end() && m_controlPoints[it.key()][1] < 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() - m_controlPoints[it.key()][1] > getMax() )
{
new_y = 2 * it.value() - getMax();
}
else if ( it != m_timeMap.begin() && 2 * it.value() - m_controlPoints[it.key()][1] < getMin() )
{
new_y = 2 * it.value() - getMin();
}
}
m_controlPoints.remove( it.key() );
m_controlPoints[it.key()].insert( 0, new_x );
m_controlPoints[it.key()].insert( 1, new_y );
}
}
TrackContentObjectView * AutomationPattern::createView( TrackView * _tv )
{
return new AutomationPatternView( this, _tv );
@@ -818,6 +1065,7 @@ void AutomationPattern::resolveAllIDs()
void AutomationPattern::clear()
{
m_timeMap.clear();
m_controlPoints.clear();
m_tangents.clear();
emit dataChanged();
@@ -870,6 +1118,41 @@ 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()].insert( 0, it.key() + 50 );
m_controlPoints[it.key()].insert( 1, it.value() );
}
}
clampControlPoints(false);
}
void AutomationPattern::generateTangents()
{
generateTangents(m_timeMap.begin(), m_timeMap.size());

View File

@@ -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()][1]) + 16 &&
yCoordOfLevel(level) >= yCoordOfLevel(control_points[it.key()][1]) - 16 &&
xCoordOfTick(pos_ticks) <= xCoordOfTick(control_points[it.key()][0]) + 16 &&
xCoordOfTick(pos_ticks) >= xCoordOfTick(control_points[it.key()][0]) - 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()][1]) + 16 &&
yCoordOfLevel(level) >= yCoordOfLevel(2 * it.value() - control_points[it.key()][1]) - 16 &&
xCoordOfTick(pos_ticks) <= xCoordOfTick(2 * it.key() - control_points[it.key()][0]) + 16 &&
xCoordOfTick(pos_ticks) >= xCoordOfTick(2 * it.key() - control_points[it.key()][0]) - 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()[0] );
int y = yCoordOfLevel( it.value()[1] );
// 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

View File

@@ -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;