view 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
line wrap: on
line source
/*  
 Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation.

 Copyright (C) 2015  Queen Mary University of London (http://depic.eecs.qmul.ac.uk/)
	
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.qmul.eecs.depic.daw.gui;

import java.awt.Dimension;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;

import uk.ac.qmul.eecs.depic.daw.Automation;
import uk.ac.qmul.eecs.depic.daw.AutomationValue;
import uk.ac.qmul.eecs.depic.daw.Daw;
import uk.ac.qmul.eecs.depic.daw.Direction;
import uk.ac.qmul.eecs.depic.daw.Selection;
import uk.ac.qmul.eecs.depic.daw.SoundType;
import uk.ac.qmul.eecs.depic.daw.SoundWave;

/*
 * This class handles the interaction with the mouse. When the mouse is dragged around 
 * the corresponding selection appears on this audio track. When the user releases the 
 * mouse button (and therefore stops dragging) the selection is assigned to 
 * the underlying sound wave by a call to setSelection().   
 */
 final class AudioTrackInput extends MouseInputAdapter implements KeyListener  {
	private static final int KEY_SELECTION_INCREMENT = 1;
	private static final int KEY_PRESS_NO_SELECTION = -1;
	private int mousePress; // where the user presses the mouse 
	private int keyPress;
	/* used to check whether the user is dragging a new selection */
	private boolean isDragging;
	private Selection mouseSelection;
	private AudioTrack track;
	private SequencePoint dragAutomationPoint;	
	
	AudioTrackInput(AudioTrack track){	
		this.track = track;
		setAudioTrackActions(track);
		mouseSelection = Selection.ZERO_SELECTION;
		keyPress = KEY_PRESS_NO_SELECTION;
	}
	
	private void setAudioTrackActions(AudioTrack track){
		for(Action action : AutomationGraphActions.getInstance()){
			Object unique = new Object();
			
			track.getInputMap(JComponent.WHEN_FOCUSED).put((KeyStroke)action.getValue(Action.ACCELERATOR_KEY), unique);
			track.getActionMap().put(unique,action);
		}
	}
	
	/**
	 * Sets the mouse selection to a new {@code Selection} ranging from {@code start} to {@code end}.
	 * 
	 * @param start
	 * @param end
	 */
	public void setMouseSelection(int start, int end){
		Selection oldSelection = mouseSelection;
		if(end == -1) // open Selection
			mouseSelection = new Selection(start,track.getScaleFactor());
		else
			mouseSelection = new Selection(start,end,track.getScaleFactor());
		
		track.firePropertyChange("mouseDragSelection",
				oldSelection,
				mouseSelection);
		track.repaint();
	}
	
	/**
	 * Sets the mouse selection and sets the beginning of the selection action to 
	 * either {@code start} or {@start end} (according to the value of {@code mouseAtStart}). 
	 * 
	 * The beginning of the selection action is where a user presses the mouse click 
	 * in order to start a dragging action which creates the selections. The click
	 * point affects how the selection is: if the user drags towards
	 * left, the selection starts from the current dragging point and ends at the click point;
	 * conversely if the user drags towards right the selection starts at the click point and
	 * end at the current dragging point. The current dragging point changes as the user drags the mouse
	 * around
	 * 
	 * @param start the beginning of the selection
	 * @param end start the end of the selection
	 * @param mouseAtStart {@code true} if the beginning of the selection action was the 
	 * selection start, {@code false} if it's the selection end
	 * 
	 */
	private void setMouseSelection(int start, int end, boolean mouseAtStart){ // FIXME remove
		if(end == -1){ // open selection
			mousePress = start;
		}else{
			mousePress = mouseAtStart ? start : end;
		}
		setMouseSelection(start,end);
	}
	
	/**
	 * Returns the current mouse selection or {@code Selection.VOID_SELECTION} if there is no selection.
	 * 
	 * @return the current mouse selection or {@code Selection.VOID_SELECTION} if there is no selection.
	 */
	public Selection getMouseSelection(){
		return mouseSelection;
	}
	
	@Override
	public void mousePressed(MouseEvent evt){
		if(SwingUtilities.isLeftMouseButton(evt)){
			/* if automation is on */
			if(track.getSoundWave().hasSequence()){
				for(SequencePoint p : track.getAutomationGraph().getSequencePoints()){
					if(p.contains(evt.getX(),evt.getY())){
						dragAutomationPoint = p;
						return;
					}
				}
			}
			
			/* keep track of where the mouse is pressed. If the user drags a selection this is needed     *
			 * to make the right selection according to the direction of the dragging. see mouseDragged() */
			mousePress = evt.getX();
		}
	}
	
	@Override
	public void	mouseDragged(MouseEvent evt){
		if(SwingUtilities.isLeftMouseButton(evt)){
			
			/* bound the dragging to the space within the sound wave in the track */
			Dimension trackSize = track.getSize();
			if(evt.getX()<0 || evt.getX() >= track.getSoundWave().getChunkNum() || evt.getY()<0 || evt.getY() > trackSize.height )
				return;
			
			/* if we are dragging an automation point, change its coordinate and refresh the automation */
			if(track.getSoundWave().hasSequence() && dragAutomationPoint != null){
				Point2D.Float pointCoord = AudioTrack.getAutomationCoord(track,evt.getX(),evt.getY());
				AutomationValue automVal = (AutomationValue)dragAutomationPoint.getSequenceValue(); 
				automVal.setLocation(pointCoord.x,pointCoord.y);

				return;
			}
			
			/* update the mouse selection as the user drags the mouse. The selection differs     * 
			 * according to the direction of the dragging. If the user moves right from where    *  
			 * they pressed, then the start of the selection is the press point, and the end the *
			 * current dragging point (evt.getX()). Conversely, if the user moves to the left,   *
			 * the start of the selection is the dragging point and the end is the press point.  *   
			 * The press point is the value assigned to mousePressX in mousePressed              */
			
			isDragging = true;
			if(evt.getX() > mousePress){
				setMouseSelection(mousePress,evt.getX());
			}else{
				setMouseSelection(evt.getX(),mousePress);
			}
		}
	}

	@Override
	public void mouseReleased(MouseEvent evt){
		if(SwingUtilities.isLeftMouseButton(evt)){
			dragAutomationPoint = null;
			if(isDragging){
				/* if the user was dragging update the selection in the sound wave */
				track.getSoundWave().setSelection(getMouseSelection());
				isDragging = false;
			}
		}
	}
	
	@Override
	public void mouseClicked(MouseEvent evt){
		/* left click  : set the cursor position * 
		 * right click : open pop up menu         */
		if(SwingUtilities.isLeftMouseButton(evt)){
			track.getSoundWave().setPosition(evt.getX());
		}else if(SwingUtilities.isRightMouseButton(evt)){
			/* show automation popup only if an automation is is displayed */
			if(track.getSoundWave().hasSequence()){
				for(Shape point : track.getAutomationGraph().getSequencePoints()){
					if(point.contains(evt.getX(), evt.getY())){
						showAutomationPopup(point,evt.getX(),evt.getY());
						return;
					}
				}
				
				for(Shape line : track.getAutomationGraph().getSequenceLines()){
					if(line.contains(evt.getX(), evt.getY())){
						showAutomationPopup(line,evt.getX(),evt.getY());
						return;
					}
				}
			}
			
			/* gets here only if no automation has been found under the mouse pointer */
			if(track.getSoundWave().getDbWave().hasSequence()){
				showPeakLevelPopup(evt.getX(),evt.getY());
			}
		}
	}

	
	void showAutomationPopup(final Shape selectedShape, final int x, final int y){
		final SoundWave wave = track.getSoundWave();
		if(x >= wave.getChunkNum()){
			/* don't show if the user clicks past the sound wave */
			return;
		}
		
		JPopupMenu pop = new JPopupMenu();
		if(selectedShape instanceof SequencePoint){
			pop.add(new AbstractAction("Remove automation point"){
				private static final long serialVersionUID = 1L;
				@Override
				public void actionPerformed(ActionEvent e) {
					SequencePoint point = (SequencePoint)selectedShape;
					Automation automation = wave.getParametersControl().getCurrentAutomation(); 
					automation.remove((AutomationValue)point.getSequenceValue());
				}
			});
		}else{ // Line2D
			pop.add(new AbstractAction("Add automation point"){
				private static final long serialVersionUID = 1L;
				@Override
				public void actionPerformed(ActionEvent e) {
					Automation automation = wave.getParametersControl().getCurrentAutomation(); 
					Point2D.Float p = AudioTrack.getAutomationCoord(track,x,y); 
					automation.add(p.x, p.y); 
				}
			});
		}
		
		pop.add(new AbstractAction("Reset Automation"){
			private static final long serialVersionUID = 1L;

			@Override
			public void actionPerformed(ActionEvent evt) {
				AutomationGraphActions.getInstance().resetAutomationAction.handleActionPerformed(track);
			}
		});
		
		Action listenToAutomation = new AbstractAction("Listen to Automation"){
			private static final long serialVersionUID = 1L;
			@Override
			public void actionPerformed(ActionEvent e) {
				AutomationGraphActions.getInstance().listenWholeAutomationAction.handleActionPerformed(track);
			}
		}; 
		listenToAutomation.putValue(Action.ACCELERATOR_KEY, 
				AutomationGraphActions.getInstance().listenWholeAutomationAction.getValue(Action.ACCELERATOR_KEY));
		pop.add(listenToAutomation);
		
		Action stopListenToAutomation = new AbstractAction("Stop Listen To Automation"){
			private static final long serialVersionUID = 1L;
			@Override
			public void actionPerformed(ActionEvent e) {
				AutomationGraphActions.getInstance().stopListenWholeAutomationAction.handleActionPerformed(track);
			}
		};
		stopListenToAutomation.putValue(Action.ACCELERATOR_KEY, 
				AutomationGraphActions.getInstance().stopListenWholeAutomationAction.getValue(Action.ACCELERATOR_KEY));
		pop.add(stopListenToAutomation);
		
		Action switchListenAutomation = new AbstractAction("Switch Listen Automation "+
				(AutomationGraphActions.getInstance().switchListenAutomationAction.isSwitchOn() ? "off" : "on")){
			private static final long serialVersionUID = 1L;
			@Override
			public void actionPerformed(ActionEvent e) {
				AutomationGraphActions.getInstance().switchListenAutomationAction.handleActionPerformed(track);
			}
		};
		switchListenAutomation.putValue(Action.ACCELERATOR_KEY, 
				AutomationGraphActions.getInstance().switchListenAutomationAction.getValue(Action.ACCELERATOR_KEY));
		pop.add(switchListenAutomation);
		pop.show(track, x, y);
	}

	void showPeakLevelPopup(int x , int y){
		final SoundWave wave = track.getSoundWave();
		if(x >= wave.getChunkNum()){
			/* don't show if the user clicks past the sound wave */
			return;
		}
		
		JPopupMenu pop = new JPopupMenu();
		
		pop.add(new AbstractAction("Switch listen peak level " + 
			(AutomationGraphActions.getInstance().switchListenPeakLevelAction.isSwitchOn() ? "off" : "on")){
			private static final long serialVersionUID = 1L;

			@Override
			public void actionPerformed(ActionEvent evt) {
				AutomationGraphActions.getInstance().switchListenPeakLevelAction.handleActionPerformed(track);
			}			
		});
		
		pop.add(new AbstractAction("Listen to Peak Level"){
			private static final long serialVersionUID = 1L;
			
			@Override
			public void actionPerformed(ActionEvent evt) {
				AutomationGraphActions.getInstance().listenWholePeakLevelAction.handleActionPerformed(track);
			}
		});
		
		pop.show(track,x,y);
	}
	
	@Override
	public void keyPressed(KeyEvent evt) {
		/* adjust the selection */
		if(evt.isShiftDown() && evt.isControlDown() && 
				!mouseSelection.equals(Selection.ZERO_SELECTION )){			
			Direction d = Direction.NONE;
			if(evt.getKeyCode() == KeyEvent.VK_LEFT){
				d = Direction.LEFT;
			}else if(evt.getKeyCode() == KeyEvent.VK_RIGHT){
				d = Direction.RIGHT;
			} else {
				return;
			}
			
			SoundWave wave = track.getSoundWave();
			int cursorPos = wave.getCurrentChunkPosition();
			if(cursorPos == mouseSelection.getStart()){
				if(d == Direction.LEFT){ // left 
					wave.setSelection(new Selection(
							mouseSelection.getStart()-KEY_SELECTION_INCREMENT,
							mouseSelection.getEnd(),
							wave.getScaleFactor()));
					wave.scan(cursorPos-KEY_SELECTION_INCREMENT);
				}else{ // right 
					if(mouseSelection.getEnd() - mouseSelection.getStart() <= KEY_SELECTION_INCREMENT){
						/* cannot push it too right so as to trespass the end of the selection */
						Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR);
					}else{
						wave.setSelection(new Selection(
								mouseSelection.getStart()+KEY_SELECTION_INCREMENT,
								mouseSelection.getEnd(),
								wave.getScaleFactor()));
						wave.scan(cursorPos+KEY_SELECTION_INCREMENT);
					}
				}
			}else if (cursorPos == mouseSelection.getEnd()){
				if(d == Direction.LEFT){ // left 
					if(mouseSelection.getEnd() - mouseSelection.getStart() <= KEY_SELECTION_INCREMENT){
						/* cannot push it too keft so as to trespass the start of the selection */
						Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR);
					}else{
						wave.setSelection(new Selection(
								mouseSelection.getStart(),
								mouseSelection.getEnd()-KEY_SELECTION_INCREMENT,
								wave.getScaleFactor()));
						wave.scan(cursorPos-KEY_SELECTION_INCREMENT);
					}
				}else{ // right 
					wave.setSelection(new Selection(
							mouseSelection.getStart(),
							mouseSelection.getEnd()+KEY_SELECTION_INCREMENT,
							wave.getScaleFactor()));
					wave.scan(cursorPos+KEY_SELECTION_INCREMENT);
				}
			}
			
		/* make selection with right arrow key */
		} else if(evt.getKeyCode() == KeyEvent.VK_RIGHT && evt.isShiftDown() ){
			if(keyPress == KEY_PRESS_NO_SELECTION){ // new selection 
				keyPress = track.getSoundWave().getCurrentChunkPosition();
				setMouseSelection(keyPress, keyPress+KEY_SELECTION_INCREMENT);
				/* scrub along with the selection right end */
				track.getSoundWave().scan(getMouseSelection().getEnd());
				/* start the selection sound */
			}else{ // user is adjusting the selection 
				/* if mouseSelection.getStart == keypress, it means the selection is at the       *
				 * right side of where the user started to select. Conversely id mouse selection  *
				 * .getEnd == keypress, the selection is at the left of the point where the user   *
				 * started to select. In the former case pressing the right key will expand       *
				 * the selection to the right, whereas in latter case pressing the right key will  *
				 * shrink the selection to the right.                                             */
				
				if(mouseSelection.getStart() == keyPress){
					setMouseSelection(keyPress,mouseSelection.getEnd()+KEY_SELECTION_INCREMENT);
					track.getSoundWave().scan(getMouseSelection().getEnd());
				}else{ // getEnd == keyPress
					setMouseSelection(mouseSelection.getStart()+KEY_SELECTION_INCREMENT,keyPress);
					track.getSoundWave().scan(getMouseSelection().getStart());
				}
			}
			
		/* make selection with left arrow key */	
		}else if(evt.getKeyCode() == KeyEvent.VK_LEFT && evt.isShiftDown()){
			if(keyPress == KEY_PRESS_NO_SELECTION){
				keyPress = track.getSoundWave().getCurrentChunkPosition();
				
				/* keep the selection after the beginning - 0 - of the audio track  */
				int start = keyPress-KEY_SELECTION_INCREMENT > 0 ? keyPress-KEY_SELECTION_INCREMENT : 0; 
				setMouseSelection(start,keyPress);
				/* scrub along with the selection left end */
				track.getSoundWave().scan(getMouseSelection().getStart());
			}else{ // user is adjusting the selection
				/* See selectio adjustment for the right key.              *
				 * The idea is the same, just with inverted left and right */
				if(mouseSelection.getEnd() == keyPress){
					setMouseSelection(mouseSelection.getStart()-KEY_SELECTION_INCREMENT,keyPress);
					track.getSoundWave().scan(getMouseSelection().getStart());
				}else{ // getStart == keyPress
					setMouseSelection(keyPress,mouseSelection.getEnd()-KEY_SELECTION_INCREMENT);
					track.getSoundWave().scan(getMouseSelection().getEnd());
				}
			}
			
		/* jump to beginning of selection, if any */	
		}else if(evt.getKeyCode() == KeyEvent.VK_F2 || evt.getKeyCode() == KeyEvent.VK_F3){
			if(mouseSelection.equals(Selection.ZERO_SELECTION)){
				Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR);
			}else if (evt.getKeyCode() == KeyEvent.VK_F2){
				track.getSoundWave().setPosition(mouseSelection.getStart());
			}else{
				track.getSoundWave().setPosition(mouseSelection.getEnd()); 
			}
		}
	}

	@Override
	public void keyReleased(KeyEvent evt) {
		/* create selection in the model when user releases shift key  */
		if(evt.getKeyCode() == KeyEvent.VK_SHIFT && keyPress != KEY_PRESS_NO_SELECTION){
			keyPress = KEY_PRESS_NO_SELECTION;
			track.getSoundWave().setSelection(getMouseSelection());
		}
	}

	@Override
	public void keyTyped(KeyEvent evt) { 	}	
} // class AudioTrackMouseInteraction