summaryrefslogtreecommitdiff
path: root/muse2/muse/arranger/pcanvas.cpp
diff options
context:
space:
mode:
authorTim E. Real <termtech@rogers.com>2011-08-29 22:44:24 +0000
committerTim E. Real <termtech@rogers.com>2011-08-29 22:44:24 +0000
commitd505c378bdf55445babb73e0ba4085702be35870 (patch)
tree40ccb82e5b27f118904a5298faedd10fe3b96d36 /muse2/muse/arranger/pcanvas.cpp
parent21e75f0c2d14010d060693c77fd3e22e1fccd65d (diff)
Fixed audio automation graph editing. And now 'snaps' to discrete integer or bool types.
All control movements should update display now. Also slightly changed behaviour of Ctrl class. TODO: Fix some painting corruption, improve discrete display, add transparency etc. etc. Applied compilation patch to rhythmbase.ui by Jean-Damien Durand.
Diffstat (limited to 'muse2/muse/arranger/pcanvas.cpp')
-rw-r--r--muse2/muse/arranger/pcanvas.cpp393
1 files changed, 218 insertions, 175 deletions
diff --git a/muse2/muse/arranger/pcanvas.cpp b/muse2/muse/arranger/pcanvas.cpp
index 82f2de45..57bf71ba 100644
--- a/muse2/muse/arranger/pcanvas.cpp
+++ b/muse2/muse/arranger/pcanvas.cpp
@@ -44,6 +44,10 @@
#include "midictrl.h"
#include "utils.h"
+//#define ABS(x) ((x) < 0) ? -(x) : (x))
+//#define ABS(x) (x>=0?x:-x)
+#define ABS(x) (abs(x))
+
//---------------------------------------------------------
// colorRect
// paints a rectangular icon with a given color
@@ -94,7 +98,8 @@ PartCanvas::PartCanvas(int* r, QWidget* parent, int sx, int sy)
setMouseTracking(true);
drag = DRAG_OFF;
curColorIndex = 0;
- automation.currentCtrl = 0;
+ //automation.currentCtrl = 0;
+ automation.currentCtrlValid = false;
automation.controllerState = doNothing;
automation.moveController = false;
partsChanged();
@@ -862,7 +867,8 @@ void PartCanvas::mouseRelease(const QPoint&)
// clear all the automation parameters
automation.moveController=false;
automation.controllerState = doNothing;
- automation.currentCtrl=0;
+ //automation.currentCtrl=0;
+ automation.currentCtrlValid = false;
automation.currentTrack=0;
automation.currentCtrlList=0;
}
@@ -3436,107 +3442,104 @@ void PartCanvas::drawAudioTrack(QPainter& p, const QRect& r, const QRect& bbox,
void PartCanvas::drawAutomation(QPainter& p, const QRect& rr, AudioTrack *t)
{
- ///QRect rr = p.worldMatrix().mapRect(r);
-
- ///p.save();
- ///p.resetTransform();
-
- int height=rr.bottom()-rr.top()-4; // limit height
-
- //printf("PartCanvas::drawAutomation x:%d y:%d w:%d h:%d height:%d\n", rr.x(), rr.y(), rr.width(), rr.height(), height);
-
- p.setBrush(Qt::NoBrush);
-
- CtrlListList* cll = t->controller();
- ///bool firstRun=true;
- for(CtrlListList::iterator icll =cll->begin();icll!=cll->end();++icll)
- {
- //iCtrlList *icl = icll->second;
- CtrlList *cl = icll->second;
- if (cl->dontShow())
- continue;
- double prevVal;
- iCtrl ic=cl->begin();
- if (!cl->isVisible())
- continue; // skip this iteration if this controller isn't in the visible list
- ///p.setPen(QPen(cl->color(),1,Qt::SolidLine));
- p.setPen(QPen(cl->color(), 0, Qt::SolidLine));
-
- // First check that there ARE automation, ic == cl->end means no automation
- if (ic != cl->end()) {
- CtrlVal cvFirst = ic->second;
- ic++;
- int prevPosFrame=cvFirst.frame;
- prevVal = cvFirst.val;
- ///bool discrete = cl->valueType() == VAL_BOOL || cl->mode() == CtrlList::DISCRETE; // Tim
-
- // prepare prevVal
- if (cl->valueType() == VAL_LOG ) { // use db scale for volume
- prevVal = dbToVal(cvFirst.val); // represent volume between 0 and 1
- if (prevVal < 0) prevVal = 0.0;
- }
- else {
- // we need to set curVal between 0 and 1
- double min, max;
- cl->range(&min,&max);
- prevVal = (prevVal- min)/(max-min);
- }
+ //QRect rr = p.worldMatrix().mapRect(r);
+ //p.save();
+ //p.resetTransform();
- // draw a square around the point
- p.drawRect(mapx(tempomap.frame2tick(prevPosFrame))-1, (rr.bottom()-2)-prevVal*height-1, 3, 3);
- p.drawRect(mapx(tempomap.frame2tick(prevPosFrame))-2, (rr.bottom()-2)-prevVal*height-2, 5, 5);
+ int bottom = rr.bottom() - 2;
+ int height = bottom - rr.top() - 2; // limit height
- bool firstRun=true;
- for (; ic !=cl->end(); ++ic)
- {
- CtrlVal cv = ic->second;
- double nextVal = cv.val; // was curVal
+ //printf("PartCanvas::drawAutomation x:%d y:%d w:%d h:%d height:%d\n", rr.x(), rr.y(), rr.width(), rr.height(), height);
+
+ p.setBrush(Qt::NoBrush);
+
+ CtrlListList* cll = t->controller();
+ for(CtrlListList::iterator icll =cll->begin();icll!=cll->end();++icll)
+ {
+ CtrlList *cl = icll->second;
+ if (cl->dontShow() || !cl->isVisible())
+ continue;
+ iCtrl ic=cl->begin();
+ int oldX = mapx(0);
+ if(rr.right() < oldX)
+ {
+ //p.restore();
+ return;
+ }
+ int xpixel = oldX;
+ int oldY = -1;
+ int ypixel = oldY;
+ double min, max;
+ cl->range(&min,&max);
+ //bool discrete = cl->valueType() == VAL_BOOL || cl->mode() == CtrlList::DISCRETE;
+ bool discrete = cl->mode() == CtrlList::DISCRETE;
+ QPen pen1(cl->color(), 0);
+ QPen pen2(cl->color(), 2);
+ pen2.setCosmetic(true);
+
+ // First check that there ARE automation, ic == cl->end means no automation
+ if (ic == cl->end())
+ {
+ double y;
+ if (cl->valueType() == VAL_LOG ) { // use db scale for volume
+ y = dbToVal(cl->curVal()); // represent volume between 0 and 1
+ if (y < 0) y = 0.0;
+ }
+ else
+ y = (cl->curVal() - min)/(max-min); // we need to set curVal between 0 and 1
+ ypixel = oldY = bottom - rmapy_f(y) * height;
+ }
+ else
+ {
+ for (; ic !=cl->end(); ++ic)
+ {
+ double y = ic->second.val;
if (cl->valueType() == VAL_LOG ) { // use db scale for volume
- nextVal = dbToVal(cv.val); // represent volume between 0 and 1
- if (nextVal < 0) nextVal = 0.0;
- }
- else {
- // we need to set curVal between 0 and 1
- double min, max;
- cl->range(&min,&max);
- nextVal = (nextVal- min)/(max-min);
+ y = dbToVal(y); // represent volume between 0 and 1
+ if (y < 0) y = 0.0;
}
- int leftX=mapx(tempomap.frame2tick(prevPosFrame));
- if (firstRun && leftX>rr.x()) {
- leftX=rr.x();
- }
- int currentPixel = mapx(tempomap.frame2tick(cv.frame));
+ else
+ y = (y-min)/(max-min); // we need to set curVal between 0 and 1
+
+ ypixel = bottom - rmapy_f(y) * height;
+ xpixel = mapx(tempomap.frame2tick(ic->second.frame));
+
+ if (oldY==-1) oldY = ypixel;
//printf(" line x1:%d x2:%d prevVal:%f nextVal:%f\n", leftX, currentPixel, prevVal, nextVal);
- p.drawLine( leftX,
- (rr.bottom()-2)-prevVal*height,
- currentPixel,
- (rr.bottom()-2)-nextVal*height);
-
- ///if(discrete)
- /// p.drawLine( currentPixel, (rr.bottom()-2)-prevVal*height, currentPixel, (rr.bottom()-2)-nextVal*height ); // Tim
-
- firstRun=false;
- prevPosFrame=cv.frame;
- prevVal=nextVal;
- if (currentPixel > rr.x()+ rr.width())
- ///goto quitDrawing;
- break;
+ p.setPen(pen1);
+ if(discrete)
+ {
+ p.drawLine(oldX, oldY, xpixel, oldY);
+ p.drawLine(xpixel, oldY, xpixel, ypixel);
+ }
+ else
+ p.drawLine(oldX, oldY, xpixel, ypixel);
+
+ if (xpixel > rr.right())
+ break;
// draw a square around the point
- p.drawRect(mapx(tempomap.frame2tick(prevPosFrame))-2, (rr.bottom()-2)-prevVal*height-2, 5, 5);
- p.drawRect(mapx(tempomap.frame2tick(prevPosFrame))-1, (rr.bottom()-1)-prevVal*height-2, 3, 3);
- }
- //printf(" endline prevVal:%f\n", prevVal);
- p.drawLine(mapx(tempomap.frame2tick(prevPosFrame)),
- (rr.bottom()-2)-prevVal*height,
- rr.x()+rr.width(),
- (rr.bottom()-2)-prevVal*height);
- }
- }
-///quitDrawing:
- ///p.restore();
- return;
+ //p.drawRect(mapx(tempomap.frame2tick(prevPosFrame))-2, (rr.bottom()-2)-prevVal*height-2, 5, 5);
+ //p.drawRect(mapx(tempomap.frame2tick(prevPosFrame))-1, (rr.bottom()-1)-prevVal*height-2, 3, 3);
+ pen2.setColor((automation.currentCtrlValid && automation.currentCtrlList == cl &&
+ automation.currentCtrlFrame == ic->second.frame) ?
+ Qt::white : cl->color());
+
+ p.setPen(pen2);
+ p.drawRect(xpixel-2, ypixel-2, 5, 5);
+ oldX = xpixel;
+ oldY = ypixel;
+ }
+ }
+ if (xpixel <= rr.right())
+ {
+ //printf(" endline prevVal:%f\n", prevVal);
+ p.setPen(pen1);
+ p.drawLine(xpixel, ypixel, rr.right(), ypixel);
+ }
+ }
+ //p.restore();
}
@@ -3553,60 +3556,68 @@ void PartCanvas::drawAutomation(QPainter& p, const QRect& rr, AudioTrack *t)
void PartCanvas::checkAutomation(Track * t, const QPoint &pointer, bool addNewCtrl)
{
- int circumference = 5;
if (t->isMidiTrack())
return;
+ int currY;
+ int trackY = t->y();
+ int trackH = t->height();
+
+ { int y = pointer.y();
+ if(y < trackY || y >= (trackY + trackH))
+ return;
+ currY = mapy(y); }
+
int currX = mapx(pointer.x());
- int currY = mapy(pointer.y());
-
+ int circumference = 5;
+
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=0;
- int xpixel=-1;
+ int oldX = mapx(0);
+ int xpixel = oldX;
+ int oldY = -1;
+ int ypixel = oldY;
+ double min, max;
+ cl->range(&min,&max);
+ //bool discrete = cl->valueType() == VAL_BOOL || cl->mode() == CtrlList::DISCRETE; // Tim
// First check that there IS automation, ic == cl->end means no automation
- if (ic != cl->end()) {
+ if (ic == cl->end())
+ {
+ double y;
+ if (cl->valueType() == VAL_LOG ) { // use db scale for volume
+ y = dbToVal(cl->curVal()); // represent volume between 0 and 1
+ if (y < 0) y = 0.0;
+ }
+ else
+ y = (cl->curVal() - min)/(max-min); // we need to set curVal between 0 and 1
+ ypixel = oldY = mapy(trackY+trackH-1 - 2 - y * trackH);
+ }
+ else
+ {
for (; ic !=cl->end(); ic++)
{
- CtrlVal &cv = ic->second;
- double y;
+ double y = ic->second.val;
if (cl->valueType() == VAL_LOG ) { // use db scale for volume
- y = dbToVal(cv.val); // represent volume between 0 and 1
+ y = dbToVal(y); // 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;
- }
+ else
+ y = (y-min)/(max-min); // we need to set curVal between 0 and 1
- ypixel = mapy(yy-2-y*t->height());
- xpixel = mapx(tempomap.frame2tick(cv.frame));
+ ypixel = mapy(trackY + trackH - 2 - y * trackH);
+ xpixel = mapx(tempomap.frame2tick(ic->second.frame));
- if (oldX==-1) oldX = xpixel;
if (oldY==-1) oldY = ypixel;
-
+ //printf(" oldX:%d oldY:%d xpixel:%d ypixel:%d\n", oldX, oldY, xpixel, ypixel);
+
bool foundIt=false;
if (addNewCtrl) {
// check if we are reasonably close to a line
@@ -3618,14 +3629,15 @@ void PartCanvas::checkAutomation(Track * t, const QPoint &pointer, bool addNewCt
double proportion = (currX-firstX)/(lastX-firstX);
- if ( (currX > lastX && firstY!=lastY) // omit special cases.
- || firstX==lastX ) {
+ //if ( (currX > lastX && firstY!=lastY) // omit special cases.
+ // || firstX==lastX ) {
+ if((currX < oldX) || (currX > lastX) || (firstX==lastX) )
+ {
oldX = xpixel;
oldY = ypixel;
continue; // not the right region
}
-
// 10 X(15) 20
// proportion = 0.5
// 10
@@ -3635,15 +3647,12 @@ void PartCanvas::checkAutomation(Track * t, const QPoint &pointer, bool addNewCt
// 1
double calcY = (lastY-firstY)*proportion+firstY;
//printf("calcY=%f currY=%d\n", calcY, currY);
- if ( abs(calcY-currY) < circumference*4)
- foundIt=true;
-
- if ( xpixel == oldX && abs(currX-xpixel) < circumference)
+ if(ABS(calcY-currY) < circumference || (xpixel == oldX && ABS(currX-xpixel) < circumference))
foundIt=true;
-
+
} else {
- int x1 = abs(currX - xpixel) ;
- int y1 = abs(currY - ypixel);
+ int x1 = ABS(currX - xpixel) ;
+ int y1 = ABS(currY - ypixel);
if (x1 < circumference && y1 < circumference && pointer.x() > 0 && pointer.y() > 0) {
foundIt=true;
}
@@ -3655,10 +3664,14 @@ void PartCanvas::checkAutomation(Track * t, const QPoint &pointer, bool addNewCt
if (foundIt) {
QWidget::setCursor(Qt::CrossCursor);
if (addNewCtrl) {
- automation.currentCtrl = 0;
+ //automation.currentCtrl = 0;
+ automation.currentCtrlValid = false;
automation.controllerState = addNewController;
}else {
- automation.currentCtrl=&cv;
+ //automation.currentCtrl=&ic->second;
+ automation.currentCtrlFrame = ic->second.frame;
+ //automation.currentCtrlVal = ic->second.val;
+ automation.currentCtrlValid = true;
automation.controllerState = movingController;
}
automation.currentCtrlList = cl;
@@ -3666,37 +3679,36 @@ void PartCanvas::checkAutomation(Track * t, const QPoint &pointer, bool addNewCt
return;
}
}
- } // if
+ }
if (addNewCtrl) {
- // check if we are reasonably close to a line, we only need to check Y
- // as the line is straight after the last controller
- bool foundIt=false;
- if ( ypixel == oldY && abs(currY-ypixel) < circumference) {
- foundIt=true;
- }
-
- if (foundIt) {
+ // check if we are reasonably close to a line, we only need to check Y
+ // as the line is straight after the last controller
+ //printf("post oldX:%d oldY:%d xpixel:%d ypixel:%d currX:%d currY:%d\n", oldX, oldY, xpixel, ypixel, currX, currY);
+ if(currX >= xpixel && ypixel == oldY && ABS(currY-ypixel) < circumference) {
QWidget::setCursor(Qt::CrossCursor);
automation.controllerState = addNewController;
automation.currentCtrlList = cl;
automation.currentTrack = t;
- automation.currentCtrl = 0;
+ //automation.currentCtrl = 0;
+ automation.currentCtrlValid = false;
return;
}
}
}
// if there are no hits we default to clearing all the data
automation.controllerState = doNothing;
- automation.currentCtrl = 0;
+ //automation.currentCtrl = 0;
+ automation.currentCtrlValid = false;
automation.currentCtrlList = 0;
automation.currentTrack = 0;
setCursor();
}
-void PartCanvas::controllerChanged(Track* /* t */)
+void PartCanvas::controllerChanged(Track* t)
{
- redraw();
+ //redraw();
+ redraw((QRect(0, mapy(t->y()), width(), rmapy(t->height())))); // TODO Check this - correct?
}
void PartCanvas::processAutomationMovements(QPoint pos, bool addPoint)
@@ -3716,18 +3728,24 @@ void PartCanvas::processAutomationMovements(QPoint pos, bool addPoint)
int prevFrame = 0;
int nextFrame = -1;
+ int currFrame = 0;
if (automation.controllerState == addNewController)
{
//printf("adding a new ctrler!\n");
int frame = tempomap.tick2frame(pos.x());
- automation.currentCtrlList->add( frame, 1.0 /*dummy value */);
+ // FIXME Inefficient to add with wait here, then remove and add with wait again below. Tim.
+ audio->msgAddACEvent((AudioTrack*)automation.currentTrack, automation.currentCtrlList->id(), frame, 1.0 /*dummy value */);
+ //song->addACEvent((AudioTrack*)automation.currentTrack, automation.currentCtrlList->id(), frame, 1.0 /*dummy value */);
iCtrl ic=automation.currentCtrlList->begin();
- for (; ic !=automation.currentCtrlList->end(); ic++) {
+ for (; ic !=automation.currentCtrlList->end(); ++ic) {
CtrlVal &cv = ic->second;
if (cv.frame == frame) {
- automation.currentCtrl = &cv;
+ //automation.currentCtrl = &cv;
+ automation.currentCtrlFrame = cv.frame;
+ //automation.currentCtrlVal = cv.val;
+ automation.currentCtrlValid = true;
automation.controllerState = movingController;
break;
}
@@ -3736,22 +3754,38 @@ void PartCanvas::processAutomationMovements(QPoint pos, bool addPoint)
// get previous and next frame position to give x bounds for this event.
iCtrl ic=automation.currentCtrlList->begin();
- for (; ic !=automation.currentCtrlList->end(); ic++)
+ iCtrl iprev = ic;
+ for (; ic !=automation.currentCtrlList->end(); ++ic)
{
CtrlVal &cv = ic->second;
- if (&cv == automation.currentCtrl)
+ //if (&cv == automation.currentCtrl)
+ if (cv.frame == automation.currentCtrlFrame)
+ {
+ currFrame = cv.frame;
break;
+ }
prevFrame = cv.frame;
+ iprev = ic;
}
+
+ iCtrl icc = ic;
+
if ( ++ic != automation.currentCtrlList->end()) {
CtrlVal &cv = ic->second;
nextFrame = cv.frame;
}
- int currFrame = tempomap.tick2frame(pos.x());
- if (currFrame < prevFrame) currFrame=prevFrame+1;
- if (nextFrame!=-1 && currFrame > nextFrame) currFrame=nextFrame-1;
- automation.currentCtrl->frame = currFrame;
-
+
+ // A perfectly straight vertical line (two points with same frame) is impossible:
+ // there is only one value at t, and the next value at t+1, and so on.
+ // Also these are maps, not multimaps. p4.0.32 Tim.
+ int newFrame = tempomap.tick2frame(pos.x());
+ //if(currFrame == 0)
+ // newFrame = 0; // Force first item to stay at x = 0.
+ //else
+ if (newFrame <= prevFrame)
+ newFrame=prevFrame + (icc == automation.currentCtrlList->begin() ? 0: 1); // Only first item is allowed to go to zero x.
+ if (nextFrame!=-1 && newFrame >= nextFrame) newFrame=nextFrame-1;
+
int posy=mapy(pos.y());
int tracky = mapy(automation.currentTrack->y());
int trackHeight = automation.currentTrack->height();
@@ -3760,29 +3794,38 @@ void PartCanvas::processAutomationMovements(QPoint pos, bool addPoint)
int mouseY = trackHeight - (posy - tracky)-2;
double yfraction = ((double)mouseY)/automation.currentTrack->height();
+ double min, max;
+ automation.currentCtrlList->range(&min,&max);
+ double cvval;
if (automation.currentCtrlList->valueType() == VAL_LOG ) { // use db scale for volume
-
- double cvval = valToDb(yfraction);
+ cvval = valToDb(yfraction);
//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;
-
+ // we need to set val between 0 and 1 (unless integer)
+ cvval = yfraction * (max-min) + min;
+ // 'Snap' to integer or boolean
+ if (automation.currentCtrlList->valueType() == VAL_INT || automation.currentCtrlList->valueType() == VAL_BOOL)
+ cvval = rint(cvval + 0.1); // LADSPA docs say add a slight bias to avoid rounding errors. Try this.
if (cvval< min) cvval=min;
if (cvval>max) cvval=max;
- automation.currentCtrl->val = cvval;
}
- controllerChanged(automation.currentTrack);
-
+
+ automation.currentCtrlFrame = newFrame;
+ //automation.currentCtrlVal = cvval;
+ automation.currentCtrlValid = true;
+
+ if(icc != automation.currentCtrlList->end())
+ audio->msgChangeACEvent((AudioTrack*)automation.currentTrack, automation.currentCtrlList->id(), icc->second.frame, newFrame, cvval);
+ //song->changeACEvent((AudioTrack*)automation.currentTrack, automation.currentCtrlList->id(), icc->second.frame, newFrame, cvval);
+ else
+ audio->msgAddACEvent((AudioTrack*)automation.currentTrack, automation.currentCtrlList->id(), newFrame, cvval);
+ //song->addACEvent((AudioTrack*)automation.currentTrack, automation.currentCtrlList->id(), newFrame, cvval);
+
+ // Not needed. Redraw is now handled by msgXXX().
+ //controllerChanged(automation.currentTrack);
}
}