f@0: /* f@0: Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. f@0: f@0: Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) f@0: f@0: This program is free software: you can redistribute it and/or modify f@0: it under the terms of the GNU General Public License as published by f@0: the Free Software Foundation, either version 3 of the License, or f@0: (at your option) any later version. f@0: f@0: This program is distributed in the hope that it will be useful, f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the f@0: GNU General Public License for more details. f@0: f@0: You should have received a copy of the GNU General Public License f@0: along with this program. If not, see . f@0: */ f@0: package uk.ac.qmul.eecs.depic.daw.gui; f@0: f@0: import java.awt.Dimension; f@0: import java.awt.Shape; f@0: import java.awt.event.ActionEvent; f@0: import java.awt.event.KeyEvent; f@0: import java.awt.event.KeyListener; f@0: import java.awt.event.MouseEvent; f@0: import java.awt.geom.Point2D; f@0: f@0: import javax.swing.AbstractAction; f@0: import javax.swing.Action; f@0: import javax.swing.JComponent; f@0: import javax.swing.JPopupMenu; f@0: import javax.swing.KeyStroke; f@0: import javax.swing.SwingUtilities; f@0: import javax.swing.event.MouseInputAdapter; f@0: f@0: import uk.ac.qmul.eecs.depic.daw.Automation; f@0: import uk.ac.qmul.eecs.depic.daw.AutomationValue; f@0: import uk.ac.qmul.eecs.depic.daw.Daw; f@0: import uk.ac.qmul.eecs.depic.daw.Direction; f@0: import uk.ac.qmul.eecs.depic.daw.Selection; f@0: import uk.ac.qmul.eecs.depic.daw.SoundType; f@0: import uk.ac.qmul.eecs.depic.daw.SoundWave; f@0: f@0: /* f@0: * This class handles the interaction with the mouse. When the mouse is dragged around f@0: * the corresponding selection appears on this audio track. When the user releases the f@0: * mouse button (and therefore stops dragging) the selection is assigned to f@0: * the underlying sound wave by a call to setSelection(). f@0: */ f@0: final class AudioTrackInput extends MouseInputAdapter implements KeyListener { f@0: private static final int KEY_SELECTION_INCREMENT = 1; f@0: private static final int KEY_PRESS_NO_SELECTION = -1; f@0: private int mousePress; // where the user presses the mouse f@0: private int keyPress; f@0: /* used to check whether the user is dragging a new selection */ f@0: private boolean isDragging; f@0: private Selection mouseSelection; f@0: private AudioTrack track; f@0: private SequencePoint dragAutomationPoint; f@0: f@0: AudioTrackInput(AudioTrack track){ f@0: this.track = track; f@0: setAudioTrackActions(track); f@0: mouseSelection = Selection.ZERO_SELECTION; f@0: keyPress = KEY_PRESS_NO_SELECTION; f@0: } f@0: f@0: private void setAudioTrackActions(AudioTrack track){ f@0: for(Action action : AutomationGraphActions.getInstance()){ f@0: Object unique = new Object(); f@0: f@0: track.getInputMap(JComponent.WHEN_FOCUSED).put((KeyStroke)action.getValue(Action.ACCELERATOR_KEY), unique); f@0: track.getActionMap().put(unique,action); f@0: } f@0: } f@0: f@0: /** f@0: * Sets the mouse selection to a new {@code Selection} ranging from {@code start} to {@code end}. f@0: * f@0: * @param start f@0: * @param end f@0: */ f@0: public void setMouseSelection(int start, int end){ f@0: Selection oldSelection = mouseSelection; f@0: if(end == -1) // open Selection f@0: mouseSelection = new Selection(start,track.getScaleFactor()); f@0: else f@0: mouseSelection = new Selection(start,end,track.getScaleFactor()); f@0: f@0: track.firePropertyChange("mouseDragSelection", f@0: oldSelection, f@0: mouseSelection); f@0: track.repaint(); f@0: } f@0: f@0: /** f@0: * Sets the mouse selection and sets the beginning of the selection action to f@0: * either {@code start} or {@start end} (according to the value of {@code mouseAtStart}). f@0: * f@0: * The beginning of the selection action is where a user presses the mouse click f@0: * in order to start a dragging action which creates the selections. The click f@0: * point affects how the selection is: if the user drags towards f@0: * left, the selection starts from the current dragging point and ends at the click point; f@0: * conversely if the user drags towards right the selection starts at the click point and f@0: * end at the current dragging point. The current dragging point changes as the user drags the mouse f@0: * around f@0: * f@0: * @param start the beginning of the selection f@0: * @param end start the end of the selection f@0: * @param mouseAtStart {@code true} if the beginning of the selection action was the f@0: * selection start, {@code false} if it's the selection end f@0: * f@0: */ f@0: private void setMouseSelection(int start, int end, boolean mouseAtStart){ // FIXME remove f@0: if(end == -1){ // open selection f@0: mousePress = start; f@0: }else{ f@0: mousePress = mouseAtStart ? start : end; f@0: } f@0: setMouseSelection(start,end); f@0: } f@0: f@0: /** f@0: * Returns the current mouse selection or {@code Selection.VOID_SELECTION} if there is no selection. f@0: * f@0: * @return the current mouse selection or {@code Selection.VOID_SELECTION} if there is no selection. f@0: */ f@0: public Selection getMouseSelection(){ f@0: return mouseSelection; f@0: } f@0: f@0: @Override f@0: public void mousePressed(MouseEvent evt){ f@0: if(SwingUtilities.isLeftMouseButton(evt)){ f@0: /* if automation is on */ f@0: if(track.getSoundWave().hasSequence()){ f@0: for(SequencePoint p : track.getAutomationGraph().getSequencePoints()){ f@0: if(p.contains(evt.getX(),evt.getY())){ f@0: dragAutomationPoint = p; f@0: return; f@0: } f@0: } f@0: } f@0: f@0: /* keep track of where the mouse is pressed. If the user drags a selection this is needed * f@0: * to make the right selection according to the direction of the dragging. see mouseDragged() */ f@0: mousePress = evt.getX(); f@0: } f@0: } f@0: f@0: @Override f@0: public void mouseDragged(MouseEvent evt){ f@0: if(SwingUtilities.isLeftMouseButton(evt)){ f@0: f@0: /* bound the dragging to the space within the sound wave in the track */ f@0: Dimension trackSize = track.getSize(); f@0: if(evt.getX()<0 || evt.getX() >= track.getSoundWave().getChunkNum() || evt.getY()<0 || evt.getY() > trackSize.height ) f@0: return; f@0: f@0: /* if we are dragging an automation point, change its coordinate and refresh the automation */ f@0: if(track.getSoundWave().hasSequence() && dragAutomationPoint != null){ f@0: Point2D.Float pointCoord = AudioTrack.getAutomationCoord(track,evt.getX(),evt.getY()); f@0: AutomationValue automVal = (AutomationValue)dragAutomationPoint.getSequenceValue(); f@0: automVal.setLocation(pointCoord.x,pointCoord.y); f@0: f@0: return; f@0: } f@0: f@0: /* update the mouse selection as the user drags the mouse. The selection differs * f@0: * according to the direction of the dragging. If the user moves right from where * f@0: * they pressed, then the start of the selection is the press point, and the end the * f@0: * current dragging point (evt.getX()). Conversely, if the user moves to the left, * f@0: * the start of the selection is the dragging point and the end is the press point. * f@0: * The press point is the value assigned to mousePressX in mousePressed */ f@0: f@0: isDragging = true; f@0: if(evt.getX() > mousePress){ f@0: setMouseSelection(mousePress,evt.getX()); f@0: }else{ f@0: setMouseSelection(evt.getX(),mousePress); f@0: } f@0: } f@0: } f@0: f@0: @Override f@0: public void mouseReleased(MouseEvent evt){ f@0: if(SwingUtilities.isLeftMouseButton(evt)){ f@0: dragAutomationPoint = null; f@0: if(isDragging){ f@0: /* if the user was dragging update the selection in the sound wave */ f@0: track.getSoundWave().setSelection(getMouseSelection()); f@0: isDragging = false; f@0: } f@0: } f@0: } f@0: f@0: @Override f@0: public void mouseClicked(MouseEvent evt){ f@0: /* left click : set the cursor position * f@0: * right click : open pop up menu */ f@0: if(SwingUtilities.isLeftMouseButton(evt)){ f@0: track.getSoundWave().setPosition(evt.getX()); f@0: }else if(SwingUtilities.isRightMouseButton(evt)){ f@0: /* show automation popup only if an automation is is displayed */ f@0: if(track.getSoundWave().hasSequence()){ f@0: for(Shape point : track.getAutomationGraph().getSequencePoints()){ f@0: if(point.contains(evt.getX(), evt.getY())){ f@0: showAutomationPopup(point,evt.getX(),evt.getY()); f@0: return; f@0: } f@0: } f@0: f@0: for(Shape line : track.getAutomationGraph().getSequenceLines()){ f@0: if(line.contains(evt.getX(), evt.getY())){ f@0: showAutomationPopup(line,evt.getX(),evt.getY()); f@0: return; f@0: } f@0: } f@0: } f@0: f@0: /* gets here only if no automation has been found under the mouse pointer */ f@0: if(track.getSoundWave().getDbWave().hasSequence()){ f@0: showPeakLevelPopup(evt.getX(),evt.getY()); f@0: } f@0: } f@0: } f@0: f@0: f@0: void showAutomationPopup(final Shape selectedShape, final int x, final int y){ f@0: final SoundWave wave = track.getSoundWave(); f@0: if(x >= wave.getChunkNum()){ f@0: /* don't show if the user clicks past the sound wave */ f@0: return; f@0: } f@0: f@0: JPopupMenu pop = new JPopupMenu(); f@0: if(selectedShape instanceof SequencePoint){ f@0: pop.add(new AbstractAction("Remove automation point"){ f@0: private static final long serialVersionUID = 1L; f@0: @Override f@0: public void actionPerformed(ActionEvent e) { f@0: SequencePoint point = (SequencePoint)selectedShape; f@0: Automation automation = wave.getParametersControl().getCurrentAutomation(); f@0: automation.remove((AutomationValue)point.getSequenceValue()); f@0: } f@0: }); f@0: }else{ // Line2D f@0: pop.add(new AbstractAction("Add automation point"){ f@0: private static final long serialVersionUID = 1L; f@0: @Override f@0: public void actionPerformed(ActionEvent e) { f@0: Automation automation = wave.getParametersControl().getCurrentAutomation(); f@0: Point2D.Float p = AudioTrack.getAutomationCoord(track,x,y); f@0: automation.add(p.x, p.y); f@0: } f@0: }); f@0: } f@0: f@0: pop.add(new AbstractAction("Reset Automation"){ f@0: private static final long serialVersionUID = 1L; f@0: f@0: @Override f@0: public void actionPerformed(ActionEvent evt) { f@0: AutomationGraphActions.getInstance().resetAutomationAction.handleActionPerformed(track); f@0: } f@0: }); f@0: f@0: Action listenToAutomation = new AbstractAction("Listen to Automation"){ f@0: private static final long serialVersionUID = 1L; f@0: @Override f@0: public void actionPerformed(ActionEvent e) { f@0: AutomationGraphActions.getInstance().listenWholeAutomationAction.handleActionPerformed(track); f@0: } f@0: }; f@0: listenToAutomation.putValue(Action.ACCELERATOR_KEY, f@0: AutomationGraphActions.getInstance().listenWholeAutomationAction.getValue(Action.ACCELERATOR_KEY)); f@0: pop.add(listenToAutomation); f@0: f@0: Action stopListenToAutomation = new AbstractAction("Stop Listen To Automation"){ f@0: private static final long serialVersionUID = 1L; f@0: @Override f@0: public void actionPerformed(ActionEvent e) { f@0: AutomationGraphActions.getInstance().stopListenWholeAutomationAction.handleActionPerformed(track); f@0: } f@0: }; f@0: stopListenToAutomation.putValue(Action.ACCELERATOR_KEY, f@0: AutomationGraphActions.getInstance().stopListenWholeAutomationAction.getValue(Action.ACCELERATOR_KEY)); f@0: pop.add(stopListenToAutomation); f@0: f@0: Action switchListenAutomation = new AbstractAction("Switch Listen Automation "+ f@0: (AutomationGraphActions.getInstance().switchListenAutomationAction.isSwitchOn() ? "off" : "on")){ f@0: private static final long serialVersionUID = 1L; f@0: @Override f@0: public void actionPerformed(ActionEvent e) { f@0: AutomationGraphActions.getInstance().switchListenAutomationAction.handleActionPerformed(track); f@0: } f@0: }; f@0: switchListenAutomation.putValue(Action.ACCELERATOR_KEY, f@0: AutomationGraphActions.getInstance().switchListenAutomationAction.getValue(Action.ACCELERATOR_KEY)); f@0: pop.add(switchListenAutomation); f@0: pop.show(track, x, y); f@0: } f@0: f@0: void showPeakLevelPopup(int x , int y){ f@0: final SoundWave wave = track.getSoundWave(); f@0: if(x >= wave.getChunkNum()){ f@0: /* don't show if the user clicks past the sound wave */ f@0: return; f@0: } f@0: f@0: JPopupMenu pop = new JPopupMenu(); f@0: f@0: pop.add(new AbstractAction("Switch listen peak level " + f@0: (AutomationGraphActions.getInstance().switchListenPeakLevelAction.isSwitchOn() ? "off" : "on")){ f@0: private static final long serialVersionUID = 1L; f@0: f@0: @Override f@0: public void actionPerformed(ActionEvent evt) { f@0: AutomationGraphActions.getInstance().switchListenPeakLevelAction.handleActionPerformed(track); f@0: } f@0: }); f@0: f@0: pop.add(new AbstractAction("Listen to Peak Level"){ f@0: private static final long serialVersionUID = 1L; f@0: f@0: @Override f@0: public void actionPerformed(ActionEvent evt) { f@0: AutomationGraphActions.getInstance().listenWholePeakLevelAction.handleActionPerformed(track); f@0: } f@0: }); f@0: f@0: pop.show(track,x,y); f@0: } f@0: f@0: @Override f@0: public void keyPressed(KeyEvent evt) { f@0: /* adjust the selection */ f@0: if(evt.isShiftDown() && evt.isControlDown() && f@0: !mouseSelection.equals(Selection.ZERO_SELECTION )){ f@0: Direction d = Direction.NONE; f@0: if(evt.getKeyCode() == KeyEvent.VK_LEFT){ f@0: d = Direction.LEFT; f@0: }else if(evt.getKeyCode() == KeyEvent.VK_RIGHT){ f@0: d = Direction.RIGHT; f@0: } else { f@0: return; f@0: } f@0: f@0: SoundWave wave = track.getSoundWave(); f@0: int cursorPos = wave.getCurrentChunkPosition(); f@0: if(cursorPos == mouseSelection.getStart()){ f@0: if(d == Direction.LEFT){ // left f@0: wave.setSelection(new Selection( f@0: mouseSelection.getStart()-KEY_SELECTION_INCREMENT, f@0: mouseSelection.getEnd(), f@0: wave.getScaleFactor())); f@0: wave.scan(cursorPos-KEY_SELECTION_INCREMENT); f@0: }else{ // right f@0: if(mouseSelection.getEnd() - mouseSelection.getStart() <= KEY_SELECTION_INCREMENT){ f@0: /* cannot push it too right so as to trespass the end of the selection */ f@0: Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); f@0: }else{ f@0: wave.setSelection(new Selection( f@0: mouseSelection.getStart()+KEY_SELECTION_INCREMENT, f@0: mouseSelection.getEnd(), f@0: wave.getScaleFactor())); f@0: wave.scan(cursorPos+KEY_SELECTION_INCREMENT); f@0: } f@0: } f@0: }else if (cursorPos == mouseSelection.getEnd()){ f@0: if(d == Direction.LEFT){ // left f@0: if(mouseSelection.getEnd() - mouseSelection.getStart() <= KEY_SELECTION_INCREMENT){ f@0: /* cannot push it too keft so as to trespass the start of the selection */ f@0: Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); f@0: }else{ f@0: wave.setSelection(new Selection( f@0: mouseSelection.getStart(), f@0: mouseSelection.getEnd()-KEY_SELECTION_INCREMENT, f@0: wave.getScaleFactor())); f@0: wave.scan(cursorPos-KEY_SELECTION_INCREMENT); f@0: } f@0: }else{ // right f@0: wave.setSelection(new Selection( f@0: mouseSelection.getStart(), f@0: mouseSelection.getEnd()+KEY_SELECTION_INCREMENT, f@0: wave.getScaleFactor())); f@0: wave.scan(cursorPos+KEY_SELECTION_INCREMENT); f@0: } f@0: } f@0: f@0: /* make selection with right arrow key */ f@0: } else if(evt.getKeyCode() == KeyEvent.VK_RIGHT && evt.isShiftDown() ){ f@0: if(keyPress == KEY_PRESS_NO_SELECTION){ // new selection f@0: keyPress = track.getSoundWave().getCurrentChunkPosition(); f@0: setMouseSelection(keyPress, keyPress+KEY_SELECTION_INCREMENT); f@0: /* scrub along with the selection right end */ f@0: track.getSoundWave().scan(getMouseSelection().getEnd()); f@0: /* start the selection sound */ f@0: }else{ // user is adjusting the selection f@0: /* if mouseSelection.getStart == keypress, it means the selection is at the * f@0: * right side of where the user started to select. Conversely id mouse selection * f@0: * .getEnd == keypress, the selection is at the left of the point where the user * f@0: * started to select. In the former case pressing the right key will expand * f@0: * the selection to the right, whereas in latter case pressing the right key will * f@0: * shrink the selection to the right. */ f@0: f@0: if(mouseSelection.getStart() == keyPress){ f@0: setMouseSelection(keyPress,mouseSelection.getEnd()+KEY_SELECTION_INCREMENT); f@0: track.getSoundWave().scan(getMouseSelection().getEnd()); f@0: }else{ // getEnd == keyPress f@0: setMouseSelection(mouseSelection.getStart()+KEY_SELECTION_INCREMENT,keyPress); f@0: track.getSoundWave().scan(getMouseSelection().getStart()); f@0: } f@0: } f@0: f@0: /* make selection with left arrow key */ f@0: }else if(evt.getKeyCode() == KeyEvent.VK_LEFT && evt.isShiftDown()){ f@0: if(keyPress == KEY_PRESS_NO_SELECTION){ f@0: keyPress = track.getSoundWave().getCurrentChunkPosition(); f@0: f@0: /* keep the selection after the beginning - 0 - of the audio track */ f@0: int start = keyPress-KEY_SELECTION_INCREMENT > 0 ? keyPress-KEY_SELECTION_INCREMENT : 0; f@0: setMouseSelection(start,keyPress); f@0: /* scrub along with the selection left end */ f@0: track.getSoundWave().scan(getMouseSelection().getStart()); f@0: }else{ // user is adjusting the selection f@0: /* See selectio adjustment for the right key. * f@0: * The idea is the same, just with inverted left and right */ f@0: if(mouseSelection.getEnd() == keyPress){ f@0: setMouseSelection(mouseSelection.getStart()-KEY_SELECTION_INCREMENT,keyPress); f@0: track.getSoundWave().scan(getMouseSelection().getStart()); f@0: }else{ // getStart == keyPress f@0: setMouseSelection(keyPress,mouseSelection.getEnd()-KEY_SELECTION_INCREMENT); f@0: track.getSoundWave().scan(getMouseSelection().getEnd()); f@0: } f@0: } f@0: f@0: /* jump to beginning of selection, if any */ f@0: }else if(evt.getKeyCode() == KeyEvent.VK_F2 || evt.getKeyCode() == KeyEvent.VK_F3){ f@0: if(mouseSelection.equals(Selection.ZERO_SELECTION)){ f@0: Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); f@0: }else if (evt.getKeyCode() == KeyEvent.VK_F2){ f@0: track.getSoundWave().setPosition(mouseSelection.getStart()); f@0: }else{ f@0: track.getSoundWave().setPosition(mouseSelection.getEnd()); f@0: } f@0: } f@0: } f@0: f@0: @Override f@0: public void keyReleased(KeyEvent evt) { f@0: /* create selection in the model when user releases shift key */ f@0: if(evt.getKeyCode() == KeyEvent.VK_SHIFT && keyPress != KEY_PRESS_NO_SELECTION){ f@0: keyPress = KEY_PRESS_NO_SELECTION; f@0: track.getSoundWave().setSelection(getMouseSelection()); f@0: } f@0: } f@0: f@0: @Override f@0: public void keyTyped(KeyEvent evt) { } f@0: } // class AudioTrackMouseInteraction f@0: f@0: f@0: