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: TouchkeyDevice.h: handles communication with the TouchKeys hardware andrewm@0: */ andrewm@0: andrewm@0: #ifndef TOUCHKEY_DEVICE_H andrewm@0: #define TOUCHKEY_DEVICE_H andrewm@0: andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@20: #ifndef _MSC_VER andrewm@20: #include andrewm@20: #endif andrewm@0: #include andrewm@0: #include andrewm@0: #include "../JuceLibraryCode/JuceHeader.h" andrewm@0: #include "PianoKeyboard.h" andrewm@0: #include "Osc.h" andrewm@0: #include "../Utility/TimestampSynchronizer.h" andrewm@0: #include "PianoKeyCalibrator.h" andrewm@0: #include "../Display/RawSensorDisplay.h" andrewm@0: andrewm@0: using namespace std; andrewm@0: andrewm@0: #define TOUCHKEY_MAX_FRAME_LENGTH 256 // Maximum data length in a single frame andrewm@0: #define ESCAPE_CHARACTER 0xFE // Indicates control sequence andrewm@0: andrewm@0: //#define TRANSMISSION_LENGTH_WHITE 9 andrewm@0: //#define TRANSMISSION_LENGTH_BLACK 8 andrewm@0: //#define TRANSMISSION_LENGTH_TOTAL (8*TRANSMISSION_LENGTH_WHITE + 5*TRANSMISSION_LENGTH_BLACK) andrewm@0: andrewm@0: const int kTransmissionLengthWhiteOldHardware = 9; andrewm@0: const int kTransmissionLengthBlackOldHardware = 8; andrewm@0: const int kTransmissionLengthWhiteNewHardware = 9; andrewm@0: const int kTransmissionLengthBlackNewHardware = 9; andrewm@0: const int kTransmissionLengthTotalOldHardware = (8 * kTransmissionLengthWhiteOldHardware + 5 * kTransmissionLengthBlackOldHardware); andrewm@0: const int kTransmissionLengthTotalNewHardware = (8 * kTransmissionLengthWhiteNewHardware + 5 * kTransmissionLengthBlackNewHardware); andrewm@0: andrewm@0: // Maximum integer values for different types of sliders andrewm@0: andrewm@0: //#define WHITE_MAX_VALUE 1280.0 // White keys, vertical (64 * 20) andrewm@0: //#define WHITE_MAX_H_VALUE 255.0 // Whtie keys, horizontal andrewm@0: //#define BLACK_MAX_VALUE 1024.0 // Black keys, vertical (64 * 16) andrewm@0: //#define SIZE_MAX_VALUE 255.0 // Max touch size for either key type andrewm@0: andrewm@0: const float kWhiteMaxYValueOldHardware = 1280.0; // White keys, vertical (64 * 20) andrewm@0: const float kWhiteMaxXValueOldHardware = 255.0; // White keys, horizontal (1 byte) andrewm@0: const float kBlackMaxYValueOldHardware = 1024.0; // Black keys, vertical (64 * 16) andrewm@0: const float kWhiteMaxYValueNewHardware = 2432.0; // White keys, vertical (128 * 19) andrewm@0: const float kWhiteMaxXValueNewHardware = 256.0; // White keys, horizontal (1 byte + 1 bit) andrewm@0: const float kBlackMaxYValueNewHardware = 1536.0; // Black keys, vertical (128 * 12) andrewm@0: const float kBlackMaxXValueNewHardware = 256.0; // Black keys, horizontal (1 byte + 1 bit) andrewm@0: andrewm@0: const float kSizeMaxValue = 255.0; andrewm@0: andrewm@0: enum { andrewm@0: kControlCharacterFrameBegin = 0x00, andrewm@0: kControlCharacterAck = 0x01, andrewm@0: kControlCharacterNak = 0x02, andrewm@0: kControlCharacterFrameError = 0xFD, andrewm@0: kControlCharacterFrameEnd = 0xFF andrewm@0: }; andrewm@0: andrewm@0: // Frame types for data sent over USB. The first byte following a frame start control sequence gives the type. andrewm@0: andrewm@0: enum { andrewm@0: kFrameTypeStatus = 0, // Status info: connected keys, current operating modes andrewm@0: kFrameTypeCentroid = 16, // Centroid data (default mode of operation) andrewm@0: kFrameTypeI2CResponse = 17, // Response from a specific I2C command andrewm@0: kFrameTypeRawKeyData = 18, // Raw data from the selected key andrewm@0: kFrameTypeAnalog = 19, // Analog data from Z-axis optical sensors andrewm@0: andrewm@0: kFrameTypeErrorMessage = 127, // Error message from controller andrewm@0: // These types are for incoming (computer -> us) data andrewm@0: kFrameTypeStartScanning = 128, // Start auto-scan andrewm@0: kFrameTypeStopScanning = 129, // Stop auto-scan andrewm@0: kFrameTypeSendI2CCommand = 130, // Send a specific I2C command andrewm@0: kFrameTypeResetDevices = 131, // Physically reset the system andrewm@0: kFrameTypeScanRate = 132, // Set the scan rate (in milliseconds) andrewm@0: kFrameTypeNoiseThreshold = 133, andrewm@0: kFrameTypeSensitivity = 134, andrewm@0: kFrameTypeSizeScaler = 135, andrewm@0: kFrameTypeMinimumSize = 136, andrewm@0: kFrameTypeSetEnabledKeys = 137, andrewm@0: kFrameTypeMonitorRawFromKey = 138, andrewm@0: kFrameTypeUpdateBaselines = 139, // Reinitialize baseline values andrewm@0: kFrameTypeRescanKeyboard = 140, // Rescan what keys are connected andrewm@53: kFrameTypeEncapsulatedMIDI = 167, // MIDI messages to pass to MIDI standalone firmware andrewm@0: kFrameTypeRGBLEDSetColors = 168, // Set RGBLEDs of given index to specific values andrewm@0: kFrameTypeRGBLEDAllOff = 169, // All LEDs off andrewm@0: kFrameTypeEnterISPMode = 192, andrewm@0: kFrameTypeEnterSelfProgramMode = 193 andrewm@0: }; andrewm@0: andrewm@0: enum { andrewm@0: kKeyColorWhite = 0, andrewm@0: kKeyColorBlack andrewm@0: }; andrewm@0: andrewm@0: enum { andrewm@0: kStatusFlagRunning = 0x01, andrewm@0: kStatusFlagRawMode = 0x02, andrewm@0: kStatusFlagHasI2C = 0x04, andrewm@0: kStatusFlagHasAnalog = 0x08, andrewm@0: kStatusFlagHasRGBLED = 0x10, andrewm@0: kStatusFlagComError = 0x80 andrewm@0: }; andrewm@0: andrewm@0: andrewm@0: const int kKeyColor[13] = { kKeyColorWhite, kKeyColorBlack, kKeyColorWhite, andrewm@0: kKeyColorBlack, kKeyColorWhite, kKeyColorWhite, kKeyColorBlack, andrewm@0: kKeyColorWhite, kKeyColorBlack, kKeyColorWhite, kKeyColorBlack, andrewm@0: kKeyColorWhite, kKeyColorWhite }; andrewm@0: andrewm@0: const int kWhiteKeyIndices[13] = { 0, -1, 1, -1, 2, 3, -1, 4, -1, 5, -1, 6, 7}; andrewm@0: andrewm@0: const unsigned char kCommandStatus[] = { ESCAPE_CHARACTER, kControlCharacterFrameBegin, kFrameTypeStatus, andrewm@0: ESCAPE_CHARACTER, kControlCharacterFrameEnd }; andrewm@0: const unsigned char kCommandStartScanning[] = { ESCAPE_CHARACTER, kControlCharacterFrameBegin, kFrameTypeStartScanning, andrewm@0: ESCAPE_CHARACTER, kControlCharacterFrameEnd }; andrewm@0: const unsigned char kCommandStopScanning[] = { ESCAPE_CHARACTER, kControlCharacterFrameBegin, kFrameTypeStopScanning, andrewm@0: ESCAPE_CHARACTER, kControlCharacterFrameEnd }; andrewm@0: andrewm@0: #define octaveNoteToIndex(octave, note) (100*octave + note) // Generate indices for containers andrewm@0: #define indexToOctave(index) (int)(index / 100) andrewm@0: #define indexToNote(index) (index % 100) andrewm@0: andrewm@0: const float kTouchkeyAnalogValueMax = 4095.0; // Maximum value any analog sample can take andrewm@0: andrewm@0: // This class implements device access to the touchkey hardware. andrewm@0: andrewm@0: class TouchkeyDevice /*: public OscHandler*/ andrewm@0: { andrewm@0: // ***** Class to implement the Juce thread ***** andrewm@0: private: andrewm@0: class DeviceThread : public Thread { andrewm@0: public: andrewm@0: DeviceThread(boost::function action, String name = "DeviceThread") andrewm@0: : Thread(name), actionFunction_(action) {} andrewm@0: andrewm@0: ~DeviceThread() {} andrewm@0: andrewm@0: void run() { andrewm@0: actionFunction_(this); andrewm@0: } andrewm@0: andrewm@0: private: andrewm@0: boost::function actionFunction_; andrewm@0: }; andrewm@0: andrewm@0: public: andrewm@0: class ControllerStatus { andrewm@0: public: andrewm@0: ControllerStatus() : connectedKeys(0) {} andrewm@0: ~ControllerStatus() { andrewm@0: if(connectedKeys != 0) andrewm@0: free(connectedKeys); andrewm@0: } andrewm@0: andrewm@0: int hardwareVersion; // Hardware version andrewm@0: int softwareVersionMajor; // Controller firmware major version andrewm@0: int softwareVersionMinor; // Controller firmware minor version andrewm@0: bool running; // Is the system currently gathering centroid data? andrewm@0: bool hasTouchSensors; // Whether the device has I2C touch sensors andrewm@0: bool hasAnalogSensors; // Whether the device has analog optical position sensors andrewm@0: bool hasRGBLEDs; // Whether the device has RGB LEDs for display andrewm@0: int octaves; // Number of octaves connected [two octaves per board] andrewm@0: int lowestHardwareNote; // Note number (0-12) of lowest connector or sensor on lowest board andrewm@0: unsigned int *connectedKeys;// Which keys are connected to each octave andrewm@0: }; andrewm@0: andrewm@0: class MultiKeySweep { andrewm@0: public: andrewm@0: andrewm@0: int sweepId; andrewm@0: int sweepOctave; andrewm@0: float sweepNote; andrewm@0: int keyCount; andrewm@0: int keyOctave[2]; andrewm@0: int keyNote[2]; andrewm@0: int keyTouchId[2]; andrewm@0: float keyPosition[2]; andrewm@0: }; andrewm@0: andrewm@0: // Structure to hold changes to RGB LEDs on relevant hardware andrewm@0: class RGBLEDUpdate { andrewm@0: public: andrewm@0: bool allLedsOff; // Set to true if all LEDs should turn off on indicated board andrewm@0: int midiNote; // MIDI note number to change andrewm@0: int red; // RGB color andrewm@0: int green; andrewm@0: int blue; andrewm@0: }; andrewm@0: andrewm@0: public: andrewm@0: // ***** Constructor ***** andrewm@0: TouchkeyDevice(PianoKeyboard& keyboard); andrewm@0: andrewm@0: // ***** Destructor ***** andrewm@0: ~TouchkeyDevice(); andrewm@0: andrewm@0: // ***** Device Management ***** andrewm@0: // Open a new device. Returns true on success andrewm@0: bool openDevice(const char * inputDevicePath); andrewm@0: void closeDevice(); andrewm@0: andrewm@0: // Start or stop the processing. startAutoGathering() returns andrewm@0: // true on success. andrewm@0: bool startAutoGathering(); andrewm@28: void stopAutoGathering(bool writeStopCommandToDevice = true); andrewm@0: andrewm@0: // Status query methods andrewm@23: bool isOpen(); andrewm@0: bool isAutoGathering() { return autoGathering_; } andrewm@0: int numberOfOctaves() { return numOctaves_; } andrewm@0: andrewm@0: // Ping the device, to see if it is ready to respond andrewm@0: bool checkIfDevicePresent(int millisecondsToWait); andrewm@0: andrewm@0: // Start collecting raw data from a given key andrewm@0: bool startRawDataCollection(int octave, int key, int mode, int scaler); andrewm@17: void rawDataChangeKeyAndMode(int octave, int key, int mode, int scaler); andrewm@0: andrewm@0: // ***** RGB LED updates ***** andrewm@0: void rgbledSetColor(const int midiNote, const float red, const float green, const float blue); andrewm@0: void rgbledSetColorHSV(const int midiNote, const float hue, const float saturation, const float value); andrewm@0: void rgbledAllOff(); andrewm@0: andrewm@0: // ***** Device Parameters ***** andrewm@0: andrewm@0: // Set the scan interval in milliseconds andrewm@0: bool setScanInterval(int intervalMilliseconds); andrewm@0: andrewm@0: // Key parameters. Setting octave or key to -1 means all octaves or all keys, respectively. andrewm@0: bool setKeySensitivity(int octave, int key, int value); andrewm@0: bool setKeyCentroidScaler(int octave, int key, int value); andrewm@0: bool setKeyMinimumCentroidSize(int octave, int key, int value); andrewm@0: bool setKeyNoiseThreshold(int octave, int key, int value); andrewm@19: bool setKeyUpdateBaseline(int octave, int key); andrewm@0: andrewm@0: // Jump to device internal bootloader andrewm@0: void jumpToBootloader(); andrewm@0: andrewm@0: // ***** Calibration Methods ***** andrewm@0: andrewm@0: // Return whether or not the controller has been calibrated, and whether it's currently calibrating andrewm@0: bool isCalibrated() { return isCalibrated_; } andrewm@0: bool calibrationInProgress() { return calibrationInProgress_; } andrewm@0: andrewm@0: // Start: begin calibrating; finish: end and save results; abort: end and discard results andrewm@0: void calibrationStart(std::vector* keysToCalibrate); andrewm@0: void calibrationFinish(); andrewm@0: void calibrationAbort(); andrewm@0: void calibrationClear(); andrewm@0: andrewm@0: bool calibrationSaveToFile(std::string const& filename); andrewm@0: bool calibrationLoadFromFile(std::string const& filename); andrewm@0: andrewm@0: // ***** Data Logging ***** andrewm@0: void createLogFiles(string keyTouchLogFilename, string analogLogFilename, string path); andrewm@0: void closeLogFile(); andrewm@0: void startLogging(); andrewm@0: void stopLogging(); andrewm@0: andrewm@0: // ***** Debugging and Utility ***** andrewm@0: andrewm@0: // Set logging level andrewm@0: void setVerboseLevel(int v) { verbose_ = v; } andrewm@0: void setTransmitRawData(bool raw) { sendRawOscMessages_ = raw; } andrewm@0: bool transmitRawDataEnabled() { return sendRawOscMessages_; } andrewm@0: andrewm@0: // Conversion between touchkey # and MIDI note andrewm@0: int lowestMidiNote() { return lowestMidiNote_; } andrewm@48: int highestMidiNote() { return lowestMidiNote_ + 12*numOctaves_ + lowestNotePerOctave_; } andrewm@0: int lowestKeyPresentMidiNote() { return lowestKeyPresentMidiNote_; } // What is the lowest key actually connected? andrewm@0: void setLowestMidiNote(int note); andrewm@48: int octaveKeyToMidi(int octave, int key); andrewm@0: andrewm@0: // Sensor data display andrewm@0: void setSensorDisplay(RawSensorDisplay *display) { sensorDisplay_ = display; } andrewm@0: andrewm@0: // ***** Run Loop Functions ***** andrewm@0: void ledUpdateLoop(DeviceThread *thread); andrewm@0: void runLoop(DeviceThread *thread); andrewm@0: void rawDataRunLoop(DeviceThread *thread); andrewm@0: andrewm@0: // for debugging andrewm@0: void testStopLeds() { ledShouldStop_ = true; } andrewm@0: andrewm@0: private: andrewm@0: // Read and parse new data from the device, splitting out by frame type andrewm@0: void processFrame(unsigned char * const frame, int length); andrewm@0: andrewm@0: // Specific data type parsing andrewm@0: void processCentroidFrame(unsigned char * const buffer, const int bufferLength); andrewm@0: int processKeyCentroid(int frame,int octave, int key, timestamp_type timestamp, unsigned char * buffer, int maxLength); andrewm@0: void processAnalogFrame(unsigned char * const buffer, const int bufferLength); andrewm@0: void processRawDataFrame(unsigned char * const buffer, const int bufferLength); andrewm@0: bool processStatusFrame(unsigned char * buffer, int maxLength, ControllerStatus *status); andrewm@0: void processI2CResponseFrame(unsigned char * const buffer, const int bufferLength); andrewm@0: void processErrorMessageFrame(unsigned char * const buffer, const int bufferLength); andrewm@0: andrewm@0: // Helper methods for centroid processing andrewm@0: //pair > matchClosestPoints(float* oldPoints, float *newPoints, float count, andrewm@0: // int oldIndex, set& availableNewPoints, float currentTotalDistance); andrewm@0: void processTwoFingerGestures(int octave, int key, KeyTouchFrame& previousPosition, KeyTouchFrame& newPosition); andrewm@0: void processThreeFingerGestures(int octave, int key, KeyTouchFrame& previousPosition, KeyTouchFrame& newPosition); andrewm@0: andrewm@0: // Utility method for parsing multi-key gestures andrewm@0: pair whiteKeyAbove(int octave, int note); andrewm@0: andrewm@17: // Write the commands to prepare a given key for raw data collection andrewm@17: void rawDataPrepareCollection(int octave, int key, int mode, int scaler); andrewm@17: andrewm@0: // After writing a command, check whether it was acknolwedged by the controller andrewm@0: bool checkForAck(int timeoutMilliseconds); andrewm@0: andrewm@0: // Utility method for debugging andrewm@0: void hexDump(ostream& str, unsigned char * buffer, int length); andrewm@17: andrewm@0: // Internal calibration methods andrewm@0: void calibrationInit(int numberOfCalibrators); andrewm@0: void calibrationDeinit(); andrewm@0: andrewm@0: // Set RGB LED color (for piano scanner boards) andrewm@0: bool internalRGBLEDSetColor(const int device, const int led, const int red, const int green, const int blue); andrewm@0: bool internalRGBLEDAllOff(); // RGB LEDs off andrewm@0: int internalRGBLEDMIDIToBoardNumber(const int midiNote); // Get board number for MIDI note andrewm@0: int internalRGBLEDMIDIToLEDNumber(const int midiNote); // Get LED number for MIDI note andrewm@22: andrewm@22: // Device low-level access methods andrewm@22: long deviceRead(char *buffer, unsigned int count); andrewm@22: int deviceWrite(char *buffer, unsigned int count); andrewm@22: void deviceFlush(bool bothDirections); andrewm@22: void deviceDrainOutput(); andrewm@0: andrewm@0: private: andrewm@0: PianoKeyboard& keyboard_; // Main keyboard controller andrewm@0: andrewm@23: #ifdef _MSC_VER andrewm@23: HANDLE serialHandle_; // Serial port handle andrewm@23: #else andrewm@0: int device_; // File descriptor andrewm@23: #endif andrewm@0: DeviceThread ioThread_; // Thread that handles the communication from the device andrewm@0: DeviceThread rawDataThread_;// Thread that handles raw data collection andrewm@0: //CriticalSection ioMutex_; // Mutex synchronizing access between internal and external threads andrewm@0: bool autoGathering_; // Whether auto-scanning is enabled andrewm@0: volatile bool shouldStop_; // Communication variable between threads andrewm@0: bool sendRawOscMessages_; // Whether we should transmit the raw frame data by OSC andrewm@0: int verbose_; // Logging level andrewm@0: int numOctaves_; // Number of connected octaves (determined from device) andrewm@0: int lowestMidiNote_; // MIDI note number for the lowest C on the lowest octave andrewm@0: int lowestKeyPresentMidiNote_; // MIDI note number for the lowest key actually attached andrewm@0: int updatedLowestMidiNote_; // Lowest MIDI note if changed; held separately for thread sync andrewm@48: int lowestNotePerOctave_; // Note which starts each octave, for non C-to-C keyboards andrewm@0: set keysPresent_; // Which keys (octave and note) are present on this device? andrewm@0: int deviceSoftwareVersion_; // Which version of the device we're talking to andrewm@0: int deviceHardwareVersion_; // Which version of the device hardware is running andrewm@0: int expectedLengthWhite_; // How long the white key data blocks are andrewm@0: int expectedLengthBlack_; // How long the black key data blocks are andrewm@0: float whiteMaxX_, whiteMaxY_; // Maximum sensor values for white keys andrewm@0: float blackMaxX_, blackMaxY_; // Maximum sensor values for black keys andrewm@0: andrewm@0: // Frame counter for analog data, to detect dropped frames andrewm@0: unsigned int analogLastFrame_[4]; // Max 4 boards andrewm@0: andrewm@0: // Synchronization between frame time and system timestamp, allowing interaction andrewm@0: // with other simultaneous streams using different clocks. Also save the last timestamp andrewm@0: // we've processed to other functions can access it. andrewm@0: TimestampSynchronizer timestampSynchronizer_; andrewm@0: timestamp_type lastTimestamp_; andrewm@0: andrewm@0: // For raw data collection, this information keeps track of which key we're reading andrewm@17: bool rawDataShouldChangeMode_; andrewm@0: int rawDataCurrentOctave_, rawDataCurrentKey_; andrewm@17: int rawDataCurrentMode_, rawDataCurrentScaler_; andrewm@0: andrewm@0: // ***** RGB LED management ***** andrewm@0: bool deviceHasRGBLEDs_; // Whether the device has RGB LEDs andrewm@0: DeviceThread ledThread_; // Thread that handles LED updates (communication to the device) andrewm@0: volatile bool ledShouldStop_; // testing andrewm@0: deque ledUpdateQueue_; // Queue that holds new LED messages to be sent to device andrewm@0: andrewm@0: // ***** Calibration ***** andrewm@0: bool isCalibrated_; andrewm@0: bool calibrationInProgress_; andrewm@0: andrewm@0: PianoKeyCalibrator** keyCalibrators_; // Calibration information for each key andrewm@0: int keyCalibratorsLength_; // How many calibrators andrewm@0: andrewm@0: // ***** Logging ***** andrewm@0: ofstream keyTouchLog_; andrewm@0: ofstream analogLog_; andrewm@0: bool logFileCreated_; andrewm@0: bool loggingActive_; andrewm@0: andrewm@0: // ***** Sensor Data Display (for debugging) ***** andrewm@0: RawSensorDisplay *sensorDisplay_; andrewm@0: }; andrewm@0: andrewm@0: #endif /* TOUCHKEY_DEVICE_H */