f@0
|
1 /*
|
f@0
|
2 Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation.
|
f@0
|
3
|
f@0
|
4 Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/)
|
f@0
|
5
|
f@0
|
6 This program is free software: you can redistribute it and/or modify
|
f@0
|
7 it under the terms of the GNU General Public License as published by
|
f@0
|
8 the Free Software Foundation, either version 3 of the License, or
|
f@0
|
9 (at your option) any later version.
|
f@0
|
10
|
f@0
|
11 This program is distributed in the hope that it will be useful,
|
f@0
|
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
f@0
|
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
f@0
|
14 GNU General Public License for more details.
|
f@0
|
15
|
f@0
|
16 You should have received a copy of the GNU General Public License
|
f@0
|
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
f@0
|
18 */
|
f@0
|
19 package uk.ac.qmul.eecs.depic.daw.gui;
|
f@0
|
20
|
f@0
|
21 import java.awt.BasicStroke;
|
f@0
|
22 import java.awt.Color;
|
f@0
|
23 import java.awt.Dimension;
|
f@0
|
24 import java.awt.Graphics;
|
f@0
|
25 import java.awt.Graphics2D;
|
f@0
|
26 import java.awt.RenderingHints;
|
f@0
|
27 import java.awt.Shape;
|
f@0
|
28 import java.awt.geom.Line2D;
|
f@0
|
29 import java.util.ArrayList;
|
f@0
|
30 import java.util.Collection;
|
f@0
|
31 import java.util.List;
|
f@0
|
32
|
f@0
|
33 import javax.swing.event.ChangeEvent;
|
f@0
|
34 import javax.swing.event.ChangeListener;
|
f@0
|
35
|
f@0
|
36 import uk.ac.qmul.eecs.depic.patterns.MathUtils;
|
f@0
|
37 import uk.ac.qmul.eecs.depic.patterns.Range;
|
f@0
|
38 import uk.ac.qmul.eecs.depic.patterns.Sequence;
|
f@0
|
39 import uk.ac.qmul.eecs.depic.patterns.SequenceEvent;
|
f@0
|
40 import uk.ac.qmul.eecs.depic.patterns.SequenceListener;
|
f@0
|
41
|
f@2
|
42 /**
|
f@2
|
43 *
|
f@2
|
44 * A visual representation of a sequence graph. Here used for automation graphs overlaying the audio track.
|
f@2
|
45 *
|
f@2
|
46 * It listen to sequences (automations) and, when the sequence is updated, it updates itself
|
f@2
|
47 * and triggers the registered change listeners.
|
f@2
|
48 */
|
f@2
|
49 public class SequenceGraph implements SequenceListener {
|
f@0
|
50 private static final float SEQUENCE_LINE_WIDTH = 4.0f;
|
f@0
|
51 private static final int SEQUENCE_POINTS_CAPACITY = 30;
|
f@0
|
52
|
f@0
|
53 private Sequence sequence;
|
f@0
|
54 private Color color;
|
f@0
|
55 private List<SequencePoint> sequencePoints;
|
f@0
|
56 private List<Shape> sequenceLines;
|
f@0
|
57 private Dimension size;
|
f@0
|
58 private Collection<ChangeListener> listeners;
|
f@0
|
59 private float millisecPerPixel;
|
f@0
|
60
|
f@0
|
61 public SequenceGraph(Color color, Dimension size) {
|
f@0
|
62 sequencePoints = new ArrayList<>(SEQUENCE_POINTS_CAPACITY);
|
f@0
|
63 sequenceLines = new ArrayList<>(SEQUENCE_POINTS_CAPACITY-1);
|
f@0
|
64 listeners = new ArrayList<>();
|
f@0
|
65 this.color = color;
|
f@0
|
66 this.size = size;
|
f@0
|
67 }
|
f@0
|
68
|
f@0
|
69 public Dimension getSize(){
|
f@0
|
70 return size;
|
f@0
|
71 }
|
f@0
|
72
|
f@0
|
73 public void setSize(Dimension size){
|
f@0
|
74 this.size = size;
|
f@0
|
75 }
|
f@0
|
76
|
f@0
|
77 public void setColor(Color c){
|
f@0
|
78 this.color = c;
|
f@0
|
79 }
|
f@0
|
80
|
f@0
|
81 public float getMillisecondsPerPixel(){
|
f@0
|
82 return millisecPerPixel;
|
f@0
|
83 }
|
f@0
|
84
|
f@0
|
85 public void setMillisecPerPixel(float millisecPerPixel){
|
f@0
|
86 this.millisecPerPixel = millisecPerPixel;
|
f@0
|
87 }
|
f@0
|
88
|
f@0
|
89 public Color getColor(){
|
f@0
|
90 return color;
|
f@0
|
91 }
|
f@0
|
92
|
f@0
|
93 public List<SequencePoint> getSequencePoints(){
|
f@0
|
94 return sequencePoints;
|
f@0
|
95 }
|
f@0
|
96
|
f@0
|
97 public List<Shape> getSequenceLines(){
|
f@0
|
98 return sequenceLines;
|
f@0
|
99 }
|
f@0
|
100
|
f@0
|
101 public void draw(Graphics g ){
|
f@0
|
102 if(sequenceLines.isEmpty()){
|
f@0
|
103 return;
|
f@0
|
104 }
|
f@0
|
105
|
f@0
|
106 Graphics2D g2 = (Graphics2D)g;
|
f@0
|
107 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
f@0
|
108 Color oldColor = g2.getColor();
|
f@0
|
109
|
f@0
|
110 g2.setColor(color);
|
f@0
|
111
|
f@0
|
112 /* draw sequence points and lines */
|
f@0
|
113 for(Shape point : sequencePoints){
|
f@0
|
114 g2.fill(point);
|
f@0
|
115 }
|
f@0
|
116
|
f@0
|
117 for(Shape line : sequenceLines){
|
f@0
|
118 g2.fill(line);
|
f@0
|
119 }
|
f@0
|
120 /* restores previous state */
|
f@0
|
121 g2.setColor(oldColor);
|
f@0
|
122 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
f@0
|
123 }
|
f@0
|
124
|
f@0
|
125 public void addChangeListener(ChangeListener l){
|
f@0
|
126 listeners.add(l);
|
f@0
|
127 }
|
f@0
|
128
|
f@0
|
129 public void removeChangeListener(ChangeListener l){
|
f@0
|
130 listeners.remove(l);
|
f@0
|
131 }
|
f@0
|
132
|
f@0
|
133 protected void fireChangeListeners(){
|
f@0
|
134 ChangeEvent evt = new ChangeEvent(this);
|
f@0
|
135 for(ChangeListener l : listeners){
|
f@0
|
136 l.stateChanged(evt);
|
f@0
|
137 }
|
f@0
|
138 }
|
f@0
|
139
|
f@0
|
140 @Override
|
f@0
|
141 public <T extends SequenceEvent> void sequenceUpdated(T evt) {
|
f@0
|
142 updateSequence();
|
f@0
|
143 }
|
f@0
|
144
|
f@0
|
145 /**
|
f@0
|
146 *
|
f@0
|
147 * Sets a new sequence to be listened to. If the argument is {@code null}
|
f@0
|
148 * it stops listening to the previously set sequence, if any,m and clears all
|
f@0
|
149 * sequence points and lines. it calls {@code updateSequence}.
|
f@0
|
150 *
|
f@0
|
151 * @param s the new sequence.
|
f@0
|
152 */
|
f@0
|
153 public void setSequence(Sequence s){
|
f@0
|
154 /* remove this from the old sequence if any and add this to the new sequence */
|
f@0
|
155 if(sequence != null){
|
f@0
|
156 sequence.removeSequenceListener(this);
|
f@0
|
157 }
|
f@0
|
158
|
f@0
|
159 if(s != null){
|
f@0
|
160 s.addSequenceListener(this);
|
f@0
|
161 }
|
f@0
|
162
|
f@0
|
163 sequence = s;
|
f@0
|
164 updateSequence();
|
f@0
|
165 }
|
f@0
|
166
|
f@0
|
167 public Sequence getSequence(){
|
f@0
|
168 return sequence;
|
f@0
|
169 }
|
f@0
|
170
|
f@0
|
171 /**
|
f@0
|
172 * Recompute the sequence points and lines. It should be called whenever a change is done to this
|
f@0
|
173 * object. For example after {@code setSize} and {@code setMilliseconsPerPixel}.
|
f@0
|
174 *
|
f@0
|
175 */
|
f@0
|
176 public void updateSequence(){
|
f@0
|
177 /* clear previous values. will rebuild them if type is not NONE */
|
f@0
|
178 sequencePoints.clear();
|
f@0
|
179 sequenceLines.clear();
|
f@0
|
180
|
f@0
|
181 if(sequence == null){
|
f@0
|
182 /* no sequence represented any more. Fire listeners with *
|
f@0
|
183 * sequencePoints and sequenceLines cleared */
|
f@0
|
184 fireChangeListeners();
|
f@0
|
185 return;
|
f@0
|
186 }
|
f@0
|
187
|
f@0
|
188 /* height and width of this panel in order to draw proportionately */
|
f@0
|
189 int height = getSize().height;
|
f@0
|
190 int width = getSize().width;
|
f@0
|
191
|
f@0
|
192 /* get the sequence range */
|
f@0
|
193 MathUtils.Scale scale = new MathUtils.Scale(sequence.getRange(),new Range<Float>(0.0f,(float)height));
|
f@0
|
194 BasicStroke stroke = new BasicStroke(SEQUENCE_LINE_WIDTH);
|
f@0
|
195
|
f@0
|
196 /* if there are no sequence values, draw a straight line from the beginning to the end of the track */
|
f@0
|
197 if(sequence.getValuesNum() == 0){
|
f@0
|
198 sequenceLines.add(stroke.createStrokedShape(
|
f@0
|
199 new Line2D.Float(
|
f@0
|
200 0f,
|
f@0
|
201 height - scale.linear(sequence.getBegin()),/* height is reversed as in swing the top is 0 */
|
f@0
|
202 width,
|
f@0
|
203 height - scale.linear(sequence.getEnd()))/* height is reversed as in swing the top is 0 */
|
f@0
|
204 ));
|
f@0
|
205 }else{
|
f@0
|
206 Sequence.Value previous = null;
|
f@0
|
207 /*previousX and previousY are the centre of sequence values calculated in the previous cycle */
|
f@0
|
208 int previousX = 0;
|
f@0
|
209 int previousY = 0;
|
f@0
|
210
|
f@0
|
211 for(int i=0; i<sequence.getValuesNum();i++){
|
f@0
|
212 Sequence.Value current = sequence.getValueAt(i);
|
f@0
|
213 int currentX = (int)(current.getTimePosition()/millisecPerPixel) - (SequencePoint.SIZE/2) ;
|
f@0
|
214 /* height is reversed as in swing the top is 0 */
|
f@0
|
215 int currentY = height - (int)(scale.linear(current.getValue())) - (SequencePoint.SIZE/2) ;
|
f@0
|
216 /* add the point */
|
f@0
|
217 sequencePoints.add(new SequencePoint(current,currentX,currentY));
|
f@0
|
218 if(previous == null){
|
f@0
|
219 /* add the line from the beginning to the first point */
|
f@0
|
220 sequenceLines.add(stroke.createStrokedShape(
|
f@0
|
221 new Line2D.Float(
|
f@0
|
222 0f,
|
f@0
|
223 height - scale.linear(sequence.getBegin()), /* height is reversed as in swing the top is 0 */
|
f@0
|
224 currentX + (SequencePoint.SIZE/2),
|
f@0
|
225 currentY + (SequencePoint.SIZE/2) )));
|
f@0
|
226 }else{
|
f@0
|
227 /* add the line with previous point */
|
f@0
|
228 sequenceLines.add(stroke.createStrokedShape(
|
f@0
|
229 new Line2D.Float(
|
f@0
|
230 previousX + (SequencePoint.SIZE/2),
|
f@0
|
231 previousY + (SequencePoint.SIZE/2),
|
f@0
|
232 currentX + (SequencePoint.SIZE/2),
|
f@0
|
233 currentY + (SequencePoint.SIZE/2) )));
|
f@0
|
234 }
|
f@0
|
235 /* prepare for next cycle */
|
f@0
|
236 previous = current;
|
f@0
|
237 previousX = currentX;
|
f@0
|
238 previousY = currentY;
|
f@0
|
239 }
|
f@0
|
240
|
f@0
|
241 /* add the line from the last point to the end */
|
f@0
|
242 sequenceLines.add(stroke.createStrokedShape(
|
f@0
|
243 new Line2D.Float(
|
f@0
|
244 previousX + (SequencePoint.SIZE/2),
|
f@0
|
245 previousY + (SequencePoint.SIZE/2),
|
f@0
|
246 width,
|
f@0
|
247 height - scale.linear(sequence.getEnd()) ))); /* height is reversed as in swing the top is 0 */
|
f@0
|
248 }
|
f@0
|
249
|
f@0
|
250 fireChangeListeners();
|
f@0
|
251 }
|
f@0
|
252
|
f@0
|
253 }
|
f@0
|
254
|