f@0: package uk.ac.qmul.eecs.depic.daw.haptics; f@0: f@0: import uk.ac.qmul.eecs.depic.daw.Chunk; f@0: import uk.ac.qmul.eecs.depic.daw.SoundWave; f@0: import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent; f@0: import uk.ac.qmul.eecs.depic.daw.SoundWaveListener; f@0: import uk.ac.qmul.eecs.depic.jhapticgui.Haptics; f@0: import uk.ac.qmul.eecs.depic.patterns.MathUtils; f@0: import uk.ac.qmul.eecs.depic.patterns.Range; f@0: import uk.ac.qmul.eecs.depic.patterns.Sequence; f@0: import uk.ac.qmul.eecs.depic.patterns.Sequence.Value; f@0: import uk.ac.qmul.eecs.depic.patterns.SequenceEvent; f@0: import uk.ac.qmul.eecs.depic.patterns.SequenceListener; f@0: import uk.ac.qmul.eecs.depic.patterns.SequenceMapping; f@0: f@2: /** f@2: * f@2: * Listens to elements of the DAW and sends commands to the haptic device in order to f@2: * render such elements in haptics. It implements all the listeners interfaces because it reacts f@2: * to changes and mirrors them in haptics. f@2: * f@2: * It is used to render sound waves and automations (sequences) in haptics f@2: * f@2: */ f@0: public class HapticTrigger implements SequenceMapping, SequenceListener, SoundWaveListener { f@0: public enum DisplayType { f@0: DISPLAY_SEQUENCE, f@0: DISPLAY_NONE, f@0: DISPLAY_PEAKS, f@0: DISPLAY_RENDER_CURVE_AT, f@0: DISPLAY_RENDER_VALUE f@0: } f@0: f@0: public abstract class Command { f@0: /** f@0: * Message to display a graph f@0: * f@0: * Arguments: intitial y value, max y value, min y value, max x value, x viewport size f@0: */ f@0: public static final String DISPLAY_SEQUENCE = "display.seq"; f@0: public static final String DISPLAY_RENDER_CURVE_AT = "display.curve_at"; f@0: public static final String DISPLAY_RENDER_VALUE = "display.value"; f@0: /** f@0: * display nothing. takes no args f@0: */ f@0: public static final String DISPLAY_NONE = "display.none"; f@0: public static final String DISPLAY_PEAKS = "display.peaks"; f@0: f@0: /** f@0: * Arguments : x time position, y in normalized form f@0: */ f@0: public static final String SEQUENCE_VALUE_ADD = "seq.value.add"; f@0: public static final String SEQUENCE_VALUE_CHANGE = "seq.value.change"; f@0: public static final String SEQUENCE_VALUE_REMOVE = "seq.value.rem"; f@0: public static final String SEQUENCE_VALUE_FIND = "seq.value.find"; f@0: public static final String SEQUENCE_SHIFT = "seq.shift"; f@0: public static final String SEQUENCE_BEGIN = "seq.begin"; f@0: f@0: public static final String RENDER_VALUE = "render_value"; f@0: public static final String RENDER_CURVE_AT = "render_curve_at"; f@0: /** f@0: * Message to rotate the viscosity scrub line in the haptic space f@0: * f@0: * Argument is the degree of rotation f@0: */ f@0: public static final String ROTATE_Z = "rotate.z"; f@0: public static final String ROTATE_Y = "rotate.y"; f@0: public static final String ROTATE_X = "rotate.x"; f@0: } f@0: f@0: private DisplayType displayType; f@0: private Haptics haptics; f@0: f@0: public HapticTrigger(Haptics haptics, DisplayType type){ f@0: if(haptics == null) f@0: throw new IllegalArgumentException("haptics cannot be null"); f@0: this.haptics = haptics; f@0: displayType = type; f@0: haptics.sendMessage(Command.DISPLAY_NONE,"",0); f@0: } f@0: f@0: @Override f@0: public void renderValue(Value val) { f@0: /* sends a normalized value of the chunk size */ f@0: haptics.sendMessage(Command.RENDER_VALUE, Float.toString(val.getValue()) , val.hashCode()); f@0: } f@0: f@0: @Override f@0: public void renderCurve(Sequence m, float startTime) { f@0: throw new UnsupportedOperationException("Only update implemented"); f@0: } f@0: f@0: @Override f@0: public void renderCurveAt(Sequence sequence, float time, float duration) { f@0: float val = new MathUtils.Interpolate(sequence).linear(time); f@0: f@0: /* normalize the value to [0-1] range */ f@0: float normalizedValue = new MathUtils.Scale(sequence.getRange(), new Range(0.0f,1.0f)).linear(val); f@0: haptics.sendMessage(Command.RENDER_CURVE_AT, ""+normalizedValue, sequence.hashCode()); f@0: } f@0: f@0: @Override f@0: public void sequenceUpdated(T t) { f@0: SequenceEvent.What evtType = t.getWhat(); f@0: f@0: if(displayType == DisplayType.DISPLAY_SEQUENCE) { f@0: if(SequenceEvent.What.VALUE_ADDED.equals(evtType)){ f@0: Sequence.Value aVal = t.getValue(); f@0: f@0: /* since the haptic graph y value are in normalized form normalize first */ f@0: float verticalValue = new MathUtils.Scale(t.getSource().getRange(), Range.NORMALIZED_RANGE_F).linear(aVal.getValue()); f@0: f@0: haptics.sendMessage(Command.SEQUENCE_VALUE_ADD, aVal.getTimePosition()+" "+verticalValue, aVal.hashCode()); f@0: }else if(SequenceEvent.What.VALUE_CHANGED.equals(evtType)){ f@0: Sequence.Value aVal = t.getValue(); f@0: f@0: float verticalValue = new MathUtils.Scale(t.getSource().getRange(), Range.NORMALIZED_RANGE_F).linear(aVal.getValue()); f@0: f@0: haptics.sendMessage(Command.SEQUENCE_VALUE_CHANGE, aVal.getTimePosition() +" "+verticalValue, aVal.hashCode()); f@0: }else if(SequenceEvent.What.VALUE_REMOVED.equals(evtType)){ f@0: Sequence.Value aVal = t.getValue(); f@0: haptics.sendMessage(Command.SEQUENCE_VALUE_REMOVE, "", aVal.hashCode()); f@0: }else if(SequenceEvent.What.BEGIN_CHANGED.equals(evtType)){ f@0: Sequence sequence = t.getSource(); f@0: f@0: haptics.sendMessage(Command.SEQUENCE_BEGIN, f@0: ""+new MathUtils.Scale(sequence.getRange(),Range.NORMALIZED_RANGE_F).linear(sequence.getBegin()), f@0: 0 f@0: ); f@0: } f@0: } f@0: f@0: } f@0: f@0: /** f@0: * Gets an update from a {@code SoundWave} this class is a listener of, and send a message to the {@code HapticDevice} f@0: * thread accordingly. f@0: * f@0: * @param evt the new event f@0: */ f@0: @Override f@0: public void update(SoundWaveEvent evt) { f@0: String evtType = evt.getType(); f@0: f@0: if(SoundWaveEvent.POSITION_CHANGED.equals(evtType)){ f@0: SoundWave wave = evt.getSource(); f@0: int pos = (Integer)evt.getArgs(); f@0: f@0: if(pos < wave.getChunkNum()){ f@0: Chunk chunk = wave.getChunkAt(pos); f@0: float chunkSize = chunk.getNormEnd() - chunk.getNormStart(); f@0: /* sends a normalized value of the chunk size */ f@0: haptics.sendMessage(Command.RENDER_VALUE, Float.toString(chunkSize) , chunk.hashCode()); f@0: }else{ f@0: /* if the scub goes past the audio wave just send wave amp = 0 */ f@0: haptics.sendMessage(Command.RENDER_VALUE, "0", 0); f@0: } f@0: }else if(SoundWaveEvent.CLOSE.equals(evtType)){ f@0: /* removes itself from listeners */ f@0: evt.getSource().removeSoundWaveListener(this); f@0: } f@0: } f@0: f@0: f@0: f@0: }