f@0: /* f@0: Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. f@0: f@0: Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) f@0: f@0: This program is free software: you can redistribute it and/or modify f@0: it under the terms of the GNU General Public License as published by f@0: the Free Software Foundation, either version 3 of the License, or f@0: (at your option) any later version. f@0: f@0: This program is distributed in the hope that it will be useful, f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the f@0: GNU General Public License for more details. f@0: f@0: You should have received a copy of the GNU General Public License f@0: along with this program. If not, see . f@0: */ f@0: package uk.ac.qmul.eecs.depic.daw.gui; f@0: f@0: import java.awt.BasicStroke; f@0: import java.awt.Color; f@0: import java.awt.Dimension; f@0: import java.awt.Graphics; f@0: import java.awt.Graphics2D; f@0: import java.awt.RenderingHints; f@0: import java.awt.Shape; f@0: import java.awt.geom.Line2D; f@0: import java.util.ArrayList; f@0: import java.util.Collection; f@0: import java.util.List; f@0: f@0: import javax.swing.event.ChangeEvent; f@0: import javax.swing.event.ChangeListener; f@0: f@0: import uk.ac.qmul.eecs.depic.patterns.MathUtils; f@0: import uk.ac.qmul.eecs.depic.patterns.Range; f@0: import uk.ac.qmul.eecs.depic.patterns.Sequence; f@0: import uk.ac.qmul.eecs.depic.patterns.SequenceEvent; f@0: import uk.ac.qmul.eecs.depic.patterns.SequenceListener; f@0: f@2: /** f@2: * f@2: * A visual representation of a sequence graph. Here used for automation graphs overlaying the audio track. f@2: * f@2: * It listen to sequences (automations) and, when the sequence is updated, it updates itself f@2: * and triggers the registered change listeners. f@2: */ f@2: public class SequenceGraph implements SequenceListener { f@0: private static final float SEQUENCE_LINE_WIDTH = 4.0f; f@0: private static final int SEQUENCE_POINTS_CAPACITY = 30; f@0: f@0: private Sequence sequence; f@0: private Color color; f@0: private List sequencePoints; f@0: private List sequenceLines; f@0: private Dimension size; f@0: private Collection listeners; f@0: private float millisecPerPixel; f@0: f@0: public SequenceGraph(Color color, Dimension size) { f@0: sequencePoints = new ArrayList<>(SEQUENCE_POINTS_CAPACITY); f@0: sequenceLines = new ArrayList<>(SEQUENCE_POINTS_CAPACITY-1); f@0: listeners = new ArrayList<>(); f@0: this.color = color; f@0: this.size = size; f@0: } f@0: f@0: public Dimension getSize(){ f@0: return size; f@0: } f@0: f@0: public void setSize(Dimension size){ f@0: this.size = size; f@0: } f@0: f@0: public void setColor(Color c){ f@0: this.color = c; f@0: } f@0: f@0: public float getMillisecondsPerPixel(){ f@0: return millisecPerPixel; f@0: } f@0: f@0: public void setMillisecPerPixel(float millisecPerPixel){ f@0: this.millisecPerPixel = millisecPerPixel; f@0: } f@0: f@0: public Color getColor(){ f@0: return color; f@0: } f@0: f@0: public List getSequencePoints(){ f@0: return sequencePoints; f@0: } f@0: f@0: public List getSequenceLines(){ f@0: return sequenceLines; f@0: } f@0: f@0: public void draw(Graphics g ){ f@0: if(sequenceLines.isEmpty()){ f@0: return; f@0: } f@0: f@0: Graphics2D g2 = (Graphics2D)g; f@0: g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); f@0: Color oldColor = g2.getColor(); f@0: f@0: g2.setColor(color); f@0: f@0: /* draw sequence points and lines */ f@0: for(Shape point : sequencePoints){ f@0: g2.fill(point); f@0: } f@0: f@0: for(Shape line : sequenceLines){ f@0: g2.fill(line); f@0: } f@0: /* restores previous state */ f@0: g2.setColor(oldColor); f@0: g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); f@0: } f@0: f@0: public void addChangeListener(ChangeListener l){ f@0: listeners.add(l); f@0: } f@0: f@0: public void removeChangeListener(ChangeListener l){ f@0: listeners.remove(l); f@0: } f@0: f@0: protected void fireChangeListeners(){ f@0: ChangeEvent evt = new ChangeEvent(this); f@0: for(ChangeListener l : listeners){ f@0: l.stateChanged(evt); f@0: } f@0: } f@0: f@0: @Override f@0: public void sequenceUpdated(T evt) { f@0: updateSequence(); f@0: } f@0: f@0: /** f@0: * f@0: * Sets a new sequence to be listened to. If the argument is {@code null} f@0: * it stops listening to the previously set sequence, if any,m and clears all f@0: * sequence points and lines. it calls {@code updateSequence}. f@0: * f@0: * @param s the new sequence. f@0: */ f@0: public void setSequence(Sequence s){ f@0: /* remove this from the old sequence if any and add this to the new sequence */ f@0: if(sequence != null){ f@0: sequence.removeSequenceListener(this); f@0: } f@0: f@0: if(s != null){ f@0: s.addSequenceListener(this); f@0: } f@0: f@0: sequence = s; f@0: updateSequence(); f@0: } f@0: f@0: public Sequence getSequence(){ f@0: return sequence; f@0: } f@0: f@0: /** f@0: * Recompute the sequence points and lines. It should be called whenever a change is done to this f@0: * object. For example after {@code setSize} and {@code setMilliseconsPerPixel}. f@0: * f@0: */ f@0: public void updateSequence(){ f@0: /* clear previous values. will rebuild them if type is not NONE */ f@0: sequencePoints.clear(); f@0: sequenceLines.clear(); f@0: f@0: if(sequence == null){ f@0: /* no sequence represented any more. Fire listeners with * f@0: * sequencePoints and sequenceLines cleared */ f@0: fireChangeListeners(); f@0: return; f@0: } f@0: f@0: /* height and width of this panel in order to draw proportionately */ f@0: int height = getSize().height; f@0: int width = getSize().width; f@0: f@0: /* get the sequence range */ f@0: MathUtils.Scale scale = new MathUtils.Scale(sequence.getRange(),new Range(0.0f,(float)height)); f@0: BasicStroke stroke = new BasicStroke(SEQUENCE_LINE_WIDTH); f@0: f@0: /* if there are no sequence values, draw a straight line from the beginning to the end of the track */ f@0: if(sequence.getValuesNum() == 0){ f@0: sequenceLines.add(stroke.createStrokedShape( f@0: new Line2D.Float( f@0: 0f, f@0: height - scale.linear(sequence.getBegin()),/* height is reversed as in swing the top is 0 */ f@0: width, f@0: height - scale.linear(sequence.getEnd()))/* height is reversed as in swing the top is 0 */ f@0: )); f@0: }else{ f@0: Sequence.Value previous = null; f@0: /*previousX and previousY are the centre of sequence values calculated in the previous cycle */ f@0: int previousX = 0; f@0: int previousY = 0; f@0: f@0: for(int i=0; i