Mercurial > hg > cmdp
comparison src/uk/ac/qmul/eecs/depic/daw/gui/AutomationGraphActions.java @ 0:3074a84ef81e
first import
author | Fiore Martin <f.martin@qmul.ac.uk> |
---|---|
date | Wed, 26 Aug 2015 16:16:53 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:3074a84ef81e |
---|---|
1 /* | |
2 Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. | |
3 | |
4 Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) | |
5 | |
6 This program is free software: you can redistribute it and/or modify | |
7 it under the terms of the GNU General Public License as published by | |
8 the Free Software Foundation, either version 3 of the License, or | |
9 (at your option) any later version. | |
10 | |
11 This program is distributed in the hope that it will be useful, | |
12 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 GNU General Public License for more details. | |
15 | |
16 You should have received a copy of the GNU General Public License | |
17 along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 */ | |
19 package uk.ac.qmul.eecs.depic.daw.gui; | |
20 | |
21 import java.awt.event.ActionEvent; | |
22 import java.awt.event.KeyEvent; | |
23 import java.awt.geom.Point2D; | |
24 import java.util.Iterator; | |
25 import java.util.NoSuchElementException; | |
26 import java.util.prefs.Preferences; | |
27 | |
28 import javax.swing.AbstractAction; | |
29 import javax.swing.Action; | |
30 import javax.swing.JComponent; | |
31 import javax.swing.JOptionPane; | |
32 import javax.swing.KeyStroke; | |
33 | |
34 import uk.ac.qmul.eecs.depic.daw.Automation; | |
35 import uk.ac.qmul.eecs.depic.daw.AutomationValue; | |
36 import uk.ac.qmul.eecs.depic.daw.Daw; | |
37 import uk.ac.qmul.eecs.depic.daw.Sonification; | |
38 import uk.ac.qmul.eecs.depic.daw.SoundType; | |
39 import uk.ac.qmul.eecs.depic.daw.SoundWave; | |
40 import uk.ac.qmul.eecs.depic.daw.beads.sonification.BeadsSonification; | |
41 import uk.ac.qmul.eecs.depic.patterns.Range; | |
42 import uk.ac.qmul.eecs.depic.patterns.Sequence; | |
43 | |
44 /** | |
45 * | |
46 * Actions for the manipulation of the automation graph. | |
47 * | |
48 * | |
49 */ | |
50 class AutomationGraphActions implements Iterable<Action> { | |
51 private static AutomationGraphActions singleton; | |
52 private SequencePoint heldPoint; | |
53 | |
54 public final AddAutomationValueAction addAutomationValueAction; | |
55 public final RemoveAutomationValueAction removeAutomationValueAction; | |
56 public final MoveAutomationValueAction moveAutomationValueUpAction; | |
57 public final MoveAutomationValueAction moveAutomationValueLeftAction; | |
58 public final MoveAutomationValueAction moveAutomationValueDownAction; | |
59 public final MoveAutomationValueAction moveAutomationValueRightAction; | |
60 public final ResetAutomationValuesAction resetAutomationAction; | |
61 public final ListenWholeAutomationAction listenWholeAutomationAction; | |
62 public final StopListenWholeAutomationAction stopListenWholeAutomationAction; | |
63 public final SwitchListenAutomationAction switchListenAutomationAction; | |
64 public final SwitchListenPeakLevelAction switchListenPeakLevelAction; | |
65 public final ListenWholePeakLevelAction listenWholePeakLevelAction; | |
66 | |
67 private Sonification sonification; | |
68 | |
69 static AutomationGraphActions getInstance(){ | |
70 if(singleton == null) | |
71 singleton = new AutomationGraphActions(); | |
72 return singleton; | |
73 } | |
74 | |
75 private AutomationGraphActions (){ | |
76 addAutomationValueAction = new AddAutomationValueAction(); | |
77 removeAutomationValueAction = new RemoveAutomationValueAction(); | |
78 moveAutomationValueUpAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl UP"),0.0f,0.05f); | |
79 moveAutomationValueDownAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl DOWN"),0.0f,-0.05f); | |
80 moveAutomationValueLeftAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl LEFT"),-50.0f,0.0f); | |
81 moveAutomationValueRightAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl RIGHT"),50.0f,0.0f); | |
82 resetAutomationAction = new ResetAutomationValuesAction(); | |
83 listenWholeAutomationAction = new ListenWholeAutomationAction(); | |
84 stopListenWholeAutomationAction = new StopListenWholeAutomationAction(); | |
85 switchListenAutomationAction = new SwitchListenAutomationAction(); | |
86 switchListenPeakLevelAction = new SwitchListenPeakLevelAction(); | |
87 listenWholePeakLevelAction = new ListenWholePeakLevelAction(); | |
88 | |
89 sonification = Daw.getSoundEngineFactory().getSharedSonification(); | |
90 } | |
91 | |
92 /** | |
93 * Create a new Actions than plays the automation value sound when it is at zero point, | |
94 * that is in the middle of the automation range | |
95 * | |
96 * @param val | |
97 * @return | |
98 */ | |
99 private static Action createZeroValAction(final Sequence.Value val){ | |
100 return new AbstractAction() { | |
101 private static final long serialVersionUID = 1L; | |
102 private boolean doneOnce = false; | |
103 @Override | |
104 public void actionPerformed(ActionEvent evt){ | |
105 /* only play feedback once. This action remains until a new action | |
106 * for the same keystroke is installed. A new action is installed | |
107 * when the user presses ctrl + up/down/left/right arrow (see MoveAutomation | |
108 * Feedback as to be given only once by each zeroAction, otherwise | |
109 * the sonification will be played each time the user releases a ctrl + | |
110 * arrow action, regardless of whether they actually moved an automation or nor */ | |
111 if(doneOnce){ | |
112 return; | |
113 }else{ | |
114 doneOnce = true; | |
115 } | |
116 Daw.getSoundEngineFactory().getSharedSonification().getSequenceMapping(SoundType.AUTOMATION).renderValue( | |
117 new Sequence.Value() { | |
118 @Override | |
119 public int index() { | |
120 return val.index(); | |
121 } | |
122 | |
123 @Override | |
124 public float getValue() { | |
125 Range<Float> range = val.getSequence().getRange(); | |
126 return range.getStart() + range.lenght()/2; | |
127 } | |
128 | |
129 @Override | |
130 public float getTimePosition() { | |
131 return val.getTimePosition(); | |
132 } | |
133 | |
134 @Override | |
135 public Sequence getSequence() { | |
136 return val.getSequence(); | |
137 } | |
138 }); | |
139 } | |
140 }; | |
141 } | |
142 | |
143 @Override | |
144 public Iterator<Action> iterator(){ | |
145 return new Iterator<Action>(){ | |
146 private int index = 0; | |
147 private Action [] actions = new Action [] { | |
148 addAutomationValueAction, | |
149 removeAutomationValueAction, | |
150 moveAutomationValueUpAction, | |
151 moveAutomationValueLeftAction, | |
152 moveAutomationValueDownAction, | |
153 moveAutomationValueRightAction, | |
154 resetAutomationAction, | |
155 listenWholeAutomationAction, | |
156 stopListenWholeAutomationAction, | |
157 switchListenAutomationAction, | |
158 switchListenPeakLevelAction, | |
159 listenWholePeakLevelAction | |
160 }; | |
161 | |
162 @Override | |
163 public boolean hasNext() { | |
164 return index < actions.length; | |
165 } | |
166 | |
167 @Override | |
168 public Action next() { | |
169 if(index == actions.length) | |
170 throw new NoSuchElementException(); | |
171 return actions[index++]; | |
172 } | |
173 | |
174 @Override | |
175 public void remove() { | |
176 throw new UnsupportedOperationException("remove not supported"); | |
177 } | |
178 | |
179 }; | |
180 } | |
181 | |
182 static abstract class AutomationGraphAction extends AbstractAction { | |
183 private static final long serialVersionUID = 1L; | |
184 | |
185 AutomationGraphAction(KeyStroke keyStroke){ | |
186 putValue(Action.ACCELERATOR_KEY,keyStroke); | |
187 } | |
188 | |
189 @Override | |
190 public void actionPerformed(ActionEvent evt){ | |
191 handleActionPerformed((AudioTrack)evt.getSource()); | |
192 } | |
193 | |
194 public abstract void handleActionPerformed(AudioTrack track); | |
195 | |
196 } | |
197 | |
198 class AddAutomationValueAction extends AutomationGraphAction { | |
199 private static final long serialVersionUID = 1L; | |
200 | |
201 public AddAutomationValueAction() { | |
202 super(KeyStroke.getKeyStroke("ctrl INSERT")); | |
203 } | |
204 | |
205 @Override | |
206 public void handleActionPerformed(AudioTrack track) { | |
207 if(track == null || !track.getSoundWave().hasSequence()) { | |
208 return; | |
209 } | |
210 | |
211 SoundWave wave = track.getSoundWave(); | |
212 int cursorPos = wave.getCurrentChunkPosition(); | |
213 | |
214 Point2D.Float p = AudioTrack.getAutomationCoord(track, cursorPos, track.getHeight()/2); | |
215 | |
216 /* if user is trying to create an automation outside the bounds of the automation, notify the error */ | |
217 if(p == null){ | |
218 sonification.play(SoundType.ERROR); | |
219 return; | |
220 } | |
221 | |
222 /* if a new point is created when moving another point the held point will still be the old one * | |
223 * by setting it to null the hel point will become the new one as soon as the user will try to move it */ | |
224 heldPoint = null; | |
225 sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue( | |
226 wave.getParametersControl().getCurrentAutomation().add(p.x, p.y)); | |
227 } | |
228 } // class AddAutomationAction | |
229 | |
230 class MoveAutomationValueAction extends AutomationGraphAction { | |
231 private static final long serialVersionUID = 1L; | |
232 private float deltaX; | |
233 private float deltaY; | |
234 | |
235 public MoveAutomationValueAction(KeyStroke keyStroke, float deltaX, float deltaY){ | |
236 super(keyStroke); | |
237 this.deltaX = deltaX; | |
238 this.deltaY = deltaY; | |
239 heldPoint = null; | |
240 } | |
241 | |
242 @Override | |
243 public void handleActionPerformed(final AudioTrack track) { | |
244 if(track == null || !track.getSoundWave().hasSequence()){ | |
245 heldPoint = null; | |
246 return; | |
247 } | |
248 | |
249 int cursorPos = track.getSoundWave().getCurrentChunkPosition(); | |
250 | |
251 /* if ctrl is held (heldPoint != null) it means the user is still moving around a * | |
252 * point that they grabbed previously. So the check that the point be under the * | |
253 * cursor must not be done, and the point can be moved and played anyway */ | |
254 if(heldPoint != null){ | |
255 changeAutomation((AutomationValue)heldPoint.getSequenceValue(),track); | |
256 return; | |
257 } | |
258 | |
259 /* find the automaton value under the cursor position if any */ | |
260 for(SequencePoint itr : track.getAutomationGraph().getSequencePoints()){ | |
261 if( itr.isXCentredAt(cursorPos) ){ | |
262 /* p is now the heldPoint, until the user releaser the ctrl key this point will be referenced * | |
263 * in next move action even if it's not under the the cursor is not under the cursor anymore */ | |
264 heldPoint = itr; | |
265 /* set up an action that will stop scrubbing free the held point when the user releases the ctrl key */ | |
266 track.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, 0 , true), "heldPoint"); | |
267 track.getActionMap().put("heldPoint", new AbstractAction(){ | |
268 private static final long serialVersionUID = 1L; | |
269 @Override | |
270 public void actionPerformed(ActionEvent evt) { | |
271 heldPoint = null; | |
272 /* stop scrubbing */ | |
273 track.getSoundWave().scan(SoundWave.STOP_SCANNING); | |
274 /* de-register itself from the track registered actions */ | |
275 track.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, 0 , true), "none"); | |
276 track.getActionMap().remove("heldPoint"); | |
277 } | |
278 }); | |
279 | |
280 changeAutomation((AutomationValue)itr.getSequenceValue(),track); | |
281 break; | |
282 } | |
283 } | |
284 } | |
285 | |
286 protected void changeAutomation(AutomationValue val, AudioTrack track){ | |
287 val.setLocation(val.getTimePosition()+deltaX, val.getValue()+deltaY); | |
288 | |
289 /* install the action that plays the zero value when the key is released */ | |
290 KeyStroke thisActionKeyStroke = (KeyStroke)getValue(Action.ACCELERATOR_KEY); | |
291 KeyStroke zeroValKeyStroke = KeyStroke.getKeyStroke(thisActionKeyStroke.getKeyCode(),thisActionKeyStroke.getModifiers(),true); | |
292 | |
293 if(Preferences.userNodeForPackage(BeadsSonification.class).getBoolean("render_val.ref", false)){ | |
294 track.getInputMap(JComponent.WHEN_FOCUSED).put(zeroValKeyStroke, "zeroVal"); | |
295 track.getActionMap().put("zeroVal", createZeroValAction(val)); | |
296 } | |
297 | |
298 /* besides moving the automation, scrub the cursor along */ | |
299 int scanTo = (int)(val.getTimePosition() / track.getSoundWave().getMillisecPerChunk()); | |
300 track.getSoundWave().scan(scanTo); | |
301 sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue(heldPoint.getSequenceValue()); | |
302 } | |
303 } // class MoveAutomationValueAction | |
304 | |
305 class RemoveAutomationValueAction extends AutomationGraphAction { | |
306 private static final long serialVersionUID = 1L; | |
307 | |
308 public RemoveAutomationValueAction() { | |
309 super(KeyStroke.getKeyStroke("ctrl DELETE")); | |
310 } | |
311 | |
312 @Override | |
313 public void handleActionPerformed(AudioTrack track) { | |
314 if(track == null || !track.getSoundWave().hasSequence()){ | |
315 return; | |
316 } | |
317 | |
318 int cursorPos = track.getSoundWave().getCurrentChunkPosition(); | |
319 | |
320 /* find the automaton value */ | |
321 SequencePoint p = null; | |
322 for(SequencePoint itr : track.getAutomationGraph().getSequencePoints()){ | |
323 float dist = Math.abs(cursorPos - (int)itr.getCenterX() ); | |
324 | |
325 if( dist < SequencePoint.SIZE ){ | |
326 if(p == null || dist < Math.abs((int)p.getCenterX() - (int)itr.getCenterX())){ | |
327 p = itr; | |
328 /* don't break as it keeps looking for other points, which might be closer to the cursor */ | |
329 } | |
330 } | |
331 } | |
332 | |
333 /* if found, then remove it and play it */ | |
334 if(p != null){ | |
335 ((Automation)p.getSequenceValue().getSequence()).remove((AutomationValue)p.getSequenceValue()); | |
336 sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue(p.getSequenceValue()); | |
337 } | |
338 } | |
339 | |
340 } // class RemoveAutomationAction | |
341 | |
342 class ResetAutomationValuesAction extends AutomationGraphAction { | |
343 private static final long serialVersionUID = 1L; | |
344 | |
345 public ResetAutomationValuesAction() { | |
346 super(KeyStroke.getKeyStroke("ctrl R ")); | |
347 } | |
348 | |
349 @Override | |
350 public void handleActionPerformed(AudioTrack track){ | |
351 if(track == null || !track.getSoundWave().hasSequence()){ | |
352 return; | |
353 } | |
354 | |
355 int confirmation = JOptionPane.showConfirmDialog(track, | |
356 "Are you sure you want to reset the automation ?", // FIXME bundle | |
357 "Confirmation Dialog", | |
358 JOptionPane.YES_NO_OPTION | |
359 ); | |
360 | |
361 if(confirmation == JOptionPane.YES_OPTION){ | |
362 track.getSoundWave().getParametersControl().getCurrentAutomation().reset(); | |
363 } | |
364 } | |
365 } // class ClearAutomationValuesAction | |
366 | |
367 class ListenWholeAutomationAction extends AutomationGraphAction { | |
368 private static final long serialVersionUID = 1L; | |
369 | |
370 public ListenWholeAutomationAction() { | |
371 super(KeyStroke.getKeyStroke("ctrl shift L")); | |
372 } | |
373 | |
374 @Override | |
375 public void handleActionPerformed(AudioTrack track){ | |
376 if(track == null || (!track.getSoundWave().hasSequence()) ){ | |
377 sonification.play(SoundType.ERROR); | |
378 return; | |
379 } | |
380 | |
381 track.getSoundWave().getTransportControl().rew(); | |
382 sonification.getSequenceMapping(SoundType.AUTOMATION).renderCurve( | |
383 track.getSoundWave().getParametersControl().getCurrentAutomation(),0.0f); | |
384 track.getSoundWave().getTransportControl().play(); | |
385 } | |
386 } // class SonifyAutomationAction | |
387 | |
388 class StopListenWholeAutomationAction extends AutomationGraphAction { | |
389 private static final long serialVersionUID = 1L; | |
390 | |
391 public StopListenWholeAutomationAction(){ | |
392 super(KeyStroke.getKeyStroke("ctrl K")); | |
393 } | |
394 | |
395 @Override | |
396 public void handleActionPerformed(AudioTrack track){ | |
397 sonification.getSequenceMapping(SoundType.AUTOMATION).renderCurve( | |
398 null,-100.0f); | |
399 sonification.getSequenceMapping(SoundType.PEAK_LEVEL).renderCurve( | |
400 null,-100.0f); | |
401 track.soundWave.getTransportControl().stop(); | |
402 } | |
403 } | |
404 | |
405 class SwitchListenAutomationAction extends AutomationGraphAction { | |
406 private static final long serialVersionUID = 1L; | |
407 private boolean isOn; | |
408 | |
409 public SwitchListenAutomationAction(){ | |
410 super(KeyStroke.getKeyStroke("ctrl L")); | |
411 isOn = false; | |
412 } | |
413 | |
414 @Override | |
415 public void handleActionPerformed(AudioTrack track){ | |
416 if(track.getSoundWave().hasSequence()){ | |
417 isOn = !isOn; | |
418 | |
419 track.showAutomationSound(isOn); | |
420 sonification.play(SoundType.OK); | |
421 }else{ | |
422 sonification.play(SoundType.ERROR); | |
423 } | |
424 } | |
425 | |
426 public boolean isSwitchOn(){ | |
427 return isOn; | |
428 } | |
429 } // class SwitchListenAutomation | |
430 | |
431 class SwitchListenPeakLevelAction extends AutomationGraphAction { | |
432 private static final long serialVersionUID = 1L; | |
433 private boolean isOn; | |
434 | |
435 SwitchListenPeakLevelAction(){ | |
436 super(KeyStroke.getKeyStroke("ctrl P")); | |
437 isOn = false; | |
438 } | |
439 | |
440 @Override | |
441 public void handleActionPerformed(AudioTrack track){ | |
442 if( track == null || (!track.getSoundWave().getDbWave().hasSequence())){ | |
443 Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); | |
444 return; | |
445 } | |
446 isOn = !isOn; | |
447 | |
448 track.showPeakLevelSound(isOn); | |
449 Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.OK); | |
450 } | |
451 | |
452 public boolean isSwitchOn(){ | |
453 return isOn; | |
454 } | |
455 } // SwitchListenPeakLevelAction | |
456 | |
457 class ListenWholePeakLevelAction extends AutomationGraphAction { | |
458 private static final long serialVersionUID = 1L; | |
459 | |
460 ListenWholePeakLevelAction(){ | |
461 super(KeyStroke.getKeyStroke("ctrl shift P")); | |
462 } | |
463 | |
464 @Override | |
465 public void handleActionPerformed(AudioTrack track){ | |
466 if(track == null || (!track.getSoundWave().getDbWave().hasSequence()) ){ | |
467 sonification.play(SoundType.ERROR); | |
468 return; | |
469 } | |
470 | |
471 track.getSoundWave().getTransportControl().rew(); | |
472 sonification.getSequenceMapping(SoundType.PEAK_LEVEL).renderCurve( | |
473 track.getSoundWave().getDbWave().getSequence(),0.0f); | |
474 track.getSoundWave().getTransportControl().play(); | |
475 } | |
476 } | |
477 | |
478 } |