diff options
Diffstat (limited to 'src/main/java/com/jsyn/swing/EnvelopeEditorBox.java')
-rw-r--r-- | src/main/java/com/jsyn/swing/EnvelopeEditorBox.java | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/src/main/java/com/jsyn/swing/EnvelopeEditorBox.java b/src/main/java/com/jsyn/swing/EnvelopeEditorBox.java new file mode 100644 index 0000000..2db4c29 --- /dev/null +++ b/src/main/java/com/jsyn/swing/EnvelopeEditorBox.java @@ -0,0 +1,573 @@ +/* + * Copyright 1997 Phil Burk, Mobileer Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jsyn.swing; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.util.ArrayList; + +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.unitgen.VariableRateDataReader; + +/** + * Edit a list of ordered duration,value pairs suitable for use with a SegmentedEnvelope. + * + * @author (C) 1997-2013 Phil Burk, SoftSynth.com + * @see EnvelopePoints + * @see SegmentedEnvelope + * @see VariableRateDataReader + */ + +/* ========================================================================== */ +public class EnvelopeEditorBox extends XYController implements MouseListener, MouseMotionListener { + EnvelopePoints points; + ArrayList<EditListener> listeners = new ArrayList<EditListener>(); + int dragIndex = -1; + double dragLowLimit; + double dragHighLimit; + double draggedPoint[]; + double xBefore; // WX value before point + double xPicked; // WX value of picked point + double dragWX; + double dragWY; + int maxPoints = Integer.MAX_VALUE; + int radius = 4; + double verticalBarSpacing = 1.0; + boolean verticalBarsEnabled = false; + double maximumXRange = Double.MAX_VALUE; + double minimumXRange = 0.1; + int rangeStart = -1; // gx coordinates + int rangeEnd = -1; + int mode = EDIT_POINTS; + public final static int EDIT_POINTS = 0; + public final static int SELECT_SUSTAIN = 1; + public final static int SELECT_RELEASE = 2; + + Color rangeColor = Color.RED; + Color sustainColor = Color.BLUE; + Color releaseColor = Color.YELLOW; + Color overlapColor = Color.GREEN; + Color firstLineColor = Color.GRAY; + + public interface EditListener { + public void objectEdited(Object editor, Object edited); + } + + public EnvelopeEditorBox() { + addMouseListener(this); + addMouseMotionListener(this); + } + + public void setMaximumXRange(double maxXRange) { + maximumXRange = maxXRange; + } + + public double getMaximumXRange() { + return maximumXRange; + } + + public void setMinimumXRange(double minXRange) { + minimumXRange = minXRange; + } + + public double getMinimumXRange() { + return minimumXRange; + } + + public void setSelection(int start, int end) { + switch (mode) { + case SELECT_SUSTAIN: + points.setSustainLoop(start, end); + break; + case SELECT_RELEASE: + points.setReleaseLoop(start, end); + break; + } + // LOGGER.debug("start = " + start + ", end = " + end ); + } + + /** Set mode to either EDIT_POINTS or SELECT_SUSTAIN, SELECT_RELEASE; */ + public void setMode(int mode) { + this.mode = mode; + } + + public int getMode() { + return mode; + } + + /** + * Add a listener to receive edit events. Listener will be passed the editor object and the + * edited object. + */ + public void addEditListener(EditListener listener) { + listeners.add(listener); + } + + public void removeEditListener(EditListener listener) { + listeners.remove(listener); + } + + /** Send event to every subscribed listener. */ + public void fireObjectEdited() { + for (EditListener listener : listeners) { + listener.objectEdited(this, points); + } + } + + public void setMaxPoints(int maxPoints) { + this.maxPoints = maxPoints; + } + + public int getMaxPoints() { + return maxPoints; + } + + public int getNumPoints() { + return points.size(); + } + + public void setPoints(EnvelopePoints points) { + this.points = points; + setMaxWorldY(points.getMaximumValue()); + } + + public EnvelopePoints getPoints() { + return points; + } + + /** + * Return index of point before this X position. + */ + private int findPointBefore(double wx) { + int pnt = -1; + double px = 0.0; + xBefore = 0.0; + for (int i = 0; i < points.size(); i++) { + px += points.getDuration(i); + if (px > wx) + break; + pnt = i; + xBefore = px; + } + return pnt; + } + + private int pickPoint(double wx, double wxAperture, double wy, double wyAperture) { + double px = 0.0; + double wxLow = wx - wxAperture; + double wxHigh = wx + wxAperture; + // LOGGER.debug("wxLow = " + wxLow + ", wxHigh = " + wxHigh ); + double wyLow = wy - wyAperture; + double wyHigh = wy + wyAperture; + // LOGGER.debug("wyLow = " + wyLow + ", wyHigh = " + wyHigh ); + double wxScale = 1.0 / wxAperture; // only divide once, then multiply + double wyScale = 1.0 / wyAperture; + int bestPoint = -1; + double bestDistance = Double.MAX_VALUE; + for (int i = 0; i < points.size(); i++) { + double dar[] = points.getPoint(i); + px += dar[0]; + double py = dar[1]; + // LOGGER.debug("px = " + px + ", py = " + py ); + if ((px > wxLow) && (px < wxHigh) && (py > wyLow) && (py < wyHigh)) { + /* Inside pick range. Calculate distance squared. */ + double ndx = (px - wx) * wxScale; + double ndy = (py - wy) * wyScale; + double dist = (ndx * ndx) + (ndy * ndy); + // LOGGER.debug("dist = " + dist ); + if (dist < bestDistance) { + bestPoint = i; + bestDistance = dist; + xPicked = px; + } + } + } + return bestPoint; + } + + private void clickDownRange(boolean shiftDown, int gx, int gy) { + setSelection(-1, -1); + rangeStart = rangeEnd = gx; + repaint(); + } + + private void dragRange(int gx, int gy) { + rangeEnd = gx; + repaint(); + } + + private void clickUpRange(int gx, int gy) { + dragRange(gx, gy); + if (rangeEnd < rangeStart) { + int temp = rangeEnd; + rangeEnd = rangeStart; + rangeStart = temp; + } + // LOGGER.debug("clickUpRange: gx = " + gx + ", rangeStart = " + + // rangeStart ); + double wx = convertGXtoWX(rangeStart); + int i0 = findPointBefore(wx); + wx = convertGXtoWX(rangeEnd); + int i1 = findPointBefore(wx); + + if (i1 == i0) { + // set single point at zero so there is nothing played for queueOn() + if (gx < 0) { + setSelection(0, 0); + } + // else clear any existing loop + } else if (i1 == (i0 + 1)) { + setSelection(i1 + 1, i1 + 1); // set to a single point + } else if (i1 > (i0 + 1)) { + setSelection(i0 + 1, i1 + 1); // set to a range of two or more + } + + rangeStart = -1; + rangeEnd = -1; + fireObjectEdited(); + } + + private void clickDownPoints(boolean shiftDown, int gx, int gy) { + dragIndex = -1; + double wx = convertGXtoWX(gx); + double wy = convertGYtoWY(gy); + // calculate world values for aperture + double wxAp = convertGXtoWX(radius + 2) - convertGXtoWX(0); + // LOGGER.debug("wxAp = " + wxAp ); + double wyAp = convertGYtoWY(0) - convertGYtoWY(radius + 2); + // LOGGER.debug("wyAp = " + wyAp ); + int pnt = pickPoint(wx, wxAp, wy, wyAp); + // LOGGER.debug("pickPoint = " + pnt); + if (shiftDown) { + if (pnt >= 0) { + points.removePoint(pnt); + repaint(); + } + } else { + if (pnt < 0) // didn't hit one so look for point to left of click + { + if (points.size() < maxPoints) // add if room + { + pnt = findPointBefore(wx); + // LOGGER.debug("pointBefore = " + pnt); + dragIndex = pnt + 1; + if (pnt == (points.size() - 1)) { + points.add(wx - xBefore, wy); + } else { + points.insert(dragIndex, wx - xBefore, wy); + } + dragLowLimit = xBefore; + dragHighLimit = wx + (maximumXRange - points.getTotalDuration()); + repaint(); + } + } else + // hit one so drag it + { + dragIndex = pnt; + if (dragIndex <= 0) + dragLowLimit = 0.0; // FIXME envelope drag limit + else + dragLowLimit = xPicked - points.getPoint(dragIndex)[0]; + dragHighLimit = xPicked + (maximumXRange - points.getTotalDuration()); + // LOGGER.debug("dragLowLimit = " + dragLowLimit ); + } + } + // Set up drag point if we are dragging. + if (dragIndex >= 0) { + draggedPoint = points.getPoint(dragIndex); + } + + } + + private void dragPoint(int gx, int gy) { + if (dragIndex < 0) + return; + + double wx = convertGXtoWX(gx); + if (wx < dragLowLimit) + wx = dragLowLimit; + else if (wx > dragHighLimit) + wx = dragHighLimit; + draggedPoint[0] = wx - dragLowLimit; // duration + + double wy = convertGYtoWY(gy); + wy = clipWorldY(wy); + draggedPoint[1] = wy; + dragWY = wy; + dragWX = wx; + points.setDirty(true); + repaint(); + } + + private void clickUpPoints(int gx, int gy) { + dragPoint(gx, gy); + fireObjectEdited(); + dragIndex = -1; + } + + // Implement the MouseMotionListener interface for AWT 1.1 + @Override + public void mouseDragged(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + if (points == null) + return; + if (mode == EDIT_POINTS) { + dragPoint(x, y); + } else { + dragRange(x, y); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + } + + // Implement the MouseListener interface for AWT 1.1 + @Override + public void mousePressed(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + if (points == null) + return; + if (mode == EDIT_POINTS) { + clickDownPoints(e.isShiftDown(), x, y); + } else { + clickDownRange(e.isShiftDown(), x, y); + } + } + + @Override + public void mouseClicked(MouseEvent e) { + } + + @Override + public void mouseReleased(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + if (points == null) + return; + if (mode == EDIT_POINTS) { + clickUpPoints(x, y); + } else { + clickUpRange(x, y); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + /** + * Draw selected range. + */ + private void drawRange(Graphics g) { + if (rangeStart >= 0) { + int height = getHeight(); + int gx0 = 0, gx1 = 0; + + if (rangeEnd < rangeStart) { + gx0 = rangeEnd; + gx1 = rangeStart; + } else { + gx0 = rangeStart; + gx1 = rangeEnd; + } + g.setColor(rangeColor); + g.fillRect(gx0, 0, gx1 - gx0, height); + } + } + + private void drawUnderSelection(Graphics g, int start, int end) { + if (start >= 0) { + int height = getHeight(); + int gx0 = 0, gx1 = radius; + double wx = 0.0; + for (int i = 0; i <= (end - 1); i++) { + double dar[] = (double[]) points.elementAt(i); + wx += dar[0]; + if (start == (i + 1)) { + gx0 = convertWXtoGX(wx) + radius; + } + if (end == (i + 1)) { + gx1 = convertWXtoGX(wx) + radius; + } + } + if (gx0 == gx1) + gx0 = gx0 - radius; + g.fillRect(gx0, 0, gx1 - gx0, height); + } + } + + private void drawSelections(Graphics g) { + int sus0 = points.getSustainBegin(); + int sus1 = points.getSustainEnd(); + int rel0 = points.getReleaseBegin(); + int rel1 = points.getReleaseEnd(); + + g.setColor(sustainColor); + drawUnderSelection(g, sus0, sus1); + g.setColor(releaseColor); + drawUnderSelection(g, rel0, rel1); + // draw overlapping sustain and release region + if (sus1 >= rel0) { + int sel1 = (rel1 < sus1) ? rel1 : sus1; + g.setColor(overlapColor); + drawUnderSelection(g, rel0, sel1); + } + } + + /** + * Override this to draw a grid or other stuff under the envelope. + */ + public void drawUnderlay(Graphics g) { + if (dragIndex < 0) { + drawSelections(g); + drawRange(g); + } + if (verticalBarsEnabled) + drawVerticalBars(g); + } + + public void setVerticalBarsEnabled(boolean flag) { + verticalBarsEnabled = flag; + } + + public boolean areVerticalBarsEnabled() { + return verticalBarsEnabled; + } + + /** + * Set spacing in world coordinates. + */ + public void setVerticalBarSpacing(double spacing) { + verticalBarSpacing = spacing; + } + + public double getVerticalBarSpacing() { + return verticalBarSpacing; + } + + /** + * Draw vertical lines. + */ + private void drawVerticalBars(Graphics g) { + int width = getWidth(); + int height = getHeight(); + double wx = verticalBarSpacing; + int gx; + + // g.setColor( getBackground().darker() ); + g.setColor(Color.lightGray); + while (true) { + gx = convertWXtoGX(wx); + if (gx > width) + break; + g.drawLine(gx, 0, gx, height); + wx += verticalBarSpacing; + } + } + + public void drawPoints(Graphics g, Color lineColor) { + double wx = 0.0; + int gx1 = 0; + int gy1 = getHeight(); + for (int i = 0; i < points.size(); i++) { + double dar[] = (double[]) points.elementAt(i); + wx += dar[0]; + double wy = dar[1]; + int gx2 = convertWXtoGX(wx); + int gy2 = convertWYtoGY(wy); + if (i == 0) { + g.setColor(isEnabled() ? firstLineColor : firstLineColor.darker()); + g.drawLine(gx1, gy1, gx2, gy2); + g.setColor(isEnabled() ? lineColor : lineColor.darker()); + } else if (i > 0) { + g.drawLine(gx1, gy1, gx2, gy2); + } + int diameter = (2 * radius) + 1; + g.fillOval(gx2 - radius, gy2 - radius, diameter, diameter); + gx1 = gx2; + gy1 = gy2; + } + } + + public void drawAllPoints(Graphics g) { + drawPoints(g, getForeground()); + } + + /* Override default paint action. */ + @Override + public void paint(Graphics g) { + double wx = 0.0; + int width = getWidth(); + int height = getHeight(); + + // draw background and erase all values + g.setColor(isEnabled() ? getBackground() : getBackground().darker()); + g.fillRect(0, 0, width, height); + + if (points == null) { + g.setColor(getForeground()); + g.drawString("No EnvelopePoints", 10, 30); + return; + } + + // Determine total duration. + if (points.size() > 0) { + wx = points.getTotalDuration(); + // Adjust max X so that we see entire circle of last point. + double radiusWX = this.convertGXtoWX(radius) - this.getMinWorldX(); + double wxFar = wx + radiusWX; + if (wxFar > getMaxWorldX()) { + if (wx > maximumXRange) + wxFar = maximumXRange; + setMaxWorldX(wxFar); + } else if (wx < (getMaxWorldX() * 0.7)) { + double newMax = wx / 0.7001; // make slightly larger to prevent + // endless jitter, FIXME - still + // needed after repaint() + // removed from setMaxWorldX? + // LOGGER.debug("newMax = " + newMax ); + if (newMax < minimumXRange) + newMax = minimumXRange; + setMaxWorldX(newMax); + } + } + // LOGGER.debug("total X = " + wx ); + + drawUnderlay(g); + + drawAllPoints(g); + + /* Show X,Y,TotalX as text. */ + g.drawString(points.getName() + ", len=" + String.format("%7.3f", wx), 5, 15); + if ((draggedPoint != null) && (dragIndex >= 0)) { + String s = "i=" + dragIndex + ", dur=" + + String.format("%7.3f", draggedPoint[0]) + ", y = " + + String.format("%8.4f", draggedPoint[1]); + g.drawString(s, 5, 30); + } + } +} |