andrewm@0: /* andrewm@0: TouchKeys: multi-touch musical keyboard control software andrewm@0: Copyright (c) 2013 Andrew McPherson andrewm@0: andrewm@0: This program is free software: you can redistribute it and/or modify andrewm@0: it under the terms of the GNU General Public License as published by andrewm@0: the Free Software Foundation, either version 3 of the License, or andrewm@0: (at your option) any later version. andrewm@0: andrewm@0: This program is distributed in the hope that it will be useful, andrewm@0: but WITHOUT ANY WARRANTY; without even the implied warranty of andrewm@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrewm@0: GNU General Public License for more details. andrewm@0: andrewm@0: You should have received a copy of the GNU General Public License andrewm@0: along with this program. If not, see . andrewm@0: andrewm@0: ===================================================================== andrewm@0: andrewm@0: TouchkeyVibratoMapping.h: per-note mapping for the vibrato mapping class, andrewm@0: which creates vibrato through side-to-side motion of the finger on the andrewm@0: key surface. andrewm@0: */ andrewm@0: andrewm@0: #ifndef __touchkeys__TouchkeyVibratoMapping__ andrewm@0: #define __touchkeys__TouchkeyVibratoMapping__ andrewm@0: andrewm@0: andrewm@0: #include andrewm@0: #include andrewm@0: #include "../../TouchKeys/KeyTouchFrame.h" andrewm@0: #include "../../TouchKeys/KeyPositionTracker.h" andrewm@0: #include "../../TouchKeys/PianoKeyboard.h" andrewm@0: #include "../TouchkeyBaseMapping.h" andrewm@0: #include "../../Utility/IIRFilter.h" andrewm@0: andrewm@0: // This class handles the detection and mapping of vibrato gestures andrewm@0: // based on Touchkey data. It outputs MIDI or OSC messages that andrewm@0: // can be used to affect the pitch of the active note. andrewm@0: andrewm@0: class TouchkeyVibratoMapping : public TouchkeyBaseMapping { andrewm@0: friend class TouchkeyVibratoMappingFactory; andrewm@0: andrewm@0: private: andrewm@0: // Useful constants for mapping MRP messages andrewm@0: /*constexpr static const int kDefaultMIDIChannel = 0; andrewm@0: constexpr static const int kDefaultFilterBufferLength = 30; andrewm@0: andrewm@0: constexpr static const float kDefaultVibratoThresholdX = 0.05; andrewm@0: constexpr static const float kDefaultVibratoRatioX = 0.3; andrewm@0: constexpr static const float kDefaultVibratoThresholdY = 0.02; andrewm@0: constexpr static const float kDefaultVibratoRatioY = 0.8; andrewm@0: constexpr static const timestamp_diff_type kDefaultVibratoTimeout = microseconds_to_timestamp(400000); // 0.4s andrewm@0: constexpr static const float kDefaultVibratoPrescaler = 2.0; andrewm@0: constexpr static const float kDefaultVibratoRangeSemitones = 1.25; andrewm@0: andrewm@0: constexpr static const timestamp_diff_type kZeroCrossingMinimumTime = microseconds_to_timestamp(50000); // 50ms andrewm@0: constexpr static const timestamp_diff_type kMinimumOnsetTime = microseconds_to_timestamp(30000); // 30ms andrewm@0: constexpr static const timestamp_diff_type kMaximumOnsetTime = microseconds_to_timestamp(300000); // 300ms andrewm@0: constexpr static const timestamp_diff_type kMinimumReleaseTime = microseconds_to_timestamp(30000); // 30ms andrewm@0: constexpr static const timestamp_diff_type kMaximumReleaseTime = microseconds_to_timestamp(300000); // 300ms*/ andrewm@0: andrewm@0: static const int kDefaultMIDIChannel; andrewm@0: static const int kDefaultFilterBufferLength; andrewm@0: andrewm@0: static const float kDefaultVibratoThresholdX; andrewm@0: static const float kDefaultVibratoRatioX; andrewm@0: static const float kDefaultVibratoThresholdY; andrewm@0: static const float kDefaultVibratoRatioY; andrewm@0: static const timestamp_diff_type kDefaultVibratoTimeout; andrewm@0: static const float kDefaultVibratoPrescaler; andrewm@0: static const float kDefaultVibratoRangeSemitones; andrewm@0: andrewm@0: static const timestamp_diff_type kZeroCrossingMinimumTime; andrewm@0: static const timestamp_diff_type kMinimumOnsetTime; andrewm@0: static const timestamp_diff_type kMaximumOnsetTime; andrewm@0: static const timestamp_diff_type kMinimumReleaseTime; andrewm@0: static const timestamp_diff_type kMaximumReleaseTime; andrewm@0: andrewm@0: static const float kWhiteKeySingleAxisThreshold; andrewm@0: andrewm@0: enum { andrewm@0: kStateInactive = 0, andrewm@0: kStateSwitchingOn, andrewm@0: kStateActive, andrewm@0: kStateSwitchingOff andrewm@0: }; andrewm@0: andrewm@0: public: andrewm@0: // ***** Constructors ***** andrewm@0: andrewm@0: // Default constructor, passing the buffer on which to trigger andrewm@0: TouchkeyVibratoMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node* touchBuffer, andrewm@0: Node* positionBuffer, KeyPositionTracker* positionTracker); andrewm@0: andrewm@0: // ***** Destructor ***** andrewm@0: andrewm@0: ~TouchkeyVibratoMapping(); andrewm@0: andrewm@0: // ***** Modifiers ***** andrewm@0: andrewm@0: // Disable mappings from being sent andrewm@0: void disengage(bool shouldDelete = false); andrewm@0: andrewm@0: // Reset the state back initial values andrewm@0: void reset(); andrewm@0: andrewm@0: // Resend the current state of all parameters andrewm@0: void resend(); andrewm@0: andrewm@0: // Parameters for vibrato algorithm andrewm@0: //void setType(int vibratoType); andrewm@0: void setRange(float rangeSemitones); andrewm@0: void setPrescaler(float prescaler); andrewm@0: void setThresholds(float thresholdX, float thresholdY, float ratioX, float ratioY); andrewm@0: void setTimeout(timestamp_diff_type timeout); andrewm@0: andrewm@0: // ***** Evaluators ***** andrewm@0: andrewm@0: // This method receives triggers whenever events occur in the touch data or the andrewm@0: // continuous key position (state changes only). It alters the behavior and scheduling andrewm@0: // of the mapping but does not itself send OSC messages andrewm@0: void triggerReceived(TriggerSource* who, timestamp_type timestamp); andrewm@0: andrewm@0: // This method handles the OSC message transmission. It should be run in the Scheduler andrewm@0: // thread provided by PianoKeyboard. andrewm@0: timestamp_type performMapping(); andrewm@0: andrewm@0: private: andrewm@0: // ***** Private Methods ***** andrewm@0: void midiNoteOnReceived(int channel, int velocity); andrewm@0: void midiNoteOffReceived(int channel); andrewm@0: andrewm@0: void changeStateSwitchingOn(timestamp_type timestamp); andrewm@0: void changeStateActive(timestamp_type timestamp); andrewm@0: void changeStateSwitchingOff(timestamp_type timestamp); andrewm@0: void changeStateInactive(timestamp_type timestamp); andrewm@0: andrewm@0: void resetDetectionState(); andrewm@0: void clearBuffers(); andrewm@0: andrewm@0: bool keyIsWhite(); andrewm@0: andrewm@0: void sendVibratoMessage(float pitchBendSemitones, bool force = false); andrewm@0: andrewm@0: // ***** Member Variables ***** andrewm@0: andrewm@0: int vibratoState_; // Whether a vibrato gesture is currently detected andrewm@0: andrewm@0: timestamp_type rampBeginTime_; // If in a switching state, when does the transition begin? andrewm@0: float rampScaleValue_; // If in a switching state, what is the end point of the ramp? andrewm@0: timestamp_diff_type rampLength_; // If in a switching state, how long is the transition? andrewm@0: float lastCalculatedRampValue_; // Value of the ramp that was last calculated andrewm@0: andrewm@0: float onsetThresholdX_, onsetThresholdY_; // Thresholds for detecting vibrato (first extremum) andrewm@0: float onsetRatioX_, onsetRatioY_; // Thresholds for detection vibrato (second extremum) andrewm@0: timestamp_diff_type onsetTimeout_; // Timeout between first and second extrema andrewm@0: andrewm@0: float onsetLocationX_, onsetLocationY_; // Where the touch began at MIDI note on andrewm@0: float lastX_, lastY_; // Where the touch was at the last frame we received andrewm@0: int idOfCurrentTouch_; // Which touch ID we're currently following andrewm@0: timestamp_type lastTimestamp_; // When the last data point arrived andrewm@0: Node::size_type lastProcessedIndex_; // Index of the last filtered position sample we've handled andrewm@0: andrewm@0: timestamp_type lastZeroCrossingTimestamp_; // Timestamp of the last zero crossing andrewm@0: timestamp_diff_type lastZeroCrossingInterval_; // Interval between the last two zero-crossings of filtered distance andrewm@0: bool lastSampleWasPositive_; // Whether the last sample was > 0 andrewm@0: andrewm@0: bool foundFirstExtremum_; // Whether the first extremum has occurred andrewm@0: float firstExtremumX_, firstExtremumY_; // Where the first extremum occurred andrewm@0: timestamp_type firstExtremumTimestamp_; // Where the first extremum occurred andrewm@0: timestamp_type lastExtremumTimestamp_; // When the most recent extremum occurred andrewm@0: andrewm@0: float vibratoPrescaler_; // Parameter controlling prescaler before nonlinear scaling andrewm@0: float vibratoRangeSemitones_; // Amount of pitch bend in one direction at maximum andrewm@0: andrewm@0: float lastPitchBendSemitones_; // The last pitch bend value we sent out andrewm@0: andrewm@0: Node rawDistance_; // Distance from onset location andrewm@0: IIRFilterNode filteredDistance_; // Bandpass filtered finger motion andrewm@0: CriticalSection distanceAccessMutex_; // Mutex that protects the access buffer from changes andrewm@0: }; andrewm@0: andrewm@0: #endif /* defined(__touchkeys__TouchkeyVibratoMapping__) */