view 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
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.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;

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.Sonification;
import uk.ac.qmul.eecs.depic.daw.SoundType;
import uk.ac.qmul.eecs.depic.daw.SoundWave;
import uk.ac.qmul.eecs.depic.daw.beads.sonification.BeadsSonification;
import uk.ac.qmul.eecs.depic.patterns.Range;
import uk.ac.qmul.eecs.depic.patterns.Sequence;

/**
 * 
 * Actions for the manipulation of the automation graph. 
 * 
 *
 */
class AutomationGraphActions implements Iterable<Action> {
	private static AutomationGraphActions singleton; 
	private SequencePoint heldPoint;
	
	public final AddAutomationValueAction addAutomationValueAction;
	public final RemoveAutomationValueAction removeAutomationValueAction;
	public final MoveAutomationValueAction moveAutomationValueUpAction;
	public final MoveAutomationValueAction moveAutomationValueLeftAction;
	public final MoveAutomationValueAction moveAutomationValueDownAction;
	public final MoveAutomationValueAction moveAutomationValueRightAction;
	public final ResetAutomationValuesAction resetAutomationAction;
	public final ListenWholeAutomationAction listenWholeAutomationAction;
	public final StopListenWholeAutomationAction stopListenWholeAutomationAction;
	public final SwitchListenAutomationAction switchListenAutomationAction;
	public final SwitchListenPeakLevelAction switchListenPeakLevelAction;
	public final ListenWholePeakLevelAction listenWholePeakLevelAction;
	
	private Sonification sonification;

	static AutomationGraphActions getInstance(){
		if(singleton == null)
			singleton = new AutomationGraphActions();
		return singleton;
	}
	
	private AutomationGraphActions (){ 
		addAutomationValueAction = new AddAutomationValueAction();
		removeAutomationValueAction = new RemoveAutomationValueAction();
		moveAutomationValueUpAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl UP"),0.0f,0.05f);
		moveAutomationValueDownAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl DOWN"),0.0f,-0.05f);
		moveAutomationValueLeftAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl LEFT"),-50.0f,0.0f);
		moveAutomationValueRightAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl RIGHT"),50.0f,0.0f);
		resetAutomationAction = new ResetAutomationValuesAction();
		listenWholeAutomationAction = new ListenWholeAutomationAction();
		stopListenWholeAutomationAction = new StopListenWholeAutomationAction();
		switchListenAutomationAction = new SwitchListenAutomationAction();
		switchListenPeakLevelAction = new SwitchListenPeakLevelAction();
		listenWholePeakLevelAction = new ListenWholePeakLevelAction();
		
		sonification = Daw.getSoundEngineFactory().getSharedSonification();
	}
	
	/**
	 * Create a new Actions than plays the automation value sound when it is at zero point, 
	 * that is in the middle of the automation range  
	 * 
	 * @param val
	 * @return
	 */
	private static Action createZeroValAction(final Sequence.Value val){
		return new AbstractAction() {
			private static final long serialVersionUID = 1L;
			private boolean doneOnce = false;
			@Override
			public void actionPerformed(ActionEvent evt){
				/* only play feedback once. This action remains until a new action 
				 * for the same keystroke is installed. A new action is installed 
				 * when the user presses ctrl + up/down/left/right arrow (see MoveAutomation 
				 * Feedback as to be given only once by each zeroAction, otherwise 
				 * the sonification will be played each time the user releases a ctrl + 
				 * arrow action, regardless of whether they actually moved an automation or nor */
				if(doneOnce){
					return;
				}else{
					doneOnce = true;
				}
				Daw.getSoundEngineFactory().getSharedSonification().getSequenceMapping(SoundType.AUTOMATION).renderValue(
					new Sequence.Value() {
						@Override
						public int index() {
							return val.index();
						}
						
						@Override
						public float getValue() {
							Range<Float> range = val.getSequence().getRange();
							return range.getStart() + range.lenght()/2;
						}
						
						@Override
						public float getTimePosition() {
							return val.getTimePosition();
						}
						
						@Override
						public Sequence getSequence() {
							return val.getSequence();
						}
					});
			}
		};
	}

	@Override
	public Iterator<Action> iterator(){
		return new Iterator<Action>(){
			private int index = 0;
			private Action [] actions = new Action [] {
					addAutomationValueAction,
					removeAutomationValueAction,
					moveAutomationValueUpAction,
					moveAutomationValueLeftAction,
					moveAutomationValueDownAction,
					moveAutomationValueRightAction,
					resetAutomationAction,
					listenWholeAutomationAction,
					stopListenWholeAutomationAction,
					switchListenAutomationAction,
					switchListenPeakLevelAction,
					listenWholePeakLevelAction
			};
			
			@Override
			public boolean hasNext() {
				return index < actions.length;
			}

			@Override
			public Action next() {
				if(index == actions.length)
					throw new NoSuchElementException();
				return actions[index++];
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException("remove not supported");
			}
			
		};
	}
	
	static abstract class AutomationGraphAction extends AbstractAction {
		private static final long serialVersionUID = 1L;
		
		AutomationGraphAction(KeyStroke keyStroke){
			putValue(Action.ACCELERATOR_KEY,keyStroke);
		}
		
		@Override 
		public void actionPerformed(ActionEvent evt){
			handleActionPerformed((AudioTrack)evt.getSource());
		}
		
		public abstract void handleActionPerformed(AudioTrack track);
			
	}
	
	class AddAutomationValueAction extends AutomationGraphAction {
		private static final long serialVersionUID = 1L;
		
		public AddAutomationValueAction() {
			super(KeyStroke.getKeyStroke("ctrl INSERT"));
		}

		@Override
		public void handleActionPerformed(AudioTrack track) {
			if(track == null || !track.getSoundWave().hasSequence()) {
				return;
			}
			
			SoundWave wave = track.getSoundWave();
			int cursorPos = wave.getCurrentChunkPosition();
			
			Point2D.Float p = AudioTrack.getAutomationCoord(track, cursorPos, track.getHeight()/2);
			
			/* if user is trying to create an automation outside the bounds of the automation, notify the error */
			if(p == null){
				sonification.play(SoundType.ERROR);
				return;
			}
			
			/* if a new point is created when moving another point the held point will still be the old one        * 
			 * by setting it to null the hel point will become the new one as soon as the user will try to move it */
			heldPoint = null;
			sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue(
					wave.getParametersControl().getCurrentAutomation().add(p.x, p.y));
		}
	} // class AddAutomationAction
	
	class MoveAutomationValueAction extends AutomationGraphAction {
		private static final long serialVersionUID = 1L;
		private float deltaX;
		private float deltaY;
		
		public MoveAutomationValueAction(KeyStroke keyStroke, float deltaX, float deltaY){
			super(keyStroke);
			this.deltaX = deltaX;
			this.deltaY = deltaY;
			heldPoint = null;
		}
		
		@Override
		public void handleActionPerformed(final AudioTrack track) {
			if(track == null || !track.getSoundWave().hasSequence()){
				heldPoint = null;
				return;
			}
			
			int cursorPos = track.getSoundWave().getCurrentChunkPosition();
			
			/* if ctrl is held (heldPoint != null) it means the user is still moving around a  * 
			 * point that they grabbed previously. So the check that the point be under the    *
			 * cursor must not be done, and the point can be moved and played anyway           */
			if(heldPoint != null){
				changeAutomation((AutomationValue)heldPoint.getSequenceValue(),track);
				return;
			}
			
			/* find the automaton value under the cursor position if any */
			for(SequencePoint itr : track.getAutomationGraph().getSequencePoints()){
				if( itr.isXCentredAt(cursorPos) ){
					/* p is now the heldPoint, until the user releaser the ctrl key this point will be referenced * 
					 * in next move action even if it's not under the the cursor is not under the cursor anymore  */
					heldPoint = itr;
					/* set up an action that will stop scrubbing free the held point when the user releases the ctrl key */
					track.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, 0 , true), "heldPoint");
					track.getActionMap().put("heldPoint", new AbstractAction(){
						private static final long serialVersionUID = 1L;
						@Override
						public void actionPerformed(ActionEvent evt) {
								heldPoint = null;
								/* stop scrubbing */
								track.getSoundWave().scan(SoundWave.STOP_SCANNING);
								/* de-register itself from the track registered actions */
								track.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, 0 , true), "none");
								track.getActionMap().remove("heldPoint");
						}
					});
					
					changeAutomation((AutomationValue)itr.getSequenceValue(),track);
					break;
				}
			}
		}
		
		protected void changeAutomation(AutomationValue val, AudioTrack track){
			val.setLocation(val.getTimePosition()+deltaX, val.getValue()+deltaY);
			
			/* install the action that plays the zero value when the key is released */
			KeyStroke thisActionKeyStroke = (KeyStroke)getValue(Action.ACCELERATOR_KEY);
			KeyStroke zeroValKeyStroke = KeyStroke.getKeyStroke(thisActionKeyStroke.getKeyCode(),thisActionKeyStroke.getModifiers(),true);
			
			if(Preferences.userNodeForPackage(BeadsSonification.class).getBoolean("render_val.ref", false)){
				track.getInputMap(JComponent.WHEN_FOCUSED).put(zeroValKeyStroke, "zeroVal");
				track.getActionMap().put("zeroVal", createZeroValAction(val));
			}
			
			/* besides moving the automation, scrub the cursor along */
			int scanTo = (int)(val.getTimePosition() / track.getSoundWave().getMillisecPerChunk());
			track.getSoundWave().scan(scanTo);
			sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue(heldPoint.getSequenceValue());
		}
	} // class MoveAutomationValueAction

	class RemoveAutomationValueAction extends AutomationGraphAction {
		private static final long serialVersionUID = 1L;

		public RemoveAutomationValueAction() {
			super(KeyStroke.getKeyStroke("ctrl DELETE"));
		}

		@Override
		public void handleActionPerformed(AudioTrack track) {
			if(track == null || !track.getSoundWave().hasSequence()){
				return;
			}
			
			int cursorPos = track.getSoundWave().getCurrentChunkPosition();
			
			/* find the automaton value */
			SequencePoint p = null;
			for(SequencePoint itr : track.getAutomationGraph().getSequencePoints()){
				float dist = Math.abs(cursorPos - (int)itr.getCenterX() );
				
				if( dist  <  SequencePoint.SIZE ){
					if(p == null || dist < Math.abs((int)p.getCenterX() - (int)itr.getCenterX())){
						p = itr;
						/* don't break as it keeps looking for other points, which might be closer to the cursor */
					}
				}
			}
			
			/* if found, then remove it and play it */
			if(p != null){
				((Automation)p.getSequenceValue().getSequence()).remove((AutomationValue)p.getSequenceValue());
				sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue(p.getSequenceValue());
			}
		}		
		
	} // class RemoveAutomationAction
	
	class ResetAutomationValuesAction extends AutomationGraphAction {
		private static final long serialVersionUID = 1L;

		public ResetAutomationValuesAction() {
			super(KeyStroke.getKeyStroke("ctrl R "));
		}

		@Override
		public void handleActionPerformed(AudioTrack track){
			if(track == null || !track.getSoundWave().hasSequence()){ 
				return;
			}
			
			int confirmation = JOptionPane.showConfirmDialog(track, 
					"Are you sure you want to reset the automation ?", // FIXME bundle 
					"Confirmation Dialog",
					JOptionPane.YES_NO_OPTION
					);
			
			if(confirmation == JOptionPane.YES_OPTION){
				track.getSoundWave().getParametersControl().getCurrentAutomation().reset();
			}			
		}
	} // class ClearAutomationValuesAction 
	
	class ListenWholeAutomationAction extends AutomationGraphAction {
		private static final long serialVersionUID = 1L;

		public ListenWholeAutomationAction() {
			super(KeyStroke.getKeyStroke("ctrl shift L"));
		}

		@Override
		public void handleActionPerformed(AudioTrack track){
			if(track == null || (!track.getSoundWave().hasSequence()) ){ 
				sonification.play(SoundType.ERROR);
				return;
			}
			
			track.getSoundWave().getTransportControl().rew();
			sonification.getSequenceMapping(SoundType.AUTOMATION).renderCurve(
					track.getSoundWave().getParametersControl().getCurrentAutomation(),0.0f);			
			track.getSoundWave().getTransportControl().play();
		}
	} // class SonifyAutomationAction
	
	class StopListenWholeAutomationAction extends AutomationGraphAction {
		private static final long serialVersionUID = 1L;
		
		public StopListenWholeAutomationAction(){
			super(KeyStroke.getKeyStroke("ctrl K"));
		}
		
		@Override
		public void handleActionPerformed(AudioTrack track){
			sonification.getSequenceMapping(SoundType.AUTOMATION).renderCurve(
					null,-100.0f);
			sonification.getSequenceMapping(SoundType.PEAK_LEVEL).renderCurve(
					null,-100.0f);
			track.soundWave.getTransportControl().stop(); 
		}
	}
	
	class SwitchListenAutomationAction extends AutomationGraphAction {
		private static final long serialVersionUID = 1L;
		private boolean isOn;
		
		public SwitchListenAutomationAction(){
			super(KeyStroke.getKeyStroke("ctrl L"));
			isOn = false;
		}
		
		@Override
		public void handleActionPerformed(AudioTrack track){
			if(track.getSoundWave().hasSequence()){
				isOn = !isOn;
				
				track.showAutomationSound(isOn);
				sonification.play(SoundType.OK);
			}else{
				sonification.play(SoundType.ERROR);
			}
		}
		
		public boolean isSwitchOn(){
			return isOn;
		}
	} // class SwitchListenAutomation
	
	class SwitchListenPeakLevelAction extends AutomationGraphAction {
		private static final long serialVersionUID = 1L;
		private boolean isOn;
		
		SwitchListenPeakLevelAction(){
			super(KeyStroke.getKeyStroke("ctrl P"));
			isOn = false;
		}
		
		@Override
		public void handleActionPerformed(AudioTrack track){
			if( track == null || (!track.getSoundWave().getDbWave().hasSequence())){
				Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR);
				return;
			}
			isOn = !isOn;
					
			track.showPeakLevelSound(isOn);
			Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.OK);
		}
		
		public boolean isSwitchOn(){
			return isOn;
		}
	} // SwitchListenPeakLevelAction
	
	class ListenWholePeakLevelAction extends AutomationGraphAction {
		private static final long serialVersionUID = 1L;
		
		ListenWholePeakLevelAction(){
			super(KeyStroke.getKeyStroke("ctrl shift P"));
		}
		
		@Override
		public void handleActionPerformed(AudioTrack track){
			if(track == null || (!track.getSoundWave().getDbWave().hasSequence()) ){ 
				sonification.play(SoundType.ERROR);
				return;
			}
			
			track.getSoundWave().getTransportControl().rew();
			sonification.getSequenceMapping(SoundType.PEAK_LEVEL).renderCurve(
					track.getSoundWave().getDbWave().getSequence(),0.0f);			
			track.getSoundWave().getTransportControl().play();
		}
	}
	
}