diff --git a/data/themes/classic/progression_bezier.png b/data/themes/classic/progression_bezier.png new file mode 100644 index 000000000..224b4edc4 Binary files /dev/null and b/data/themes/classic/progression_bezier.png differ diff --git a/data/themes/default/progression_bezier.png b/data/themes/default/progression_bezier.png new file mode 100644 index 000000000..3c8fdb688 Binary files /dev/null and b/data/themes/default/progression_bezier.png differ diff --git a/include/AutomationEditor.h b/include/AutomationEditor.h index a4e9d5286..d14122b0c 100644 --- a/include/AutomationEditor.h +++ b/include/AutomationEditor.h @@ -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; diff --git a/include/AutomationPattern.h b/include/AutomationPattern.h index aff1f26bf..d9da7f1b8 100644 --- a/include/AutomationPattern.h +++ b/include/AutomationPattern.h @@ -46,10 +46,12 @@ public: { DiscreteProgression, LinearProgression, - CubicHermiteProgression + CubicHermiteProgression, + BezierProgression } ; typedef QMap timeMap; + typedef QMap > controlPointTimeMap; typedef QVector > 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(); @@ -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; diff --git a/src/core/AutomationPattern.cpp b/src/core/AutomationPattern.cpp index 3c6aa3e9c..b18cbf321 100644 --- a/src/core/AutomationPattern.cpp +++ b/src/core/AutomationPattern.cpp @@ -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()); diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index c5116b928..63506e5e8 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -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 diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index cee6870b7..0eeb9cfb4 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -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;