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 }