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: PianoKey.h: handles the operation of a single key on the keyboard, andrewm@0: including fusing touch and MIDI data. Also has hooks for continuous andrewm@0: key angle. andrewm@0: */ andrewm@0: andrewm@0: andrewm@0: #ifndef KEYCONTROL_PIANOKEY_H andrewm@0: #define KEYCONTROL_PIANOKEY_H andrewm@0: andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include "../JuceLibraryCode/JuceHeader.h" andrewm@0: #include "../Utility/Node.h" andrewm@0: #include "PianoTypes.h" andrewm@0: #include "KeyIdleDetector.h" andrewm@0: #include "KeyPositionTracker.h" andrewm@0: #include "KeyTouchFrame.h" andrewm@0: #include "../Utility/Scheduler.h" andrewm@0: #include "../Utility/IIRFilter.h" andrewm@0: andrewm@0: const unsigned int kPianoKeyStateBufferLength = 20; // How many previous states to save andrewm@0: const unsigned int kPianoKeyIdleBufferLength = 10; // How many idle/active transitions to save andrewm@0: const unsigned int kPianoKeyPositionTrackerBufferLength = 30; // How many state histories to save andrewm@0: const key_position kPianoKeyDefaultIdleActivityThreshold = scale_key_position(.020); andrewm@0: const key_position kPianoKeyDefaultIdlePositionThreshold = scale_key_position(.05); andrewm@0: const int kPianoKeyDefaultIdleCounter = 20; andrewm@0: const timestamp_diff_type kPianoKeyDefaultTouchTimeoutInterval = microseconds_to_timestamp(0); // was 20000 andrewm@0: const timestamp_diff_type kPianoKeyGuiUpdateInterval = microseconds_to_timestamp(15000); // How frequently to update the position display andrewm@0: andrewm@0: // Possible key states andrewm@0: enum { andrewm@0: kKeyStateToBeInitialized = -1, andrewm@0: kKeyStateUnknown = 0, andrewm@0: kKeyStateDisabled, andrewm@0: kKeyStateIdle, andrewm@0: kKeyStateActive andrewm@0: }; andrewm@0: andrewm@0: // Possible touch events andrewm@0: enum { andrewm@0: kTouchEventIdle = 0, andrewm@0: kTouchEventAdd, andrewm@0: kTouchEventRemove andrewm@0: }; andrewm@0: andrewm@0: typedef int key_state; andrewm@0: andrewm@0: class PianoKeyboard; andrewm@0: class MidiKeyboardSegment; andrewm@0: andrewm@0: /* andrewm@0: * PianoKey andrewm@0: * andrewm@0: * This class holds the buffer and state information for a single piano key, andrewm@0: * with methods to manage its status. andrewm@0: */ andrewm@0: andrewm@0: class PianoKey : public TriggerDestination { andrewm@0: private: andrewm@0: // ***** Internal Classes ***** andrewm@0: andrewm@0: // Data on key touch events: what, when and where andrewm@0: struct KeyTouchEvent { andrewm@0: int type; andrewm@0: timestamp_type timestamp; andrewm@0: KeyTouchFrame frame; andrewm@0: }; andrewm@0: andrewm@0: public: andrewm@0: // ***** Constructors ***** andrewm@0: andrewm@0: PianoKey(PianoKeyboard& keyboard, int noteNumber, int bufferLength); andrewm@0: //PianoKey(PianoKey const& obj); andrewm@0: andrewm@0: // ***** Destructor ***** andrewm@0: andrewm@0: ~PianoKey(); andrewm@0: andrewm@0: // ***** Access Methods ***** andrewm@0: andrewm@0: Node& buffer() { return positionBuffer_; } andrewm@0: andrewm@0: // ***** Control Methods ***** andrewm@0: // andrewm@0: // Force changes in the key state (e.g. to resolve stuck notes) andrewm@0: andrewm@0: // Enable or disable a key from generating events andrewm@0: void disable(); andrewm@0: void enable(); andrewm@0: andrewm@0: // Force the key to the Idle state, provided it is enabled andrewm@0: void forceIdle(); andrewm@0: andrewm@0: // Clear any previous state, go back to initial state andrewm@0: void reset(); andrewm@0: andrewm@0: void insertSample(key_position pos, timestamp_type ts); andrewm@0: andrewm@0: // ***** Trigger Methods ***** andrewm@0: // andrewm@0: // This will be called by positionBuffer_ on each new sample. Examine each sample to see andrewm@0: // whether the key is idle or not. andrewm@0: andrewm@0: void triggerReceived(TriggerSource* who, timestamp_type timestamp); andrewm@0: andrewm@0: // ***** MIDI Methods ***** andrewm@0: // andrewm@0: // If MIDI input is used to control this note, the controller should call these functions andrewm@0: andrewm@0: void midiNoteOn(MidiKeyboardSegment *who, int velocity, int channel, timestamp_type timestamp); andrewm@0: void midiNoteOff(MidiKeyboardSegment *who, timestamp_type timestamp); andrewm@0: void midiAftertouch(MidiKeyboardSegment *who, int value, timestamp_type timestamp); andrewm@0: andrewm@0: bool midiNoteIsOn() { return midiNoteIsOn_; } andrewm@0: int midiVelocity() { return midiVelocity_; } andrewm@0: int midiChannel() { return midiChannel_; } andrewm@0: void changeMidiChannel(int newChannel) { midiChannel_ = newChannel; } andrewm@0: int midiOutputPort() { return midiOutputPort_; } andrewm@0: void changeMidiOutputPort(int newPort) { midiOutputPort_ = newPort; } andrewm@0: andrewm@0: // ***** Touch Methods ***** andrewm@0: // andrewm@0: // If touchkeys are used, the controller uses these functions to provide data andrewm@0: // touchInsertFrame() implies touch is active if not already. andrewm@0: andrewm@0: void touchInsertFrame(KeyTouchFrame& newFrame, timestamp_type timestamp); andrewm@0: void touchOff(timestamp_type timestamp); andrewm@0: bool touchIsActive() { return touchIsActive_; } andrewm@0: void setTouchSensorsPresent(bool present) { touchSensorsArePresent_ = present; } andrewm@0: andrewm@0: // This function is called on a timer when the key receives MIDI data before touch data andrewm@0: // and wants to wait to integrate the two. If the touch data doesn't materialize, this function andrewm@0: // is called by the scheduler. andrewm@0: timestamp_type touchTimedOut(); andrewm@0: andrewm@0: private: andrewm@0: // ***** MIDI Methods (private) ***** andrewm@0: andrewm@0: // This method does the real work of midiNoteOn(), and might be called from it directly andrewm@0: // or after a delay. andrewm@0: void midiNoteOnHelper(MidiKeyboardSegment *who); andrewm@0: andrewm@0: // ***** Touch Methods (private) ***** andrewm@0: andrewm@0: std::pair > touchMatchClosestPoints(const float* oldPoints, const float *newPoints, float count, andrewm@0: int oldIndex, std::set& availableNewPoints, float currentTotalDistance); andrewm@0: void touchAdd(const KeyTouchFrame& frame, int index, timestamp_type timestamp); andrewm@0: void touchRemove(const KeyTouchFrame& frame, int idRemoved, int remainingCount, timestamp_type timestamp); andrewm@0: void touchMultiFingerGestures(const KeyTouchFrame& lastFrame, const KeyTouchFrame& newFrame, timestamp_type timestamp); andrewm@0: andrewm@0: // ***** State Machine Methods ***** andrewm@0: // andrewm@0: // This class maintains a current state that determines its response to the incoming andrewm@0: // key position data. andrewm@0: andrewm@0: void changeState(key_state newState); andrewm@0: void changeState(key_state newState, timestamp_type timestamp); andrewm@0: andrewm@0: void terminateActivity(); andrewm@0: andrewm@0: // ***** Member Variables ***** andrewm@0: andrewm@0: // Reference back to the keyboard which centralizes control andrewm@0: PianoKeyboard& keyboard_; andrewm@0: andrewm@0: // Identity of the key (MIDI note number) andrewm@0: int noteNumber_; andrewm@0: andrewm@0: // --- Data related to MIDI --- andrewm@0: andrewm@0: bool midiNoteIsOn_; // Whether this note is currently active from MIDI andrewm@0: int midiChannel_; // MIDI channel currently associated with this note andrewm@0: int midiOutputPort_; // Which port MIDI output for this note goes to andrewm@0: int midiVelocity_; // Velocity of last MIDI onset andrewm@0: Node midiAftertouch_; // Aftertouch history on this note, if any andrewm@0: andrewm@0: // Timestamps for the most recent MIDI note on and note off events andrewm@0: timestamp_type midiOnTimestamp_, midiOffTimestamp_; andrewm@0: andrewm@0: // --- Data related to continuous key position --- andrewm@0: andrewm@0: Node positionBuffer_; // Buffer that holds the key positions andrewm@0: KeyIdleDetector idleDetector_; // Detector for whether the key is still or moving andrewm@0: KeyPositionTracker positionTracker_; // Object to track the various active states of the key andrewm@0: timestamp_type timeOfLastGuiUpdate_; // How long it's been since the last key position GUI call andrewm@0: timestamp_type timeOfLastDebugPrint_; // TESTING andrewm@0: andrewm@0: Node stateBuffer_; // State history andrewm@0: key_state state_; // Current state of the key (see enum above) andrewm@0: CriticalSection stateMutex_; // Use this to synchronize changes of state andrewm@0: andrewm@0: //IIRFilterNode testFilter_; // Filter the raw key position data, for testing andrewm@0: andrewm@0: // --- Data related to surface touches --- andrewm@0: andrewm@0: bool touchSensorsArePresent_; // Whether touch sensitivity exists on this key andrewm@0: bool touchIsActive_; // Whether the user is currently touching the key andrewm@0: Node touchBuffer_; // Buffer that holds touchkey frames andrewm@0: std::multimap touchEvents_; // Mapping from touch number to event andrewm@0: bool touchIsWaiting_; // Whether we're waiting for a touch to occur andrewm@0: MidiKeyboardSegment *touchWaitingSource_; // Who we're waiting from a touch for andrewm@0: timestamp_type touchWaitingTimestamp_; // When the timeout will occur andrewm@0: timestamp_diff_type touchTimeoutInterval_; // How long to wait for a touch before timing out andrewm@0: andrewm@0: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PianoKey) andrewm@0: }; andrewm@0: andrewm@0: #endif /* KEYCONTROL_PIANOKEY_H */