From cbee698e6b2c7e6043909fb672ee4f9868475841 Mon Sep 17 00:00:00 2001 From: Robert Jonsson Date: Fri, 21 Jan 2011 22:36:42 +0000 Subject: graphical editing of automation 1st drop --- muse2/muse/arranger/pcanvas.cpp | 286 +++++++++++++++++++++++++++++++++++----- muse2/muse/arranger/pcanvas.h | 14 +- muse2/muse/audiotrack.cpp | 13 +- muse2/muse/ctrl.cpp | 17 +++ muse2/muse/ctrl.h | 6 +- muse2/muse/midiedit/ecanvas.cpp | 6 +- muse2/muse/midiedit/ecanvas.h | 2 +- muse2/muse/widgets/canvas.cpp | 6 +- muse2/muse/widgets/canvas.h | 2 +- muse2/muse/widgets/tools.cpp | 2 + muse2/muse/widgets/tools.h | 4 +- 11 files changed, 308 insertions(+), 50 deletions(-) diff --git a/muse2/muse/arranger/pcanvas.cpp b/muse2/muse/arranger/pcanvas.cpp index 3e6919a7..95ceebd5 100644 --- a/muse2/muse/arranger/pcanvas.cpp +++ b/muse2/muse/arranger/pcanvas.cpp @@ -141,6 +141,8 @@ PartCanvas::PartCanvas(int* r, QWidget* parent, int sx, int sy) setMouseTracking(true); drag = DRAG_OFF; curColorIndex = 0; + automation.currentCtrl = 0; + moveController = 0; partsChanged(); } @@ -1002,7 +1004,7 @@ void PartCanvas::mousePress(QMouseEvent* event) } QPoint pt = event->pos(); CItem* item = items.find(pt); - if (item == 0) + if (item == 0 && _tool!=AutomationTool) return; switch (_tool) { default: @@ -1022,6 +1024,10 @@ void PartCanvas::mousePress(QMouseEvent* event) redraw(); break; } + case AutomationTool: + if (automation.currentCtrl || (automation.currentCtrlList && event->modifiers() & Qt::ControlModifier)) + moveController=true; + break; } } @@ -1031,17 +1037,24 @@ void PartCanvas::mousePress(QMouseEvent* event) void PartCanvas::mouseRelease(const QPoint&) { + moveController=false; + automation.currentCtrl=0; + automation.currentTrack=0; + automation.currentCtrlList=0; } //--------------------------------------------------------- -// viewMouseMoveEvent +// viewMousevent //--------------------------------------------------------- -void PartCanvas::mouseMove(const QPoint& pos) +void PartCanvas::mouseMove(QMouseEvent* event) { - int x = pos.x(); + int x = event->pos().x(); if (x < 0) x = 0; + + processAutomationMovements(event); + emit timeChanged(AL::sigmap.raster(x, *_raster)); } @@ -2628,7 +2641,7 @@ void PartCanvas::dragEnterEvent(QDragEnterEvent* event) } //--------------------------------------------------------- -// dragMoveEvent +// dragvent //--------------------------------------------------------- void PartCanvas::dragMoveEvent(QDragMoveEvent*) @@ -2884,22 +2897,14 @@ void PartCanvas::drawAudioTrack(QPainter& p, const QRect& r, AudioTrack* /* t */ void PartCanvas::drawAutomation(QPainter& p, const QRect& r, AudioTrack *t) { -// printf("drawAudioTrack %d x %d y %d w %d h %d\n",t, r.x(), r.y(), r.width(), r.height()); - //int v2=r.x()+r.width(); - //printf("v2=%d mapx=%d rmapx=%d mapxdev=%d rmapxdev=%d\n",v2, mapx(v2),rmapx(v2),mapxDev(v2),rmapxDev(v2)); - //return; + QRect rr = p.worldMatrix().mapRect(r); -// p.setPen(QPen(Qt::black, 2, Qt::SolidLine)); - int height=r.bottom()-r.top()-4; // limit height + p.save(); + p.resetTransform(); + + int height=rr.bottom()-rr.top()-4; // limit height CtrlListList* cll = t->controller(); -// QColor cols[10]; -// cols[0]=Qt::white; -// cols[1]=Qt::red; -// cols[2]=Qt::yellow; -// cols[3]=Qt::black; -// cols[4]=Qt::blue; - //int colIndex=0; bool firstRun=true; for(CtrlListList::iterator icll =cll->begin();icll!=cll->end();++icll) { @@ -2917,7 +2922,7 @@ void PartCanvas::drawAutomation(QPainter& p, const QRect& r, AudioTrack *t) if (ic != cl->end()) { CtrlVal cvFirst = ic->second; ic++; - int prevPos=cvFirst.frame; + int prevPosFrame=cvFirst.frame; prevVal = cvFirst.val; // prepare prevVal @@ -2932,6 +2937,9 @@ void PartCanvas::drawAutomation(QPainter& p, const QRect& r, AudioTrack *t) prevVal = (prevVal- min)/(max-min); } + // draw a square around the point + p.drawRect(mapx(tempomap.frame2tick(prevPosFrame))-1, (rr.bottom()-2)-prevVal*height-1, 3, 3); + for (; ic !=cl->end(); ++ic) { CtrlVal cv = ic->second; @@ -2946,32 +2954,244 @@ void PartCanvas::drawAutomation(QPainter& p, const QRect& r, AudioTrack *t) cl->range(&min,&max); nextVal = (nextVal- min)/(max-min); } - int leftX=tempomap.frame2tick(prevPos); - if (firstRun && leftX>r.x()) { - leftX=r.x(); + int leftX=mapx(tempomap.frame2tick(prevPosFrame)); + if (firstRun && leftX>rr.x()) { + leftX=rr.x(); } - + int currentPixel = mapx(tempomap.frame2tick(cv.frame)); p.drawLine( leftX, - (r.bottom()-2)-prevVal*height, - tempomap.frame2tick(cv.frame), - (r.bottom()-2)-nextVal*height); + (rr.bottom()-2)-prevVal*height, + currentPixel, + (rr.bottom()-2)-nextVal*height); firstRun=false; - //printf("draw line: %d %f %d %f\n",tempomap.frame2tick(lastPos),r.bottom()-lastVal*height,tempomap.frame2tick(cv.frame),r.bottom()-curVal*height); - prevPos=cv.frame; + //printf("draw line: %d %f %d %f\n",tempomap.frame2tick(lastPos),rr.bottom()-lastVal*height,tempomap.frame2tick(cv.frame),rr.bottom()-curVal*height); + prevPosFrame=cv.frame; prevVal=nextVal; + if (currentPixel > rr.x()+ rr.width()) + goto quitDrawing; + + // draw a square around the point + p.drawRect(mapx(tempomap.frame2tick(prevPosFrame))-1, (rr.bottom()-2)-prevVal*height-1, 3, 3); } //printf("outer draw %f\n", cvFirst.val ); - p.drawLine(tempomap.frame2tick(prevPos), - (r.bottom()-2)-prevVal*height, - r.x()+r.width(), - (r.bottom()-2)-prevVal*height); - //printf("draw last line: %d %f %d %f\n",tempomap.frame2tick(prevPos),(r.bottom()-2)-prevVal*height,tempomap.frame2tick(prevPos)+r.width(),(r.bottom()-2)-prevVal*height); + p.drawLine(mapx(tempomap.frame2tick(prevPosFrame)), + (rr.bottom()-2)-prevVal*height, + rr.x()+rr.width(), + (rr.bottom()-2)-prevVal*height); + //printf("draw last line: %d %f %d %f\n",tempomap.frame2tick(prevPosFrame),(rr.bottom()-2)-prevVal*height,tempomap.frame2tick(prevPosFrame)+rr.width(),(rr.bottom()-2)-prevVal*height); } } +quitDrawing: + p.restore(); } +void PartCanvas::checkAutomation(Track * t, const QPoint &pointer, bool addNewCtrl) +{ + int circumference = 4; + if (t->isMidiTrack()) + return; + //printf("checkAutomation p.x()=%d p.y()=%d\n", mapx(pointer.x()), mapx(pointer.y())); + + int currX = mapx(pointer.x()); + int currY = mapy(pointer.y()); + + CtrlListList* cll = ((AudioTrack*) t)->controller(); + for(CtrlListList::iterator icll =cll->begin();icll!=cll->end();++icll) + { + //iCtrlList *icl = icll->second; + CtrlList *cl = icll->second; + if (cl->dontShow() || !cl->isVisible()) { + continue; + } + iCtrl ic=cl->begin(); + + int oldX=-1; + int oldY=-1; + int ypixel; + int xpixel; + // First check that there ARE automation, ic == cl->end means no automation + if (ic != cl->end()) { + for (; ic !=cl->end(); ic++) + { + CtrlVal &cv = ic->second; + double y; + if (cl->id() == AC_VOLUME ) { // use db scale for volume + y = (20.0*log10(cv.val)+60) / 70.0; // represent volume between 0 and 1 + if (y < 0) y = 0; + } + else { + // we need to set curVal between 0 and 1 + double min, max; + cl->range(&min,&max); + y = ( cv.val - min)/(max-min); + } + + TrackList* tl = song->tracks(); + int yy = 0; + for (iTrack it = tl->begin(); it != tl->end(); ++it) { + Track* track = *it; + yy += track->height(); + if (track == t) + break; + } + + ypixel = mapy(yy-2-y*t->height()); + xpixel = mapx(tempomap.frame2tick(cv.frame)); + + if (oldX==-1) oldX = xpixel; + if (oldY==-1) oldY = ypixel; + + bool foundIt=false; + if (addNewCtrl) { + // check if we are reasonably close to a line + //printf("xpixel=%d oldX=%d\n", xpixel, oldX); + if ( xpixel == oldX && abs(currX-xpixel) < circumference) + foundIt=true; + + } + + //printf("point at x=%d xdiff=%d y=%d ydiff=%d\n", mapx(tempomap.frame2tick(cv.frame)), x1, mapx(ypixel), y1); + oldX = xpixel; + oldY = ypixel; + + int x1 = abs(currX - xpixel) ; + int y1 = abs(currY - ypixel); + if (!addNewCtrl && x1 < circumference && y1 < circumference && pointer.x() > 0 && pointer.y() > 0) { + foundIt=true; + } + + if (foundIt) { + QWidget::setCursor(Qt::CrossCursor); + if (addNewCtrl) + automation.currentCtrl = 0; // the logic relies on this to be zero if a new controller is to be added + else + automation.currentCtrl=&cv; + automation.currentCtrlList = cl; + automation.currentTrack = t; + return; + } + } + } // if + + if (addNewCtrl) { + // check if we are reasonably close to a line, we only need to check Y as this is the line is straight after the last controller + //printf("LAST ypixel=%d oldY=%d currY=%d\n", ypixel, oldY, currY); + bool foundIt=false; + if ( ypixel == oldY && abs(currY-ypixel) < circumference) { + //printf("found it!\n"); + foundIt=true; + } + + if (foundIt) { + QWidget::setCursor(Qt::CrossCursor); + automation.currentCtrlList = cl; + automation.currentTrack = t; + automation.currentCtrl = 0; + return; + } + + + } + + } + automation.currentCtrl = 0; + automation.currentCtrlList = 0; + automation.currentTrack = 0; + // + setCursor(); +} void PartCanvas::controllerChanged(Track* /* t */) { redraw(); } + +void PartCanvas::processAutomationMovements(QMouseEvent *event) +{ + + if (_tool == AutomationTool) { + bool addNewPoints=false; + if (event->modifiers() & Qt::ControlModifier) { + addNewPoints=true; + } + if (moveController) { + //printf("update automation for controller=%d\n", automation.currentCtrl); + // update currentController to this position + + int prevFrame = 0; + int nextFrame = -1; + + if (addNewPoints && automation.currentCtrl == 0) // we don't have a controller, create one! + { + //printf("adding a new ctrler!\n"); + int frame = tempomap.tick2frame(event->pos().x()); + automation.currentCtrlList->add( frame, 1.0 /*dummy value */); + + iCtrl ic=automation.currentCtrlList->begin(); + for (; ic !=automation.currentCtrlList->end(); ic++) { + CtrlVal &cv = ic->second; + if (cv.frame == frame) { + automation.currentCtrl = &cv; + break; + } + } + + } + + // get previous and next frame position to give x bounds for this event. + iCtrl ic=automation.currentCtrlList->begin(); + for (; ic !=automation.currentCtrlList->end(); ic++) + { + CtrlVal &cv = ic->second; + if (&cv == automation.currentCtrl) + break; + prevFrame = cv.frame; + } + if ( ++ic != automation.currentCtrlList->end()) { + CtrlVal &cv = ic->second; + nextFrame = cv.frame; + } + int currFrame = tempomap.tick2frame(event->pos().x()); + if (currFrame < prevFrame) currFrame=prevFrame+1; + if (nextFrame!=-1 && currFrame > nextFrame) currFrame=nextFrame-1; + automation.currentCtrl->frame = currFrame; + + int mouseY = automation.currentTrack->height() - (mapy(event->pos().y()) - automation.currentTrack->y())-2; + double yfraction = ((double)mouseY)/automation.currentTrack->height(); + + if (automation.currentCtrlList->id() == AC_VOLUME ) { // use db scale for volume + //y = (20.0*log10(cv.val)+60) / 70.0; // represent volume between 0 and 1 + + double cvval = exp10((yfraction*70.0-60)/20.0); + //printf("calc yfraction = %f v=%f ",yfraction,cvval); + double min, max; + automation.currentCtrlList->range(&min,&max); + if (cvval< min) cvval=min; + if (cvval>max) cvval=max; + automation.currentCtrl->val=cvval; + + } + else { + // we need to set curVal between 0 and 1 + double min, max; + automation.currentCtrlList->range(&min,&max); + double cvval = yfraction * (max-min) + min; + + if (cvval< min) cvval=min; + if (cvval>max) cvval=max; + automation.currentCtrl->val = cvval; + //printf("calc cvval=%f yfraction=%f ", cvval, yfraction); + } + //printf("mouseY=%d\n", mouseY); + controllerChanged(automation.currentTrack); + + } else { + + Track * t = y2Track(event->pos().y()); + if (t) { + checkAutomation(t, event->pos(), addNewPoints); + } + } + } + +} diff --git a/muse2/muse/arranger/pcanvas.h b/muse2/muse/arranger/pcanvas.h index 103b3d02..6afe1dca 100644 --- a/muse2/muse/arranger/pcanvas.h +++ b/muse2/muse/arranger/pcanvas.h @@ -37,10 +37,17 @@ class NPart : public CItem { Track* track() const { return part()->track(); } }; +struct AutomationObject { + CtrlVal *currentCtrl; + CtrlList *currentCtrlList; + Track *currentTrack; +}; + class QLineEdit; class MidiEditor; class QMenu; class Xml; +class CtrlVal; //--------------------------------------------------------- // PartCanvas @@ -56,11 +63,14 @@ class PartCanvas : public Canvas { int curColorIndex; bool editMode; + AutomationObject automation; + bool moveController; + std::vector automationViews; Q_OBJECT virtual void keyPress(QKeyEvent*); virtual void mousePress(QMouseEvent*); - virtual void mouseMove(const QPoint&); + virtual void mouseMove(QMouseEvent* event); virtual void mouseRelease(const QPoint&); virtual void viewMouseDoubleClickEvent(QMouseEvent*); virtual void leaveEvent(QEvent*e); @@ -103,6 +113,8 @@ class PartCanvas : public Canvas { Track* y2Track(int) const; void drawAudioTrack(QPainter& p, const QRect& r, AudioTrack* track); void drawAutomation(QPainter& p, const QRect& r, AudioTrack* track); + void checkAutomation(Track * t, const QPoint& pointer, bool addNewCtrl); + void processAutomationMovements(QMouseEvent *event); protected: diff --git a/muse2/muse/audiotrack.cpp b/muse2/muse/audiotrack.cpp index eb70c51e..4be1a1a9 100644 --- a/muse2/muse/audiotrack.cpp +++ b/muse2/muse/audiotrack.cpp @@ -871,8 +871,10 @@ void AudioTrack::writeProperties(int level, Xml& xml) const } for (ciCtrlList icl = _controller.begin(); icl != _controller.end(); ++icl) { const CtrlList* cl = icl->second; - QString s("controller id=\"%1\" cur=\"%2\""); - xml.tag(level++, s.arg(cl->id()).arg(cl->curVal()).toAscii().constData()); + + QString s= QString("controller id=\"%1\" cur=\"%2\"").arg(cl->id()).arg(cl->curVal()).toAscii().constData(); + s += QString(" color=\"%1\" visible=\"%2\"").arg(cl->color().name()).arg(cl->isVisible()); + xml.tag(level++, s.toAscii().constData()); int i = 0; for (ciCtrl ic = cl->begin(); ic != cl->end(); ++ic) { QString s("%1 %2, "); @@ -967,7 +969,7 @@ bool AudioTrack::readProperties(Xml& xml, const QString& tag) else if (tag == "controller") { CtrlList* l = new CtrlList(); l->read(xml); - + // Since (until now) muse wrote a 'zero' for plugin controller current value // in the XML file, we can't use that value, now that plugin automation is added. // We must take the value from the plugin control value. @@ -995,8 +997,9 @@ bool AudioTrack::readProperties(Xml& xml, const QString& tag) d->insert(std::pair (i->first, i->second)); if(!ctlfound) - d->setCurVal(l->curVal()); - + d->setCurVal(l->curVal()); + d->setColor(l->color()); + d->setVisible(l->isVisible()); d->setDefault(l->getDefault()); delete l; l = d; diff --git a/muse2/muse/ctrl.cpp b/muse2/muse/ctrl.cpp index 2a6f7cb0..e5da5f2b 100644 --- a/muse2/muse/ctrl.cpp +++ b/muse2/muse/ctrl.cpp @@ -197,6 +197,23 @@ void CtrlList::read(Xml& xml) if(!ok) printf("CtrlList::read failed reading _curVal string: %s\n", xml.s2().toLatin1().constData()); } + else if (tag == "visible") + { + _visible = loc.toInt(xml.s2(), &ok); + if(!ok) + printf("CtrlList::read failed reading _visible string: %s\n", xml.s2().toLatin1().constData()); + } + else if (tag == "color") + { +#if QT_VERSION >= 0x040700 + ok = _displayColor.isValidColor(xml.s2()); + if (!ok) { + printf("CtrlList::read failed reading color string: %s\n", xml.s2().toLatin1().constData()); + break; + } +#endif + _displayColor.setNamedColor(xml.s2()); + } else printf("unknown tag %s\n", tag.toLatin1().constData()); break; diff --git a/muse2/muse/ctrl.h b/muse2/muse/ctrl.h index c845bb1e..307a3783 100644 --- a/muse2/muse/ctrl.h +++ b/muse2/muse/ctrl.h @@ -124,10 +124,10 @@ class CtrlList : public std::map > { void read(Xml& xml); void setColor( QColor c ) { _displayColor = c;} - QColor color() { return _displayColor; } + QColor color() const { return _displayColor; } void setVisible(bool v) { _visible = v; } - bool isVisible() { return _visible; } - bool dontShow() { return _dontShow; } + bool isVisible() const { return _visible; } + bool dontShow() const { return _dontShow; } }; //--------------------------------------------------------- diff --git a/muse2/muse/midiedit/ecanvas.cpp b/muse2/muse/midiedit/ecanvas.cpp index 68819adf..889657aa 100644 --- a/muse2/muse/midiedit/ecanvas.cpp +++ b/muse2/muse/midiedit/ecanvas.cpp @@ -123,10 +123,10 @@ void EventCanvas::endUndo(DragType dtype, int flags) // mouseMove //--------------------------------------------------------- -void EventCanvas::mouseMove(const QPoint& pos) +void EventCanvas::mouseMove(QMouseEvent* event) { - emit pitchChanged(y2pitch(pos.y())); - int x = pos.x(); + emit pitchChanged(y2pitch(event->pos().y())); + int x = event->pos().x(); emit timeChanged(editor->rasterVal(x)); } diff --git a/muse2/muse/midiedit/ecanvas.h b/muse2/muse/midiedit/ecanvas.h index 461a717a..86e1c200 100644 --- a/muse2/muse/midiedit/ecanvas.h +++ b/muse2/muse/midiedit/ecanvas.h @@ -44,7 +44,7 @@ class EventCanvas : public Canvas { virtual void startUndo(DragType); virtual void endUndo(DragType, int flags = 0); - virtual void mouseMove(const QPoint&); + virtual void mouseMove(QMouseEvent* event); protected: bool _playEvents; diff --git a/muse2/muse/widgets/canvas.cpp b/muse2/muse/widgets/canvas.cpp index 20b92e8d..5b6dc453 100644 --- a/muse2/muse/widgets/canvas.cpp +++ b/muse2/muse/widgets/canvas.cpp @@ -1040,7 +1040,7 @@ void Canvas::viewMouseMoveEvent(QMouseEvent* event) break; } - mouseMove(ev_pos); + mouseMove(event); } //--------------------------------------------------------- @@ -1141,6 +1141,7 @@ void Canvas::viewMouseReleaseEvent(QMouseEvent* event) if (redrawFlag) redraw(); setCursor(); + mouseRelease(pos); } //--------------------------------------------------------- @@ -1348,6 +1349,9 @@ void Canvas::setCursor() case MuteTool: QWidget::setCursor(QCursor(*editmuteIcon, 4, 15)); break; + case AutomationTool: + QWidget::setCursor(QCursor(Qt::PointingHandCursor)); + break; default: QWidget::setCursor(QCursor(Qt::ArrowCursor)); break; diff --git a/muse2/muse/widgets/canvas.h b/muse2/muse/widgets/canvas.h index 595fe04e..2f9a3907 100644 --- a/muse2/muse/widgets/canvas.h +++ b/muse2/muse/widgets/canvas.h @@ -88,7 +88,7 @@ class Canvas : public View { virtual void mousePress(QMouseEvent*) {} virtual void keyPress(QKeyEvent*); - virtual void mouseMove(const QPoint&) = 0; + virtual void mouseMove(QMouseEvent* event) = 0; virtual void mouseRelease(const QPoint&) {} virtual void drawCanvas(QPainter&, const QRect&) = 0; virtual void drawItem(QPainter&, const CItem*, const QRect&) = 0; diff --git a/muse2/muse/widgets/tools.cpp b/muse2/muse/widgets/tools.cpp index 32f42ad9..268a03d5 100644 --- a/muse2/muse/widgets/tools.cpp +++ b/muse2/muse/widgets/tools.cpp @@ -34,6 +34,7 @@ const char* infoQuant = QT_TRANSLATE_NOOP("@default", "select Quantize Tool:\n" const char* infoDraw = QT_TRANSLATE_NOOP("@default", "select Drawing Tool"); const char* infoMute = QT_TRANSLATE_NOOP("@default", "select Muting Tool:\n" "click on part to mute/unmute"); +const char* infoAutomation = QT_TRANSLATE_NOOP("@default", "Manipulate automation"); ToolB toolList[] = { {&pointerIcon, QT_TRANSLATE_NOOP("@default", "pointer"), infoPointer }, @@ -45,6 +46,7 @@ ToolB toolList[] = { {&quantIcon, QT_TRANSLATE_NOOP("@default", "quantize"), infoQuant }, {&drawIcon, QT_TRANSLATE_NOOP("@default", "draw"), infoDraw }, {&editmuteIcon, QT_TRANSLATE_NOOP("@default", "mute parts"), infoMute }, + {&drawIcon, QT_TRANSLATE_NOOP("@default", "edit automation"), infoAutomation}, }; //--------------------------------------------------------- diff --git a/muse2/muse/widgets/tools.h b/muse2/muse/widgets/tools.h index 7cc5e62c..43d1ebaf 100644 --- a/muse2/muse/widgets/tools.h +++ b/muse2/muse/widgets/tools.h @@ -17,9 +17,9 @@ class QPixmap; class QWidget; enum Tool { PointerTool=1, PencilTool=2, RubberTool=4, CutTool=8, - ScoreTool=16, GlueTool=32, QuantTool=64, DrawTool=128, MuteTool=256}; + ScoreTool=16, GlueTool=32, QuantTool=64, DrawTool=128, MuteTool=256, AutomationTool=512}; -const int arrangerTools = PointerTool | PencilTool | RubberTool | CutTool | GlueTool | MuteTool; +const int arrangerTools = PointerTool | PencilTool | RubberTool | CutTool | GlueTool | MuteTool | AutomationTool; struct ToolB { QPixmap** icon; -- cgit v1.2.3