annotate src/uk/ac/qmul/eecs/depic/daw/gui/AudioTrackInput.java @ 4:473da40f3d39 tip

added html formatting to Daw/package-info.java
author Fiore Martin <f.martin@qmul.ac.uk>
date Thu, 25 Feb 2016 17:50:09 +0000
parents 3074a84ef81e
children
rev   line source
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.Dimension;
f@0 22 import java.awt.Shape;
f@0 23 import java.awt.event.ActionEvent;
f@0 24 import java.awt.event.KeyEvent;
f@0 25 import java.awt.event.KeyListener;
f@0 26 import java.awt.event.MouseEvent;
f@0 27 import java.awt.geom.Point2D;
f@0 28
f@0 29 import javax.swing.AbstractAction;
f@0 30 import javax.swing.Action;
f@0 31 import javax.swing.JComponent;
f@0 32 import javax.swing.JPopupMenu;
f@0 33 import javax.swing.KeyStroke;
f@0 34 import javax.swing.SwingUtilities;
f@0 35 import javax.swing.event.MouseInputAdapter;
f@0 36
f@0 37 import uk.ac.qmul.eecs.depic.daw.Automation;
f@0 38 import uk.ac.qmul.eecs.depic.daw.AutomationValue;
f@0 39 import uk.ac.qmul.eecs.depic.daw.Daw;
f@0 40 import uk.ac.qmul.eecs.depic.daw.Direction;
f@0 41 import uk.ac.qmul.eecs.depic.daw.Selection;
f@0 42 import uk.ac.qmul.eecs.depic.daw.SoundType;
f@0 43 import uk.ac.qmul.eecs.depic.daw.SoundWave;
f@0 44
f@0 45 /*
f@0 46 * This class handles the interaction with the mouse. When the mouse is dragged around
f@0 47 * the corresponding selection appears on this audio track. When the user releases the
f@0 48 * mouse button (and therefore stops dragging) the selection is assigned to
f@0 49 * the underlying sound wave by a call to setSelection().
f@0 50 */
f@0 51 final class AudioTrackInput extends MouseInputAdapter implements KeyListener {
f@0 52 private static final int KEY_SELECTION_INCREMENT = 1;
f@0 53 private static final int KEY_PRESS_NO_SELECTION = -1;
f@0 54 private int mousePress; // where the user presses the mouse
f@0 55 private int keyPress;
f@0 56 /* used to check whether the user is dragging a new selection */
f@0 57 private boolean isDragging;
f@0 58 private Selection mouseSelection;
f@0 59 private AudioTrack track;
f@0 60 private SequencePoint dragAutomationPoint;
f@0 61
f@0 62 AudioTrackInput(AudioTrack track){
f@0 63 this.track = track;
f@0 64 setAudioTrackActions(track);
f@0 65 mouseSelection = Selection.ZERO_SELECTION;
f@0 66 keyPress = KEY_PRESS_NO_SELECTION;
f@0 67 }
f@0 68
f@0 69 private void setAudioTrackActions(AudioTrack track){
f@0 70 for(Action action : AutomationGraphActions.getInstance()){
f@0 71 Object unique = new Object();
f@0 72
f@0 73 track.getInputMap(JComponent.WHEN_FOCUSED).put((KeyStroke)action.getValue(Action.ACCELERATOR_KEY), unique);
f@0 74 track.getActionMap().put(unique,action);
f@0 75 }
f@0 76 }
f@0 77
f@0 78 /**
f@0 79 * Sets the mouse selection to a new {@code Selection} ranging from {@code start} to {@code end}.
f@0 80 *
f@0 81 * @param start
f@0 82 * @param end
f@0 83 */
f@0 84 public void setMouseSelection(int start, int end){
f@0 85 Selection oldSelection = mouseSelection;
f@0 86 if(end == -1) // open Selection
f@0 87 mouseSelection = new Selection(start,track.getScaleFactor());
f@0 88 else
f@0 89 mouseSelection = new Selection(start,end,track.getScaleFactor());
f@0 90
f@0 91 track.firePropertyChange("mouseDragSelection",
f@0 92 oldSelection,
f@0 93 mouseSelection);
f@0 94 track.repaint();
f@0 95 }
f@0 96
f@0 97 /**
f@0 98 * Sets the mouse selection and sets the beginning of the selection action to
f@0 99 * either {@code start} or {@start end} (according to the value of {@code mouseAtStart}).
f@0 100 *
f@0 101 * The beginning of the selection action is where a user presses the mouse click
f@0 102 * in order to start a dragging action which creates the selections. The click
f@0 103 * point affects how the selection is: if the user drags towards
f@0 104 * left, the selection starts from the current dragging point and ends at the click point;
f@0 105 * conversely if the user drags towards right the selection starts at the click point and
f@0 106 * end at the current dragging point. The current dragging point changes as the user drags the mouse
f@0 107 * around
f@0 108 *
f@0 109 * @param start the beginning of the selection
f@0 110 * @param end start the end of the selection
f@0 111 * @param mouseAtStart {@code true} if the beginning of the selection action was the
f@0 112 * selection start, {@code false} if it's the selection end
f@0 113 *
f@0 114 */
f@0 115 private void setMouseSelection(int start, int end, boolean mouseAtStart){ // FIXME remove
f@0 116 if(end == -1){ // open selection
f@0 117 mousePress = start;
f@0 118 }else{
f@0 119 mousePress = mouseAtStart ? start : end;
f@0 120 }
f@0 121 setMouseSelection(start,end);
f@0 122 }
f@0 123
f@0 124 /**
f@0 125 * Returns the current mouse selection or {@code Selection.VOID_SELECTION} if there is no selection.
f@0 126 *
f@0 127 * @return the current mouse selection or {@code Selection.VOID_SELECTION} if there is no selection.
f@0 128 */
f@0 129 public Selection getMouseSelection(){
f@0 130 return mouseSelection;
f@0 131 }
f@0 132
f@0 133 @Override
f@0 134 public void mousePressed(MouseEvent evt){
f@0 135 if(SwingUtilities.isLeftMouseButton(evt)){
f@0 136 /* if automation is on */
f@0 137 if(track.getSoundWave().hasSequence()){
f@0 138 for(SequencePoint p : track.getAutomationGraph().getSequencePoints()){
f@0 139 if(p.contains(evt.getX(),evt.getY())){
f@0 140 dragAutomationPoint = p;
f@0 141 return;
f@0 142 }
f@0 143 }
f@0 144 }
f@0 145
f@0 146 /* keep track of where the mouse is pressed. If the user drags a selection this is needed *
f@0 147 * to make the right selection according to the direction of the dragging. see mouseDragged() */
f@0 148 mousePress = evt.getX();
f@0 149 }
f@0 150 }
f@0 151
f@0 152 @Override
f@0 153 public void mouseDragged(MouseEvent evt){
f@0 154 if(SwingUtilities.isLeftMouseButton(evt)){
f@0 155
f@0 156 /* bound the dragging to the space within the sound wave in the track */
f@0 157 Dimension trackSize = track.getSize();
f@0 158 if(evt.getX()<0 || evt.getX() >= track.getSoundWave().getChunkNum() || evt.getY()<0 || evt.getY() > trackSize.height )
f@0 159 return;
f@0 160
f@0 161 /* if we are dragging an automation point, change its coordinate and refresh the automation */
f@0 162 if(track.getSoundWave().hasSequence() && dragAutomationPoint != null){
f@0 163 Point2D.Float pointCoord = AudioTrack.getAutomationCoord(track,evt.getX(),evt.getY());
f@0 164 AutomationValue automVal = (AutomationValue)dragAutomationPoint.getSequenceValue();
f@0 165 automVal.setLocation(pointCoord.x,pointCoord.y);
f@0 166
f@0 167 return;
f@0 168 }
f@0 169
f@0 170 /* update the mouse selection as the user drags the mouse. The selection differs *
f@0 171 * according to the direction of the dragging. If the user moves right from where *
f@0 172 * they pressed, then the start of the selection is the press point, and the end the *
f@0 173 * current dragging point (evt.getX()). Conversely, if the user moves to the left, *
f@0 174 * the start of the selection is the dragging point and the end is the press point. *
f@0 175 * The press point is the value assigned to mousePressX in mousePressed */
f@0 176
f@0 177 isDragging = true;
f@0 178 if(evt.getX() > mousePress){
f@0 179 setMouseSelection(mousePress,evt.getX());
f@0 180 }else{
f@0 181 setMouseSelection(evt.getX(),mousePress);
f@0 182 }
f@0 183 }
f@0 184 }
f@0 185
f@0 186 @Override
f@0 187 public void mouseReleased(MouseEvent evt){
f@0 188 if(SwingUtilities.isLeftMouseButton(evt)){
f@0 189 dragAutomationPoint = null;
f@0 190 if(isDragging){
f@0 191 /* if the user was dragging update the selection in the sound wave */
f@0 192 track.getSoundWave().setSelection(getMouseSelection());
f@0 193 isDragging = false;
f@0 194 }
f@0 195 }
f@0 196 }
f@0 197
f@0 198 @Override
f@0 199 public void mouseClicked(MouseEvent evt){
f@0 200 /* left click : set the cursor position *
f@0 201 * right click : open pop up menu */
f@0 202 if(SwingUtilities.isLeftMouseButton(evt)){
f@0 203 track.getSoundWave().setPosition(evt.getX());
f@0 204 }else if(SwingUtilities.isRightMouseButton(evt)){
f@0 205 /* show automation popup only if an automation is is displayed */
f@0 206 if(track.getSoundWave().hasSequence()){
f@0 207 for(Shape point : track.getAutomationGraph().getSequencePoints()){
f@0 208 if(point.contains(evt.getX(), evt.getY())){
f@0 209 showAutomationPopup(point,evt.getX(),evt.getY());
f@0 210 return;
f@0 211 }
f@0 212 }
f@0 213
f@0 214 for(Shape line : track.getAutomationGraph().getSequenceLines()){
f@0 215 if(line.contains(evt.getX(), evt.getY())){
f@0 216 showAutomationPopup(line,evt.getX(),evt.getY());
f@0 217 return;
f@0 218 }
f@0 219 }
f@0 220 }
f@0 221
f@0 222 /* gets here only if no automation has been found under the mouse pointer */
f@0 223 if(track.getSoundWave().getDbWave().hasSequence()){
f@0 224 showPeakLevelPopup(evt.getX(),evt.getY());
f@0 225 }
f@0 226 }
f@0 227 }
f@0 228
f@0 229
f@0 230 void showAutomationPopup(final Shape selectedShape, final int x, final int y){
f@0 231 final SoundWave wave = track.getSoundWave();
f@0 232 if(x >= wave.getChunkNum()){
f@0 233 /* don't show if the user clicks past the sound wave */
f@0 234 return;
f@0 235 }
f@0 236
f@0 237 JPopupMenu pop = new JPopupMenu();
f@0 238 if(selectedShape instanceof SequencePoint){
f@0 239 pop.add(new AbstractAction("Remove automation point"){
f@0 240 private static final long serialVersionUID = 1L;
f@0 241 @Override
f@0 242 public void actionPerformed(ActionEvent e) {
f@0 243 SequencePoint point = (SequencePoint)selectedShape;
f@0 244 Automation automation = wave.getParametersControl().getCurrentAutomation();
f@0 245 automation.remove((AutomationValue)point.getSequenceValue());
f@0 246 }
f@0 247 });
f@0 248 }else{ // Line2D
f@0 249 pop.add(new AbstractAction("Add automation point"){
f@0 250 private static final long serialVersionUID = 1L;
f@0 251 @Override
f@0 252 public void actionPerformed(ActionEvent e) {
f@0 253 Automation automation = wave.getParametersControl().getCurrentAutomation();
f@0 254 Point2D.Float p = AudioTrack.getAutomationCoord(track,x,y);
f@0 255 automation.add(p.x, p.y);
f@0 256 }
f@0 257 });
f@0 258 }
f@0 259
f@0 260 pop.add(new AbstractAction("Reset Automation"){
f@0 261 private static final long serialVersionUID = 1L;
f@0 262
f@0 263 @Override
f@0 264 public void actionPerformed(ActionEvent evt) {
f@0 265 AutomationGraphActions.getInstance().resetAutomationAction.handleActionPerformed(track);
f@0 266 }
f@0 267 });
f@0 268
f@0 269 Action listenToAutomation = new AbstractAction("Listen to Automation"){
f@0 270 private static final long serialVersionUID = 1L;
f@0 271 @Override
f@0 272 public void actionPerformed(ActionEvent e) {
f@0 273 AutomationGraphActions.getInstance().listenWholeAutomationAction.handleActionPerformed(track);
f@0 274 }
f@0 275 };
f@0 276 listenToAutomation.putValue(Action.ACCELERATOR_KEY,
f@0 277 AutomationGraphActions.getInstance().listenWholeAutomationAction.getValue(Action.ACCELERATOR_KEY));
f@0 278 pop.add(listenToAutomation);
f@0 279
f@0 280 Action stopListenToAutomation = new AbstractAction("Stop Listen To Automation"){
f@0 281 private static final long serialVersionUID = 1L;
f@0 282 @Override
f@0 283 public void actionPerformed(ActionEvent e) {
f@0 284 AutomationGraphActions.getInstance().stopListenWholeAutomationAction.handleActionPerformed(track);
f@0 285 }
f@0 286 };
f@0 287 stopListenToAutomation.putValue(Action.ACCELERATOR_KEY,
f@0 288 AutomationGraphActions.getInstance().stopListenWholeAutomationAction.getValue(Action.ACCELERATOR_KEY));
f@0 289 pop.add(stopListenToAutomation);
f@0 290
f@0 291 Action switchListenAutomation = new AbstractAction("Switch Listen Automation "+
f@0 292 (AutomationGraphActions.getInstance().switchListenAutomationAction.isSwitchOn() ? "off" : "on")){
f@0 293 private static final long serialVersionUID = 1L;
f@0 294 @Override
f@0 295 public void actionPerformed(ActionEvent e) {
f@0 296 AutomationGraphActions.getInstance().switchListenAutomationAction.handleActionPerformed(track);
f@0 297 }
f@0 298 };
f@0 299 switchListenAutomation.putValue(Action.ACCELERATOR_KEY,
f@0 300 AutomationGraphActions.getInstance().switchListenAutomationAction.getValue(Action.ACCELERATOR_KEY));
f@0 301 pop.add(switchListenAutomation);
f@0 302 pop.show(track, x, y);
f@0 303 }
f@0 304
f@0 305 void showPeakLevelPopup(int x , int y){
f@0 306 final SoundWave wave = track.getSoundWave();
f@0 307 if(x >= wave.getChunkNum()){
f@0 308 /* don't show if the user clicks past the sound wave */
f@0 309 return;
f@0 310 }
f@0 311
f@0 312 JPopupMenu pop = new JPopupMenu();
f@0 313
f@0 314 pop.add(new AbstractAction("Switch listen peak level " +
f@0 315 (AutomationGraphActions.getInstance().switchListenPeakLevelAction.isSwitchOn() ? "off" : "on")){
f@0 316 private static final long serialVersionUID = 1L;
f@0 317
f@0 318 @Override
f@0 319 public void actionPerformed(ActionEvent evt) {
f@0 320 AutomationGraphActions.getInstance().switchListenPeakLevelAction.handleActionPerformed(track);
f@0 321 }
f@0 322 });
f@0 323
f@0 324 pop.add(new AbstractAction("Listen to Peak Level"){
f@0 325 private static final long serialVersionUID = 1L;
f@0 326
f@0 327 @Override
f@0 328 public void actionPerformed(ActionEvent evt) {
f@0 329 AutomationGraphActions.getInstance().listenWholePeakLevelAction.handleActionPerformed(track);
f@0 330 }
f@0 331 });
f@0 332
f@0 333 pop.show(track,x,y);
f@0 334 }
f@0 335
f@0 336 @Override
f@0 337 public void keyPressed(KeyEvent evt) {
f@0 338 /* adjust the selection */
f@0 339 if(evt.isShiftDown() && evt.isControlDown() &&
f@0 340 !mouseSelection.equals(Selection.ZERO_SELECTION )){
f@0 341 Direction d = Direction.NONE;
f@0 342 if(evt.getKeyCode() == KeyEvent.VK_LEFT){
f@0 343 d = Direction.LEFT;
f@0 344 }else if(evt.getKeyCode() == KeyEvent.VK_RIGHT){
f@0 345 d = Direction.RIGHT;
f@0 346 } else {
f@0 347 return;
f@0 348 }
f@0 349
f@0 350 SoundWave wave = track.getSoundWave();
f@0 351 int cursorPos = wave.getCurrentChunkPosition();
f@0 352 if(cursorPos == mouseSelection.getStart()){
f@0 353 if(d == Direction.LEFT){ // left
f@0 354 wave.setSelection(new Selection(
f@0 355 mouseSelection.getStart()-KEY_SELECTION_INCREMENT,
f@0 356 mouseSelection.getEnd(),
f@0 357 wave.getScaleFactor()));
f@0 358 wave.scan(cursorPos-KEY_SELECTION_INCREMENT);
f@0 359 }else{ // right
f@0 360 if(mouseSelection.getEnd() - mouseSelection.getStart() <= KEY_SELECTION_INCREMENT){
f@0 361 /* cannot push it too right so as to trespass the end of the selection */
f@0 362 Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR);
f@0 363 }else{
f@0 364 wave.setSelection(new Selection(
f@0 365 mouseSelection.getStart()+KEY_SELECTION_INCREMENT,
f@0 366 mouseSelection.getEnd(),
f@0 367 wave.getScaleFactor()));
f@0 368 wave.scan(cursorPos+KEY_SELECTION_INCREMENT);
f@0 369 }
f@0 370 }
f@0 371 }else if (cursorPos == mouseSelection.getEnd()){
f@0 372 if(d == Direction.LEFT){ // left
f@0 373 if(mouseSelection.getEnd() - mouseSelection.getStart() <= KEY_SELECTION_INCREMENT){
f@0 374 /* cannot push it too keft so as to trespass the start of the selection */
f@0 375 Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR);
f@0 376 }else{
f@0 377 wave.setSelection(new Selection(
f@0 378 mouseSelection.getStart(),
f@0 379 mouseSelection.getEnd()-KEY_SELECTION_INCREMENT,
f@0 380 wave.getScaleFactor()));
f@0 381 wave.scan(cursorPos-KEY_SELECTION_INCREMENT);
f@0 382 }
f@0 383 }else{ // right
f@0 384 wave.setSelection(new Selection(
f@0 385 mouseSelection.getStart(),
f@0 386 mouseSelection.getEnd()+KEY_SELECTION_INCREMENT,
f@0 387 wave.getScaleFactor()));
f@0 388 wave.scan(cursorPos+KEY_SELECTION_INCREMENT);
f@0 389 }
f@0 390 }
f@0 391
f@0 392 /* make selection with right arrow key */
f@0 393 } else if(evt.getKeyCode() == KeyEvent.VK_RIGHT && evt.isShiftDown() ){
f@0 394 if(keyPress == KEY_PRESS_NO_SELECTION){ // new selection
f@0 395 keyPress = track.getSoundWave().getCurrentChunkPosition();
f@0 396 setMouseSelection(keyPress, keyPress+KEY_SELECTION_INCREMENT);
f@0 397 /* scrub along with the selection right end */
f@0 398 track.getSoundWave().scan(getMouseSelection().getEnd());
f@0 399 /* start the selection sound */
f@0 400 }else{ // user is adjusting the selection
f@0 401 /* if mouseSelection.getStart == keypress, it means the selection is at the *
f@0 402 * right side of where the user started to select. Conversely id mouse selection *
f@0 403 * .getEnd == keypress, the selection is at the left of the point where the user *
f@0 404 * started to select. In the former case pressing the right key will expand *
f@0 405 * the selection to the right, whereas in latter case pressing the right key will *
f@0 406 * shrink the selection to the right. */
f@0 407
f@0 408 if(mouseSelection.getStart() == keyPress){
f@0 409 setMouseSelection(keyPress,mouseSelection.getEnd()+KEY_SELECTION_INCREMENT);
f@0 410 track.getSoundWave().scan(getMouseSelection().getEnd());
f@0 411 }else{ // getEnd == keyPress
f@0 412 setMouseSelection(mouseSelection.getStart()+KEY_SELECTION_INCREMENT,keyPress);
f@0 413 track.getSoundWave().scan(getMouseSelection().getStart());
f@0 414 }
f@0 415 }
f@0 416
f@0 417 /* make selection with left arrow key */
f@0 418 }else if(evt.getKeyCode() == KeyEvent.VK_LEFT && evt.isShiftDown()){
f@0 419 if(keyPress == KEY_PRESS_NO_SELECTION){
f@0 420 keyPress = track.getSoundWave().getCurrentChunkPosition();
f@0 421
f@0 422 /* keep the selection after the beginning - 0 - of the audio track */
f@0 423 int start = keyPress-KEY_SELECTION_INCREMENT > 0 ? keyPress-KEY_SELECTION_INCREMENT : 0;
f@0 424 setMouseSelection(start,keyPress);
f@0 425 /* scrub along with the selection left end */
f@0 426 track.getSoundWave().scan(getMouseSelection().getStart());
f@0 427 }else{ // user is adjusting the selection
f@0 428 /* See selectio adjustment for the right key. *
f@0 429 * The idea is the same, just with inverted left and right */
f@0 430 if(mouseSelection.getEnd() == keyPress){
f@0 431 setMouseSelection(mouseSelection.getStart()-KEY_SELECTION_INCREMENT,keyPress);
f@0 432 track.getSoundWave().scan(getMouseSelection().getStart());
f@0 433 }else{ // getStart == keyPress
f@0 434 setMouseSelection(keyPress,mouseSelection.getEnd()-KEY_SELECTION_INCREMENT);
f@0 435 track.getSoundWave().scan(getMouseSelection().getEnd());
f@0 436 }
f@0 437 }
f@0 438
f@0 439 /* jump to beginning of selection, if any */
f@0 440 }else if(evt.getKeyCode() == KeyEvent.VK_F2 || evt.getKeyCode() == KeyEvent.VK_F3){
f@0 441 if(mouseSelection.equals(Selection.ZERO_SELECTION)){
f@0 442 Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR);
f@0 443 }else if (evt.getKeyCode() == KeyEvent.VK_F2){
f@0 444 track.getSoundWave().setPosition(mouseSelection.getStart());
f@0 445 }else{
f@0 446 track.getSoundWave().setPosition(mouseSelection.getEnd());
f@0 447 }
f@0 448 }
f@0 449 }
f@0 450
f@0 451 @Override
f@0 452 public void keyReleased(KeyEvent evt) {
f@0 453 /* create selection in the model when user releases shift key */
f@0 454 if(evt.getKeyCode() == KeyEvent.VK_SHIFT && keyPress != KEY_PRESS_NO_SELECTION){
f@0 455 keyPress = KEY_PRESS_NO_SELECTION;
f@0 456 track.getSoundWave().setSelection(getMouseSelection());
f@0 457 }
f@0 458 }
f@0 459
f@0 460 @Override
f@0 461 public void keyTyped(KeyEvent evt) { }
f@0 462 } // class AudioTrackMouseInteraction
f@0 463
f@0 464
f@0 465