From d505c378bdf55445babb73e0ba4085702be35870 Mon Sep 17 00:00:00 2001
From: "Tim E. Real" <termtech@rogers.com>
Date: Mon, 29 Aug 2011 22:44:24 +0000
Subject: 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.

---
 muse2/ChangeLog                   |   5 +
 muse2/awl/posedit.cpp             |   3 +-
 muse2/muse/arranger/arranger.cpp  |   1 +
 muse2/muse/arranger/pcanvas.cpp   | 393 +++++++++++++++++++++-----------------
 muse2/muse/arranger/pcanvas.h     |  10 +-
 muse2/muse/audio.cpp              |   4 +
 muse2/muse/audio.h                |   2 +
 muse2/muse/audiotrack.cpp         |  41 ++--
 muse2/muse/cliplist/cliplist.cpp  |   8 +-
 muse2/muse/ctrl.cpp               | 110 ++++++-----
 muse2/muse/ctrl.h                 |   5 +-
 muse2/muse/dssihost.cpp           |  15 +-
 muse2/muse/dssihost.h             |   2 +
 muse2/muse/mixer/astrip.cpp       |  43 +++--
 muse2/muse/mixer/panknob.cpp      |   4 +-
 muse2/muse/mplugins/rhythmbase.ui |   8 +-
 muse2/muse/plugin.cpp             |  92 ++++++---
 muse2/muse/plugin.h               |  10 +-
 muse2/muse/seqmsg.cpp             |  27 +++
 muse2/muse/song.cpp               |  17 ++
 muse2/muse/song.h                 |   7 +-
 muse2/muse/track.cpp              |   4 +-
 muse2/muse/track.h                |   1 +
 23 files changed, 506 insertions(+), 306 deletions(-)

diff --git a/muse2/ChangeLog b/muse2/ChangeLog
index 9eee3e8b..2505f901 100644
--- a/muse2/ChangeLog
+++ b/muse2/ChangeLog
@@ -1,3 +1,8 @@
+29.08.2011:
+        - Fixed audio automation graph editing. And now 'snaps' to discrete integer or bool types. (Tim p4.0.32)
+          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. (Tim)
 28.08.2011:
         - Fixed wierd column expansion for [rec] column by removing autoexpand of the last column. Still
           something fishy with moving columns (rj)
diff --git a/muse2/awl/posedit.cpp b/muse2/awl/posedit.cpp
index 6ca49566..07741e58 100644
--- a/muse2/awl/posedit.cpp
+++ b/muse2/awl/posedit.cpp
@@ -70,7 +70,8 @@ PosEdit::~PosEdit()
 
 QSize PosEdit::sizeHint() const
 	{
-      QFontMetrics fm(font());
+      //QFontMetrics fm(font());
+      QFontMetrics fm = fontMetrics();
       int fw = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth);
       int h  = fm.height() + fw * 2;
       int w = fw * 4 + 10;	// HACK: 10 = spinbox up/down arrows
diff --git a/muse2/muse/arranger/arranger.cpp b/muse2/muse/arranger/arranger.cpp
index e1205d6f..65a705e2 100644
--- a/muse2/muse/arranger/arranger.cpp
+++ b/muse2/muse/arranger/arranger.cpp
@@ -427,6 +427,7 @@ Arranger::Arranger(QMainWindow* parent, const char* name)
       connect(canvas, SIGNAL(dropMidiFile(const QString&)), SIGNAL(dropMidiFile(const QString&)));
 
       connect(canvas, SIGNAL(toolChanged(int)), SIGNAL(toolChanged(int)));
+      connect(song,   SIGNAL(controllerChanged(Track*)), SLOT(controllerChanged(Track*)));
 //      connect(song, SIGNAL(posChanged(int, unsigned, bool)), SLOT(seek()));
 
       // Removed p3.3.43 
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);
   }
 
 }
diff --git a/muse2/muse/arranger/pcanvas.h b/muse2/muse/arranger/pcanvas.h
index 05f380e6..210557dc 100644
--- a/muse2/muse/arranger/pcanvas.h
+++ b/muse2/muse/arranger/pcanvas.h
@@ -43,7 +43,9 @@ class NPart : public CItem {
 
 enum ControllerVals { doNothing, movingController, addNewController };
 struct AutomationObject {
-  CtrlVal *currentCtrl;
+  //CtrlVal *currentCtrl;
+  int currentCtrlFrame;
+  bool currentCtrlValid;
   CtrlList *currentCtrlList;
   Track *currentTrack;
   bool moveController;
@@ -154,8 +156,10 @@ class PartCanvas : public Canvas {
       PartCanvas(int* raster, QWidget* parent, int, int);
       void partsChanged();
       void cmd(int);
-      void controllerChanged(Track *t);
    public slots:
    void redirKeypress(QKeyEvent* e) { keyPress(e); }
-      };
+   void controllerChanged(Track *t);
+};
+
 #endif
+
diff --git a/muse2/muse/audio.cpp b/muse2/muse/audio.cpp
index 975054b0..668c2a30 100644
--- a/muse2/muse/audio.cpp
+++ b/muse2/muse/audio.cpp
@@ -80,6 +80,7 @@ const char* seqMsgList[] = {
       "AUDIO_ERASE_AC_EVENT",
       "AUDIO_ERASE_RANGE_AC_EVENTS",
       "AUDIO_ADD_AC_EVENT",
+      "AUDIO_CHANGE_AC_EVENT",
       "AUDIO_SET_SOLO", "AUDIO_SET_SEND_METRONOME", 
       "MS_PROCESS", "MS_STOP", "MS_SET_RTC", "MS_UPDATE_POLL_FD",
       "SEQM_IDLE", "SEQM_SEEK"
@@ -675,6 +676,9 @@ void Audio::processMsg(AudioMsg* msg)
             case AUDIO_ADD_AC_EVENT:
                   msg->snode->addACEvent(msg->ival, msg->a, msg->dval);
                   break;
+            case AUDIO_CHANGE_AC_EVENT:
+                  msg->snode->changeACEvent(msg->ival, msg->a, msg->b, msg->dval);
+                  break;
             case AUDIO_SET_SOLO:
                   msg->track->setSolo((bool)msg->ival);
                   break;
diff --git a/muse2/muse/audio.h b/muse2/muse/audio.h
index e332f516..ea9986c3 100644
--- a/muse2/muse/audio.h
+++ b/muse2/muse/audio.h
@@ -74,6 +74,7 @@ enum {
       AUDIO_ERASE_AC_EVENT,
       AUDIO_ERASE_RANGE_AC_EVENTS,
       AUDIO_ADD_AC_EVENT,
+      AUDIO_CHANGE_AC_EVENT,
       AUDIO_SET_SOLO, AUDIO_SET_SEND_METRONOME, 
       MS_PROCESS, MS_STOP, MS_SET_RTC, MS_UPDATE_POLL_FD,
       SEQM_IDLE, SEQM_SEEK,
@@ -253,6 +254,7 @@ class Audio {
       void msgEraseACEvent(AudioTrack*, int, int);
       void msgEraseRangeACEvents(AudioTrack*, int, int, int);
       void msgAddACEvent(AudioTrack*, int, int, double);
+      void msgChangeACEvent(AudioTrack* node, int acid, int frame, int newFrame, double val);
       void msgSetSolo(Track*, bool);
       void msgSetHwCtrlState(MidiPort*, int, int, int);
       void msgSetHwCtrlStates(MidiPort*, int, int, int, int);
diff --git a/muse2/muse/audiotrack.cpp b/muse2/muse/audiotrack.cpp
index b004638f..42229111 100644
--- a/muse2/muse/audiotrack.cpp
+++ b/muse2/muse/audiotrack.cpp
@@ -253,16 +253,11 @@ void AudioTrack::addPlugin(PluginI* plugin, int idx)
       const char* name = plugin->paramName(i);
       float min, max;
       plugin->range(i, &min, &max);
-      CtrlValueType t = plugin->valueType();
       CtrlList* cl = new CtrlList(id);
       cl->setRange(min, max);
       cl->setName(QString(name));
-      cl->setValueType(t);
-      LADSPA_PortRangeHint range = plugin->range(i);
-      if(LADSPA_IS_HINT_TOGGLED(range.HintDescriptor))
-        cl->setMode(CtrlList::DISCRETE);
-      else  
-        cl->setMode(CtrlList::INTERPOLATE);
+      cl->setValueType(plugin->ctrlValueType(i));
+      cl->setMode(plugin->ctrlMode(i));
       cl->setCurVal(plugin->param(i));
       addController(cl);
     }
@@ -699,6 +694,22 @@ void AudioTrack::addACEvent(int id, int frame, double val)
     return;  
 }
 
+//---------------------------------------------------------
+//   changeACEvent
+//---------------------------------------------------------
+
+void AudioTrack::changeACEvent(int id, int frame, int newframe, double newval)
+{
+  ciCtrlList icl = _controller.find(id);
+  if(icl == _controller.end())
+    return;
+  CtrlList* cl = icl->second;
+  iCtrl ic = cl->find(frame); 
+  if(ic != cl->end())
+    cl->erase(ic);
+  cl->insert(std::pair<const int, CtrlVal> (newframe, CtrlVal(newframe, newval)));      
+}
+
 //---------------------------------------------------------
 //   volume
 //---------------------------------------------------------
@@ -1039,11 +1050,8 @@ bool AudioTrack::readProperties(Xml& xml, const QString& tag)
               if(ctlfound)
                 {
                   l->setCurVal(p->param(m));
-                  LADSPA_PortRangeHint range = p->range(m);
-                  if(LADSPA_IS_HINT_TOGGLED(range.HintDescriptor))
-                    l->setMode(CtrlList::DISCRETE);
-                  else  
-                    l->setMode(CtrlList::INTERPOLATE);
+                  l->setValueType(p->ctrlValueType(m));  
+                  l->setMode(p->ctrlMode(m));  
                 } 
             }
       else
@@ -1139,15 +1147,10 @@ void AudioTrack::mapRackPluginsToControllers()
       //  0.9pre1 med file with broken controller sections they may not be set correct.
       float min, max;
       p->range(i, &min, &max);
-      CtrlValueType t = p->valueType();
       l->setRange(min, max);
       l->setName(QString(p->paramName(i)));
-      l->setValueType(t);
-      LADSPA_PortRangeHint rh = p->range(i);
-      if(LADSPA_IS_HINT_TOGGLED(rh.HintDescriptor))
-        l->setMode(CtrlList::DISCRETE);
-      else  
-        l->setMode(CtrlList::INTERPOLATE);
+      l->setValueType(p->ctrlValueType(i));
+      l->setMode(p->ctrlMode(i));  
       l->setCurVal(p->param(i));
       //l->setDefault(p->defaultValue(i));
     }  
diff --git a/muse2/muse/cliplist/cliplist.cpp b/muse2/muse/cliplist/cliplist.cpp
index 96636463..5a5796aa 100644
--- a/muse2/muse/cliplist/cliplist.cpp
+++ b/muse2/muse/cliplist/cliplist.cpp
@@ -26,7 +26,7 @@ enum { COL_NAME=0, COL_REFS, COL_POS, COL_LEN };
 class ClipItem : public QTreeWidgetItem {
       SndFileR _wf;
 
-      virtual QString text(int) const;
+      //virtual QString text(int) const;
 
    public:
       ClipItem(QTreeWidget*, const SndFileR&);
@@ -36,6 +36,10 @@ class ClipItem : public QTreeWidgetItem {
 ClipItem::ClipItem(QTreeWidget* parent, const SndFileR& w)
    : QTreeWidgetItem(parent), _wf(w)
       {
+        setText(COL_NAME, _wf.name());
+        setText(COL_REFS, QString().setNum(_wf.getRefCount()));
+        setText(COL_POS, QString().setNum(_wf.samplerate()));
+        setText(COL_LEN, QString().setNum(_wf.samples()));
       }
 
 //---------------------------------------------------------
@@ -71,6 +75,7 @@ static QString samples2smpte(int samples)
       }
 #endif
 
+/*
 //---------------------------------------------------------
 //   text
 //---------------------------------------------------------
@@ -91,6 +96,7 @@ QString ClipItem::text(int col) const
             }
       return s;
       }
+*/
 
 //---------------------------------------------------------
 //   ClipListEdit
diff --git a/muse2/muse/ctrl.cpp b/muse2/muse/ctrl.cpp
index 65a04ba1..0a863423 100644
--- a/muse2/muse/ctrl.cpp
+++ b/muse2/muse/ctrl.cpp
@@ -6,6 +6,7 @@
 //    controller handling for mixer automation
 //
 //  (C) Copyright 2003 Werner Schweer (ws@seh.de)
+//  (C) Copyright 2011 Time E. Real (terminator356 on users dot sourceforge dot net)
 //=========================================================
 
 
@@ -83,66 +84,86 @@ CtrlList::CtrlList()
 
 double CtrlList::value(int frame)
 {
-      if (!automation || empty()) {
-            return _curVal;
-            }
+      // Changed by Tim. p4.0.32...
+      
+      ///if (!automation || empty()) 
+      ///      return _curVal;
+      if(empty()) 
+        return _curVal;
 
+      double rv;
       ciCtrl i = upper_bound(frame); // get the index after current frame
 
       if (i == end()) { // if we are past all items just return the last value
-            ciCtrl i = end();
+            ///ciCtrl i = end();
             --i;
-            const CtrlVal& val = i->second;
-            _curVal = val.val;
+            ///const CtrlVal& val = i->second;
+            ///_curVal = val.val;
+            rv = i->second.val;
             }
       else if(_mode == DISCRETE)
       {
         if(i == begin())
-          _curVal = _default;
+        {
+          ///_curVal = _default;
+          //if(i->second.frame == frame)
+            rv = i->second.val;
+          //else  
+          //  rv = _default;
+        }  
         else
         {  
           --i;
-          const CtrlVal& val = i->second;
-          _curVal = val.val;
+          ///const CtrlVal& val = i->second;
+          ///_curVal = val.val;
+          rv = i->second.val;
         }  
       }
       else {
-        int frame2 = i->second.frame;
-        double val2 = i->second.val;
-        int frame1;
-        double val1;
+        ///int frame2 = i->second.frame;
+        ///double val2 = i->second.val;
+        ///int frame1;
+        ///double val1;
         if (i == begin()) {
-            frame1 = 0;
-            val1   = _default;
+            ///frame1 = 0;
+            ///val1   = _default;
+            rv = i->second.val;
         }
         else {
+            int frame2 = i->second.frame;
+            double val2 = i->second.val;
             --i;
-            frame1 = i->second.frame;
-            val1   = i->second.val;
-        }
-        //printf("before val1=%f val2=%f\n", val1,val2);
-        if (_valueType == VAL_LOG) {
-          val1 = 20.0*fast_log10(val1);
-          if (val1 < config.minSlider)
-            val1=config.minSlider;
-          val2 = 20.0*fast_log10(val2);
-          if (val2 < config.minSlider)
-            val2=config.minSlider;
-        }
-        //printf("after val1=%f val2=%f\n", val1,val2);
-        frame -= frame1;
-        val2  -= val1;
-        frame2 -= frame1;
-        val1 += (double(frame) * val2)/double(frame2);
-
-        if (_valueType == VAL_LOG) {
-          val1 = exp10(val1/20.0);
-        }
-        //printf("after val1=%f\n", val1);
-        _curVal = val1;
+            ///frame1 = i->second.frame;
+            ///val1   = i->second.val;
+            int frame1 = i->second.frame;
+            double val1   = i->second.val;
+        ///}
+            //printf("before val1=%f val2=%f\n", val1,val2);
+            if (_valueType == VAL_LOG) {
+              val1 = 20.0*fast_log10(val1);
+              if (val1 < config.minSlider)
+                val1=config.minSlider;
+              val2 = 20.0*fast_log10(val2);
+              if (val2 < config.minSlider)
+                val2=config.minSlider;
+            }
+            //printf("after val1=%f val2=%f\n", val1,val2);
+            frame -= frame1;
+            val2  -= val1;
+            frame2 -= frame1;
+            val1 += (double(frame) * val2)/double(frame2);
+    
+            if (_valueType == VAL_LOG) {
+              val1 = exp10(val1/20.0);
+            }
+            //printf("after val1=%f\n", val1);
+            ///_curVal = val1;
+            rv = val1;
+          }
       }
 // printf("autoVal %d %f\n", frame, _curVal);
-      return _curVal;
+      ///return _curVal;
+      return rv;
 }
 
 
@@ -152,9 +173,8 @@ double CtrlList::value(int frame)
 void CtrlList::setCurVal(double val)
 {
   _curVal = val;
-  if (size() < 2) {
-    add(0,val);
-  }
+  //if (size() < 2)   // Removed p4.0.32
+  //  add(0,val);
 }
 
 //---------------------------------------------------------
@@ -175,16 +195,14 @@ void CtrlList::add(int frame, double val)
 //   del
 //---------------------------------------------------------
 
-void CtrlList::del(int /* frame*/)
+void CtrlList::del(int frame)
       {
-      /*
       iCtrl e = find(frame);
       if (e == end()) {
-            printf("CtrlList::del(%d): not found\n", frame);
+            //printf("CtrlList::del(%d): not found\n", frame);
             return;
             }
       erase(e);
-      */
       }
 
 //---------------------------------------------------------
diff --git a/muse2/muse/ctrl.h b/muse2/muse/ctrl.h
index 34a31211..14f23643 100644
--- a/muse2/muse/ctrl.h
+++ b/muse2/muse/ctrl.h
@@ -6,6 +6,7 @@
 //    controller for mixer automation
 //
 //  (C) Copyright 2003-2004 Werner Schweer (ws@seh.de)
+//  (C) Copyright 2011 Time E. Real (terminator356 on users dot sourceforge dot net)
 //=========================================================
 
 #ifndef __CTRL_H__
@@ -120,8 +121,8 @@ class CtrlList : public std::map<int, CtrlVal, std::less<int> > {
       void setValueType(CtrlValueType t) { _valueType = t; }
 
       double value(int frame);
-      void add(int tick, double value);
-      void del(int tick);
+      void add(int frame, double value);
+      void del(int frame);
       void read(Xml& xml);
 
       void setColor( QColor c ) { _displayColor = c;}
diff --git a/muse2/muse/dssihost.cpp b/muse2/muse/dssihost.cpp
index 850fc8b8..72456dd1 100644
--- a/muse2/muse/dssihost.cpp
+++ b/muse2/muse/dssihost.cpp
@@ -1092,17 +1092,8 @@ bool DssiSynthIF::init(DssiSynth* s)
             }
             cl->setRange(min, max);
             cl->setName(QString(name));
-            LADSPA_PortRangeHint range = ld->PortRangeHints[k];
-            if(LADSPA_IS_HINT_TOGGLED(range.HintDescriptor))
-            {    
-              cl->setMode(CtrlList::DISCRETE);
-              cl->setValueType(VAL_BOOL);
-            }  
-            else  
-            {
-              cl->setMode(CtrlList::INTERPOLATE);
-              cl->setValueType(VAL_LINEAR);
-            }  
+            cl->setValueType(ladspaCtrlValueType(ld, k));
+            cl->setMode(ladspaCtrlMode(ld, k));
             
             ld->connect_port(handle, k, &controls[cip].val);
             
@@ -3627,6 +3618,8 @@ const char* DssiSynthIF::paramOutName(unsigned long i)       { return (synth &&
 //LADSPA_PortRangeHint DssiSynthIF::range(unsigned long i)     { return (synth && synth->dssi) ? synth->dssi->LADSPA_Plugin->PortRangeHints[i] : 0; }
 LADSPA_PortRangeHint DssiSynthIF::range(unsigned long i)     { return synth->dssi->LADSPA_Plugin->PortRangeHints[controls[i].idx]; }
 LADSPA_PortRangeHint DssiSynthIF::rangeOut(unsigned long i)  { return synth->dssi->LADSPA_Plugin->PortRangeHints[controlsOut[i].idx]; }
+CtrlValueType DssiSynthIF::ctrlValueType(unsigned long i) const { return ladspaCtrlValueType(synth->dssi->LADSPA_Plugin, controls[i].idx); }
+CtrlList::Mode DssiSynthIF::ctrlMode(unsigned long i) const     { return ladspaCtrlMode(synth->dssi->LADSPA_Plugin, controls[i].idx); };
 
 
 #else //DSSI_SUPPORT
diff --git a/muse2/muse/dssihost.h b/muse2/muse/dssihost.h
index d46cb570..12be7b50 100644
--- a/muse2/muse/dssihost.h
+++ b/muse2/muse/dssihost.h
@@ -282,6 +282,8 @@ class DssiSynthIF : public SynthIF, public PluginIBase
       const char* paramOutName(unsigned long /*i*/);
       LADSPA_PortRangeHint range(unsigned long /*i*/);
       LADSPA_PortRangeHint rangeOut(unsigned long /*i*/);
+      CtrlValueType ctrlValueType(unsigned long /*i*/) const; 
+      CtrlList::Mode ctrlMode(unsigned long /*i*/) const; 
 
       friend class DssiSynth;
       };
diff --git a/muse2/muse/mixer/astrip.cpp b/muse2/muse/mixer/astrip.cpp
index 658a4970..97ddc98c 100644
--- a/muse2/muse/mixer/astrip.cpp
+++ b/muse2/muse/mixer/astrip.cpp
@@ -209,15 +209,19 @@ void AudioStrip::songChanged(int val)
       if (autoType && (val & SC_AUTOMATION)) {
             autoType->blockSignals(true);
             autoType->setCurrentItem(track->automationType());
+            QPalette palette;
             if(track->automationType() == AUTO_TOUCH || track->automationType() == AUTO_WRITE)
                   {
-                  QPalette palette;
                   palette.setColor(QPalette::Button, QColor(Qt::red));
                   autoType->setPalette(palette);
                   }
+            else if(track->automationType() == AUTO_READ)
+                  {
+                  palette.setColor(QPalette::Button, QColor(Qt::green));
+                  autoType->setPalette(palette);
+                  }
             else  
                   {
-                  QPalette palette;
                   palette.setColor(QPalette::Button, qApp->palette().color(QPalette::Active, QPalette::Background));
                   autoType->setPalette(palette);
                   }
@@ -394,13 +398,14 @@ void AudioStrip::volumeChanged(double val)
       else
             vol = pow(10.0, val/20.0);
       volume = vol;
-      //audio->msgSetVolume((AudioTrack*)track, vol);
+      audio->msgSetVolume((AudioTrack*)track, vol);
       // p4.0.21 audio->msgXXX waits. Do we really need to?
-      ((AudioTrack*)track)->setVolume(vol);
+      //((AudioTrack*)track)->setVolume(vol);
       
       ((AudioTrack*)track)->recordAutomation(AC_VOLUME, vol);
 
-      song->update(SC_TRACK_MODIFIED); // for graphical automation update
+      //song->update(SC_TRACK_MODIFIED); // for graphical automation update
+      //song->controllerChange(track);
       }
 
 //---------------------------------------------------------
@@ -422,9 +427,9 @@ void AudioStrip::volumePressed()
       else
             vol = pow(10.0, val/20.0);
       volume = vol;
-      //audio->msgSetVolume((AudioTrack*)track, volume);
+      audio->msgSetVolume((AudioTrack*)track, volume);
       // p4.0.21 audio->msgXXX waits. Do we really need to?
-      ((AudioTrack*)track)->setVolume(volume);
+      //((AudioTrack*)track)->setVolume(volume);
       
       ((AudioTrack*)track)->startAutoRecord(AC_VOLUME, volume);
       }
@@ -468,9 +473,9 @@ void AudioStrip::volLabelChanged(double val)
             vol = pow(10.0, val/20.0);
       volume = vol;
       slider->setValue(val);
-      //audio->msgSetVolume((AudioTrack*)track, vol);
+      audio->msgSetVolume((AudioTrack*)track, vol);
       // p4.0.21 audio->msgXXX waits. Do we really need to?
-      ((AudioTrack*)track)->setVolume(vol);
+      //((AudioTrack*)track)->setVolume(vol);
       
       ((AudioTrack*)track)->startAutoRecord(AC_VOLUME, vol);
       }
@@ -486,9 +491,9 @@ void AudioStrip::panChanged(double val)
         track->enablePanController(false);
       
       panVal = val;  
-      //audio->msgSetPan(((AudioTrack*)track), val);
+      audio->msgSetPan(((AudioTrack*)track), val);
       // p4.0.21 audio->msgXXX waits. Do we really need to?
-      ((AudioTrack*)track)->setPan(val);
+      //((AudioTrack*)track)->setPan(val);
       
       ((AudioTrack*)track)->recordAutomation(AC_PAN, val);
       }
@@ -504,9 +509,9 @@ void AudioStrip::panPressed()
         track->enablePanController(false);
       
       panVal = pan->value();  
-      //audio->msgSetPan(((AudioTrack*)track), panVal);
+      audio->msgSetPan(((AudioTrack*)track), panVal);
       // p4.0.21 audio->msgXXX waits. Do we really need to?
-      ((AudioTrack*)track)->setPan(panVal);
+      //((AudioTrack*)track)->setPan(panVal);
       ((AudioTrack*)track)->startAutoRecord(AC_PAN, panVal);
       }
 
@@ -541,9 +546,9 @@ void AudioStrip::panLabelChanged(double val)
       
       panVal = val;
       pan->setValue(val);
-      //audio->msgSetPan((AudioTrack*)track, val);
+      audio->msgSetPan((AudioTrack*)track, val);
       // p4.0.21 audio->msgXXX waits. Do we really need to?
-      ((AudioTrack*)track)->setPan(val);
+      //((AudioTrack*)track)->setPan(val);
       ((AudioTrack*)track)->startAutoRecord(AC_PAN, val);
       }
 
@@ -935,15 +940,19 @@ AudioStrip::AudioStrip(QWidget* parent, AudioTrack* at)
       autoType->addAction(tr("Write"), AUTO_WRITE);
       autoType->setCurrentItem(t->automationType());
 
+      QPalette palette;
       if(t->automationType() == AUTO_TOUCH || t->automationType() == AUTO_WRITE)
             {
-            QPalette palette;
             palette.setColor(QPalette::Button, QColor(Qt::red));
             autoType->setPalette(palette);
             }
+      else if(t->automationType() == AUTO_READ)
+            {
+            palette.setColor(QPalette::Button, QColor(Qt::green));
+            autoType->setPalette(palette);
+            }
       else  
             {
-            QPalette palette;
             palette.setColor(QPalette::Button, qApp->palette().color(QPalette::Active, QPalette::Background));
             autoType->setPalette(palette);
             }
diff --git a/muse2/muse/mixer/panknob.cpp b/muse2/muse/mixer/panknob.cpp
index c99f0bd5..598bf5bf 100644
--- a/muse2/muse/mixer/panknob.cpp
+++ b/muse2/muse/mixer/panknob.cpp
@@ -27,9 +27,9 @@ PanKnob::PanKnob(QWidget* parent, AudioTrack* s)
 
 void PanKnob::valueChanged(double val)
       {
-      //audio->msgSetPan(src, val);
+      audio->msgSetPan(src, val);
       // p4.0.21 audio->msgXXX waits. Do we really need to?
-      src->setPan(val);
+      //src->setPan(val);
       }
 
 
diff --git a/muse2/muse/mplugins/rhythmbase.ui b/muse2/muse/mplugins/rhythmbase.ui
index 21373690..7d3458d1 100644
--- a/muse2/muse/mplugins/rhythmbase.ui
+++ b/muse2/muse/mplugins/rhythmbase.ui
@@ -542,9 +542,11 @@ Random Rhythm Generator is not enabled yet!</string>
      <height>38</height>
     </rect>
    </property>
-   <property name="label">
-    <string>Tools</string>
-   </property>
+   <widget class="QLabel">
+     <property name="text">
+       <string>Tools</string>
+     </property>
+   </widget>
    <addaction name="fileNewAction"/>
    <addaction name="fileOpenAction"/>
    <addaction name="fileSaveAction"/>
diff --git a/muse2/muse/plugin.cpp b/muse2/muse/plugin.cpp
index 215a7844..e6027c6f 100644
--- a/muse2/muse/plugin.cpp
+++ b/muse2/muse/plugin.cpp
@@ -445,7 +445,44 @@ float midi2LadspaValue(const LADSPA_Descriptor* plugin, unsigned long port, int
   return ret;
 }      
 
+//---------------------------------------------------------
+//   ladspaCtrlValueType
+//---------------------------------------------------------
 
+CtrlValueType ladspaCtrlValueType(const LADSPA_Descriptor* plugin, int port)
+{
+  LADSPA_PortRangeHint range = plugin->PortRangeHints[port];
+  LADSPA_PortRangeHintDescriptor desc = range.HintDescriptor;
+  
+  if(desc & LADSPA_HINT_INTEGER)
+    return VAL_INT;
+  else if(desc & LADSPA_HINT_LOGARITHMIC)
+    return VAL_LOG;
+  else if(desc & LADSPA_HINT_TOGGLED)
+    return VAL_BOOL;
+  else
+    return VAL_LINEAR;
+}  
+  
+//---------------------------------------------------------
+//   ladspaCtrlMode
+//---------------------------------------------------------
+
+CtrlList::Mode ladspaCtrlMode(const LADSPA_Descriptor* plugin, int port)
+{
+  LADSPA_PortRangeHint range = plugin->PortRangeHints[port];
+  LADSPA_PortRangeHintDescriptor desc = range.HintDescriptor;
+  
+  if(desc & LADSPA_HINT_INTEGER)
+    return CtrlList::DISCRETE;
+  else if(desc & LADSPA_HINT_LOGARITHMIC)
+    return CtrlList::INTERPOLATE;
+  else if(desc & LADSPA_HINT_TOGGLED)
+    return CtrlList::DISCRETE;
+  else
+    return CtrlList::INTERPOLATE;
+}  
+  
 // Works but not needed.
 /*
 //---------------------------------------------------------
@@ -1002,6 +1039,24 @@ float Plugin::defaultValue(unsigned long port) const
     */
 }
 
+//---------------------------------------------------------
+//   ctrlValueType
+//---------------------------------------------------------
+
+CtrlValueType Plugin::ctrlValueType(unsigned long i) const
+      {
+      return ladspaCtrlValueType(plugin, i);
+      }
+
+//---------------------------------------------------------
+//   ctrlMode
+//---------------------------------------------------------
+
+CtrlList::Mode Plugin::ctrlMode(unsigned long i) const
+      {
+      return ladspaCtrlMode(plugin, i);
+      }
+
 //---------------------------------------------------------
 //   loadPluginLib
 //---------------------------------------------------------
@@ -1699,20 +1754,11 @@ void PluginI::updateControllers()
   for(unsigned long i = 0; i < controlPorts; ++i) 
     //audio->msgSetPluginCtrlVal(this, genACnum(_id, i), controls[i].val);
     // p3.3.43
-    //audio->msgSetPluginCtrlVal(_track, genACnum(_id, i), controls[i].val);
+    audio->msgSetPluginCtrlVal(_track, genACnum(_id, i), controls[i].val);
     // p4.0.21 audio->msgXXX waits. Do we really need to?
-    _track->setPluginCtrlVal(genACnum(_id, i), controls[i].val);
+    //_track->setPluginCtrlVal(genACnum(_id, i), controls[i].val);  // TODO A faster bulk message
 }
   
-//---------------------------------------------------------
-//   valueType
-//---------------------------------------------------------
-
-CtrlValueType PluginI::valueType() const
-      {
-      return VAL_LINEAR;
-      }
-
 //---------------------------------------------------------
 //   setChannel
 //---------------------------------------------------------
@@ -3718,9 +3764,9 @@ void PluginGui::ctrlPressed(int param)
         if(track)
         {
           // p3.3.43
-          //audio->msgSetPluginCtrlVal(track, id, val);
+          audio->msgSetPluginCtrlVal(track, id, val);
           // p4.0.21 audio->msgXXX waits. Do we really need to?
-          track->setPluginCtrlVal(id, val);
+          //track->setPluginCtrlVal(id, val);
           
           track->startAutoRecord(id, val);
         }  
@@ -3737,9 +3783,9 @@ void PluginGui::ctrlPressed(int param)
         if(track)
         {
           // p3.3.43
-          //audio->msgSetPluginCtrlVal(track, id, val);
+          audio->msgSetPluginCtrlVal(track, id, val);
           // p4.0.21 audio->msgXXX waits. Do we really need to?
-          track->setPluginCtrlVal(id, val);
+          //track->setPluginCtrlVal(id, val);
           
           track->startAutoRecord(id, val);
         }  
@@ -3831,9 +3877,9 @@ void PluginGui::sliderChanged(double val, int param)
       if(track)
       {
         // p3.3.43
-        //audio->msgSetPluginCtrlVal(track, id, val);
+        audio->msgSetPluginCtrlVal(track, id, val);
         // p4.0.21 audio->msgXXX waits. Do we really need to?
-        track->setPluginCtrlVal(id, val);
+        //track->setPluginCtrlVal(id, val);
         
         track->recordAutomation(id, val);
       }  
@@ -3875,9 +3921,9 @@ void PluginGui::labelChanged(double val, int param)
       if(track)
       {
         // p3.3.43
-        //audio->msgSetPluginCtrlVal(track, id, val);
+        audio->msgSetPluginCtrlVal(track, id, val);
         // p4.0.21 audio->msgXXX waits. Do we really need to?
-        track->setPluginCtrlVal(id, val);
+        //track->setPluginCtrlVal(id, val);
         
         track->startAutoRecord(id, val);
       }  
@@ -4277,9 +4323,9 @@ void PluginGui::guiParamChanged(int idx)
           //if(track)
           //{
             // p3.3.43
-            //audio->msgSetPluginCtrlVal(track, id, val);
+            audio->msgSetPluginCtrlVal(track, id, val);
             // p4.0.21 audio->msgXXX waits. Do we really need to?
-            track->setPluginCtrlVal(id, val);
+            //track->setPluginCtrlVal(id, val);
             
             switch(type) 
             {
@@ -4413,9 +4459,9 @@ void PluginGui::guiSliderPressed(int idx)
       
       //audio->msgSetPluginCtrlVal(((PluginI*)plugin), id, val);
       // p3.3.43
-      //audio->msgSetPluginCtrlVal(track, id, val);
+      audio->msgSetPluginCtrlVal(track, id, val);
       // p4.0.21 audio->msgXXX waits. Do we really need to?
-      track->setPluginCtrlVal(id, val);
+      //track->setPluginCtrlVal(id, val);
       
       track->startAutoRecord(id, val);
       
diff --git a/muse2/muse/plugin.h b/muse2/muse/plugin.h
index 30cc5912..dec77d2f 100644
--- a/muse2/muse/plugin.h
+++ b/muse2/muse/plugin.h
@@ -177,6 +177,8 @@ class Plugin {
       //double defaultValue(unsigned long port) const;
       float defaultValue(unsigned long port) const; // p4.0.21     
       void range(unsigned long i, float*, float*) const;
+      CtrlValueType ctrlValueType(unsigned long /*i*/) const;
+      CtrlList::Mode ctrlMode(unsigned long /*i*/) const;
       
       const char* portName(unsigned long i) {
             return plugin ? plugin->PortNames[i] : 0;
@@ -354,6 +356,9 @@ class PluginIBase
       virtual const char* paramOutName(unsigned long /*i*/) = 0;
       virtual LADSPA_PortRangeHint range(unsigned long /*i*/) = 0;
       virtual LADSPA_PortRangeHint rangeOut(unsigned long /*i*/) = 0;
+      
+      virtual CtrlValueType ctrlValueType(unsigned long /*i*/) const = 0;
+      virtual CtrlList::Mode ctrlMode(unsigned long /*i*/) const = 0;
       QString dssi_ui_filename() const;
       
       //virtual void showGui(bool) = 0;         // p4.0.20
@@ -499,7 +504,6 @@ class PluginI : public PluginIBase {
       QString pluginLabel() const    { return _plugin->label(); }
       QString label() const          { return _label; }
       QString name() const           { return _name; }
-      CtrlValueType valueType() const;
       QString lib() const            { return _plugin->lib(); }
       QString dirPath() const        { return _plugin->dirPath(); }
       QString fileName() const       { return _plugin->fileName(); }
@@ -560,6 +564,8 @@ class PluginI : public PluginIBase {
       LADSPA_PortRangeHint range(unsigned long i) { return _plugin->range(controls[i].idx); }
       LADSPA_PortRangeHint rangeOut(unsigned long i) { return _plugin->range(controlsOut[i].idx); }
       bool inPlaceCapable() const { return _plugin->inPlaceCapable(); }
+      CtrlValueType ctrlValueType(unsigned long i) const { return _plugin->ctrlValueType(controls[i].idx); }
+      CtrlList::Mode ctrlMode(unsigned long i) const { return _plugin->ctrlMode(controls[i].idx); };
       };
 
 //---------------------------------------------------------
@@ -650,6 +656,8 @@ extern bool ladspaDefaultValue(const LADSPA_Descriptor* plugin, unsigned long po
 extern void ladspaControlRange(const LADSPA_Descriptor* plugin, unsigned long port, float* min, float* max);
 extern bool ladspa2MidiControlValues(const LADSPA_Descriptor* plugin, unsigned long port, int ctlnum, int* min, int* max, int* def);
 extern float midi2LadspaValue(const LADSPA_Descriptor* plugin, unsigned long port, int ctlnum, int val);
+extern CtrlValueType ladspaCtrlValueType(const LADSPA_Descriptor* plugin, int port);
+extern CtrlList::Mode ladspaCtrlMode(const LADSPA_Descriptor* plugin, int port);
 //extern MidiController* ladspa2MidiController(const LADSPA_Descriptor* plugin, unsigned long port, int ctlnum);
 
 #endif
diff --git a/muse2/muse/seqmsg.cpp b/muse2/muse/seqmsg.cpp
index 57aadc18..035ee949 100644
--- a/muse2/muse/seqmsg.cpp
+++ b/muse2/muse/seqmsg.cpp
@@ -338,6 +338,7 @@ void Audio::msgSetVolume(AudioTrack* src, double val)
       msg.dval  = val;
       sendMsg(&msg);
       //muse->arranger->controllerChanged(src);
+      song->controllerChange(src);
       }
 
 //---------------------------------------------------------
@@ -352,6 +353,7 @@ void Audio::msgSetPan(AudioTrack* node, double val)
       msg.dval  = val;
       sendMsg(&msg);
       //muse->arranger->controllerChanged(node);
+      song->controllerChange(node);
       }
 
 //---------------------------------------------------------
@@ -513,6 +515,7 @@ void Audio::msgSetPluginCtrlVal(AudioTrack* track, int param, double val)
       msg.snode  = track;
       sendMsg(&msg);
       //muse->arranger->controllerChanged(track);
+      song->controllerChange(track);
 }
 
 //---------------------------------------------------------
@@ -529,6 +532,7 @@ void Audio::msgSwapControllerIDX(AudioTrack* node, int idx1, int idx2)
       msg.b      = idx2;
       sendMsg(&msg);
       //muse->arranger->controllerChanged(node);
+      song->controllerChange(node);
 }
 
 //---------------------------------------------------------
@@ -544,6 +548,7 @@ void Audio::msgClearControllerEvents(AudioTrack* node, int acid)
       msg.ival   = acid;
       sendMsg(&msg);
       //muse->arranger->controllerChanged(node);
+      song->controllerChange(node);
 }
 
 //---------------------------------------------------------
@@ -588,6 +593,7 @@ void Audio::msgEraseACEvent(AudioTrack* node, int acid, int frame)
       msg.a      = frame; 
       sendMsg(&msg);
       //muse->arranger->controllerChanged(node);
+      song->controllerChange(node);
 }
 
 //---------------------------------------------------------
@@ -605,6 +611,7 @@ void Audio::msgEraseRangeACEvents(AudioTrack* node, int acid, int frame1, int fr
       msg.b      = frame2; 
       sendMsg(&msg);
       //muse->arranger->controllerChanged(node);
+      song->controllerChange(node);
 }
 
 //---------------------------------------------------------
@@ -622,6 +629,26 @@ void Audio::msgAddACEvent(AudioTrack* node, int acid, int frame, double val)
       msg.dval   = val;
       sendMsg(&msg);
       //muse->arranger->controllerChanged(node);
+      song->controllerChange(node);
+}
+
+//---------------------------------------------------------
+//   msgChangeACEvent
+//---------------------------------------------------------
+
+void Audio::msgChangeACEvent(AudioTrack* node, int acid, int frame, int newFrame, double val)
+{
+      AudioMsg msg;
+      
+      msg.id     = AUDIO_CHANGE_AC_EVENT;
+      msg.snode  = node;
+      msg.ival   = acid;
+      msg.a      = frame; 
+      msg.b      = newFrame; 
+      msg.dval   = val;
+      sendMsg(&msg);
+      //muse->arranger->controllerChanged(node);
+      song->controllerChange(node);
 }
 
 //---------------------------------------------------------
diff --git a/muse2/muse/song.cpp b/muse2/muse/song.cpp
index 6d0541a3..9d1c3afb 100644
--- a/muse2/muse/song.cpp
+++ b/muse2/muse/song.cpp
@@ -714,6 +714,23 @@ void Song::changeAllPortDrumCtrlEvents(bool add, bool drumonly)
   }
 }
 
+void Song::addACEvent(AudioTrack* t, int acid, int frame, double val)
+{
+  audio->msgAddACEvent(t, acid, frame, val);
+  emit controllerChanged(t); 
+}
+
+void Song::changeACEvent(AudioTrack* t, int acid, int frame, int newFrame, double val)
+{
+  audio->msgChangeACEvent(t, acid, frame, newFrame, val);
+  emit controllerChanged(t); 
+}
+
+void Song::controllerChange(Track* t)
+{
+  emit controllerChanged(t); 
+}
+
 //---------------------------------------------------------
 //   cmdAddRecordedEvents
 //    add recorded Events into part
diff --git a/muse2/muse/song.h b/muse2/muse/song.h
index 45751418..bb96b619 100644
--- a/muse2/muse/song.h
+++ b/muse2/muse/song.h
@@ -248,6 +248,10 @@ class Song : public QObject {
       void cmdChangeWave(QString original, QString tmpfile, unsigned sx, unsigned ex);
       void remapPortDrumCtrlEvents(int mapidx, int newnote, int newchan, int newport);
       void changeAllPortDrumCtrlEvents(bool add, bool drumonly = false);
+      
+      void addACEvent(AudioTrack* t, int acid, int frame, double val);
+      void changeACEvent(AudioTrack* t, int acid, int frame, int newFrame, double val);
+      void controllerChange(Track* t);
 
       //-----------------------------------------
       //   part manipulations
@@ -400,7 +404,8 @@ class Song : public QObject {
       void quantizeChanged(bool);
       void markerChanged(int);
       void midiPortsChanged();
-      void midiNote(int pitch, int velo);
+      void midiNote(int pitch, int velo);  
+      void controllerChanged(Track* t); 
       };
 
 extern Song* song;
diff --git a/muse2/muse/track.cpp b/muse2/muse/track.cpp
index 5f358375..eae74ccf 100644
--- a/muse2/muse/track.cpp
+++ b/muse2/muse/track.cpp
@@ -139,7 +139,9 @@ int Track::y() const
                   return yy;
             yy += (*it)->height();
             }
-      printf("Track::y(%s): track not in tracklist\n", name().toLatin1().constData());
+      // FIXME Get this when loading a song with automation graphs showing. Benign. Likely song not fully loaded yet. p4.0.32
+      if(debugMsg)
+        printf("Track::y(%s): track not in tracklist\n", name().toLatin1().constData());
       return -1;
       }
 
diff --git a/muse2/muse/track.h b/muse2/muse/track.h
index 50870166..de766e32 100644
--- a/muse2/muse/track.h
+++ b/muse2/muse/track.h
@@ -428,6 +428,7 @@ class AudioTrack : public Track {
       void eraseACEvent(int, int);
       void eraseRangeACEvents(int, int, int);
       void addACEvent(int, int, double);
+      void changeACEvent(int id, int frame, int newframe, double newval);
       };
 
 //---------------------------------------------------------
-- 
cgit v1.2.3