aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/jsyn/swing/EnvelopeEditorBox.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/jsyn/swing/EnvelopeEditorBox.java')
-rw-r--r--src/main/java/com/jsyn/swing/EnvelopeEditorBox.java573
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);
+ }
+ }
+}