view src/uk/ac/qmul/eecs/depic/daw/gui/SequenceGraph.java @ 2:c0412c81d274

Added documentation
author Fiore Martin <f.martin@qmul.ac.uk>
date Thu, 18 Feb 2016 18:35:26 +0000
parents 3074a84ef81e
children
line wrap: on
line source
/*  
 Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation.

 Copyright (C) 2015  Queen Mary University of London (http://depic.eecs.qmul.ac.uk/)
	
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.qmul.eecs.depic.daw.gui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import uk.ac.qmul.eecs.depic.patterns.MathUtils;
import uk.ac.qmul.eecs.depic.patterns.Range;
import uk.ac.qmul.eecs.depic.patterns.Sequence;
import uk.ac.qmul.eecs.depic.patterns.SequenceEvent;
import uk.ac.qmul.eecs.depic.patterns.SequenceListener;

/**
 * 
 * A visual representation of a sequence graph. Here used for automation graphs overlaying the audio track.
 *
 * It listen to sequences (automations) and, when the sequence is updated, it updates itself
 * and triggers the registered change listeners. 
 */
public class SequenceGraph implements  SequenceListener {
	private static final float SEQUENCE_LINE_WIDTH = 4.0f;
	private static final int   SEQUENCE_POINTS_CAPACITY = 30;
	
	private Sequence sequence;
	private Color color;
	private  List<SequencePoint>  sequencePoints;
	private  List<Shape> sequenceLines;
	private Dimension size;
	private Collection<ChangeListener> listeners;
	private float millisecPerPixel;
	
	public SequenceGraph(Color color, Dimension size) {
		sequencePoints = new ArrayList<>(SEQUENCE_POINTS_CAPACITY);
		sequenceLines = new ArrayList<>(SEQUENCE_POINTS_CAPACITY-1);
		listeners = new ArrayList<>();
		this.color = color;
		this.size = size;
	}
	
	public Dimension getSize(){
		return size;
	}
	
	public void setSize(Dimension size){
		this.size = size;
	}
	
	public void setColor(Color c){
		this.color = c;
	}
	
	public float getMillisecondsPerPixel(){
		return millisecPerPixel;
	}
	
	public void setMillisecPerPixel(float millisecPerPixel){
		this.millisecPerPixel = millisecPerPixel;
	}
	
	public Color getColor(){
		return color; 
	}
	
	public List<SequencePoint> getSequencePoints(){
		return sequencePoints;
	}
	
	public List<Shape> getSequenceLines(){
		return sequenceLines;
	}

	public void draw(Graphics g ){
		if(sequenceLines.isEmpty()){
			return;
		}
		
		Graphics2D g2 = (Graphics2D)g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		Color oldColor = g2.getColor();
		
		g2.setColor(color);
		
		/* draw sequence points and lines */
		for(Shape point : sequencePoints){
			g2.fill(point);
		}
				
		for(Shape line : sequenceLines){
			g2.fill(line);
		}
		/* restores previous state */
		g2.setColor(oldColor);
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
	}

	public void addChangeListener(ChangeListener l){
		listeners.add(l);
	}
	
	public void removeChangeListener(ChangeListener l){
		listeners.remove(l);
	}
	
	protected void fireChangeListeners(){
		ChangeEvent evt = new ChangeEvent(this);
		for(ChangeListener l : listeners){
			l.stateChanged(evt);
		}
	}
	
	@Override 
	public <T extends SequenceEvent> void sequenceUpdated(T evt) {
		updateSequence();
	}
	
	/**
	 * 
	 * Sets a new sequence to be listened to. If the argument is {@code null} 
	 * it stops listening to the previously set sequence, if any,m and clears all 
	 * sequence points and lines. it calls {@code updateSequence}.
	 * 
	 * @param s the new sequence. 
	 */
	public void setSequence(Sequence s){
		/* remove this from the old sequence if any and add this to the new sequence */
		if(sequence != null){
			sequence.removeSequenceListener(this);
		}
		
		if(s != null){
			s.addSequenceListener(this);
		}
		
		sequence = s;
		updateSequence();
	}
	
	public Sequence getSequence(){
		return sequence;
	}
	
	/** 
	 * Recompute the sequence points and lines. It should be called whenever a change is done to this 
	 * object. For example after {@code setSize} and {@code setMilliseconsPerPixel}. 
	 * 
	 */
	public void updateSequence(){
		/* clear previous values. will rebuild them if type is not NONE */
		sequencePoints.clear();
		sequenceLines.clear();
		
		if(sequence == null){
			/* no sequence represented any more. Fire listeners with * 
			 * sequencePoints and sequenceLines cleared              */
			fireChangeListeners();
			return;
		}
		
		/* height and width of this panel in order to draw proportionately */
		int height = getSize().height;
		int width = getSize().width;
		
		/* get the sequence range */
		MathUtils.Scale scale = new MathUtils.Scale(sequence.getRange(),new Range<Float>(0.0f,(float)height));
		BasicStroke stroke = new BasicStroke(SEQUENCE_LINE_WIDTH);

		/* if there are no sequence values, draw a straight line from the beginning to the end of the track */
		if(sequence.getValuesNum() == 0){
			sequenceLines.add(stroke.createStrokedShape(
					new Line2D.Float(
							0f,
							height - scale.linear(sequence.getBegin()),/* height is reversed as in swing the top is 0 */
							width, 
							height - scale.linear(sequence.getEnd()))/* height is reversed as in swing the top is 0 */
					));
		}else{
			Sequence.Value previous = null;
			/*previousX and previousY are the centre of sequence values calculated in the previous cycle */
			int previousX = 0;
			int previousY = 0;
			
			for(int i=0; i<sequence.getValuesNum();i++){
				Sequence.Value current = sequence.getValueAt(i);
				int currentX = (int)(current.getTimePosition()/millisecPerPixel) - (SequencePoint.SIZE/2) ;
				/* height is reversed as in swing the top is 0 */
				int currentY = height - (int)(scale.linear(current.getValue())) - (SequencePoint.SIZE/2) ;
				/* add the point */
				sequencePoints.add(new SequencePoint(current,currentX,currentY));
				if(previous == null){
					/* add the line from the beginning to the first point */
					sequenceLines.add(stroke.createStrokedShape(
							new Line2D.Float(
									0f,
									height - scale.linear(sequence.getBegin()), /* height is reversed as in swing the top is 0 */
									currentX + (SequencePoint.SIZE/2), 
									currentY + (SequencePoint.SIZE/2) )));
				}else{
					/* add the line with previous point */
					sequenceLines.add(stroke.createStrokedShape(
							new Line2D.Float(
									previousX + (SequencePoint.SIZE/2),
									previousY + (SequencePoint.SIZE/2),
									currentX + (SequencePoint.SIZE/2), 
									currentY + (SequencePoint.SIZE/2) )));
				}
				/* prepare for next cycle */
				previous = current;
				previousX = currentX;
				previousY = currentY;
			}
			
			/* add the line from the last point to the end */
			sequenceLines.add(stroke.createStrokedShape(
					new Line2D.Float(
							previousX + (SequencePoint.SIZE/2),
							previousY + (SequencePoint.SIZE/2),
							width, 
							height - scale.linear(sequence.getEnd()) ))); /* height is reversed as in swing the top is 0 */
		}
		
		fireChangeListeners();
	}

}