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.cpp: handles communication with the TouchKeys hardware andrewm@0: */ andrewm@0: andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include "TouchkeyDevice.h" andrewm@0: andrewm@0: const char* kKeyNames[13] = {"C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B ", "c "}; andrewm@0: andrewm@0: // Constructor andrewm@0: andrewm@0: TouchkeyDevice::TouchkeyDevice(PianoKeyboard& keyboard) andrewm@23: : keyboard_(keyboard), andrewm@23: #ifdef _MSC_VER andrewm@23: serialHandle_(INVALID_HANDLE_VALUE), andrewm@23: #else andrewm@23: device_(-1), andrewm@23: #endif andrewm@0: ioThread_(boost::bind(&TouchkeyDevice::runLoop, this, _1), "TouchKeyDevice::ioThread"), andrewm@0: rawDataThread_(boost::bind(&TouchkeyDevice::rawDataRunLoop, this, _1), "TouchKeyDevice::rawDataThread"), andrewm@0: autoGathering_(false), shouldStop_(false), sendRawOscMessages_(false), andrewm@28: verbose_(0), numOctaves_(0), lowestMidiNote_(48), lowestKeyPresentMidiNote_(48), andrewm@48: updatedLowestMidiNote_(48), lowestNotePerOctave_(0), andrewm@48: deviceSoftwareVersion_(-1), deviceHardwareVersion_(-1), andrewm@0: expectedLengthWhite_(kTransmissionLengthWhiteNewHardware), andrewm@0: expectedLengthBlack_(kTransmissionLengthBlackNewHardware), deviceHasRGBLEDs_(false), andrewm@0: ledThread_(boost::bind(&TouchkeyDevice::ledUpdateLoop, this, _1), "TouchKeyDevice::ledThread"), andrewm@0: isCalibrated_(false), calibrationInProgress_(false), andrewm@0: keyCalibrators_(0), keyCalibratorsLength_(0), sensorDisplay_(0) andrewm@0: { andrewm@0: // Tell the piano keyboard class how to call us back andrewm@0: keyboard_.setTouchkeyDevice(this); andrewm@0: andrewm@0: // Initialize the frame -> timestamp synchronization. Frame interval is nominally 1ms, andrewm@0: // but this class helps us find the actual rate which might drift slightly, and it keeps andrewm@0: // the time stamps of each data point in sync with other streams. andrewm@0: timestampSynchronizer_.initialize(Time::getMillisecondCounterHiRes(), keyboard_.schedulerCurrentTimestamp()); andrewm@0: timestampSynchronizer_.setNominalSampleInterval(.001); andrewm@0: timestampSynchronizer_.setFrameModulus(65536); andrewm@0: andrewm@0: for(int i = 0; i < 4; i++) andrewm@0: analogLastFrame_[i] = 0; andrewm@0: andrewm@0: logFileCreated_ = false; andrewm@0: loggingActive_ = false; andrewm@0: } andrewm@0: andrewm@0: andrewm@0: // ------------------------------------------------------ andrewm@0: // create a new MIDI log file, ready to have data written to it andrewm@0: void TouchkeyDevice::createLogFiles(string keyTouchLogFilename, string analogLogFilename, string path) andrewm@0: { andrewm@0: if (path.compare("") != 0) andrewm@0: { andrewm@0: path = path + "/"; andrewm@0: } andrewm@0: andrewm@0: keyTouchLogFilename = path + keyTouchLogFilename; andrewm@0: analogLogFilename = path + analogLogFilename; andrewm@0: andrewm@0: char *fileName = (char*)keyTouchLogFilename.c_str(); andrewm@0: andrewm@0: // create output file for key touch andrewm@0: keyTouchLog_.open (fileName, ios::out | ios::binary); andrewm@0: keyTouchLog_.seekp(0); andrewm@0: andrewm@0: fileName = (char*)analogLogFilename.c_str(); andrewm@0: andrewm@0: // create output file for analog data andrewm@0: analogLog_.open (fileName, ios::out | ios::binary); andrewm@0: analogLog_.seekp(0); andrewm@0: andrewm@0: // indicate that we have created a log file (so we can close it later) andrewm@0: logFileCreated_ = true; andrewm@0: } andrewm@0: andrewm@0: // ------------------------------------------------------ andrewm@0: // close the log file andrewm@0: void TouchkeyDevice::closeLogFile() andrewm@0: { andrewm@0: if (logFileCreated_) andrewm@0: { andrewm@0: keyTouchLog_.close(); andrewm@0: analogLog_.close(); andrewm@0: logFileCreated_ = false; andrewm@0: } andrewm@0: loggingActive_ = false; andrewm@0: } andrewm@0: andrewm@0: // ------------------------------------------------------ andrewm@0: // start logging midi data andrewm@0: void TouchkeyDevice::startLogging() andrewm@0: { andrewm@0: loggingActive_ = true; andrewm@0: } andrewm@0: andrewm@0: // ------------------------------------------------------ andrewm@0: // stop logging midi data andrewm@0: void TouchkeyDevice::stopLogging() andrewm@0: { andrewm@0: loggingActive_ = false; andrewm@0: } andrewm@0: andrewm@0: // Open the touchkey device (a USB CDC device). Returns true on success. andrewm@0: andrewm@0: bool TouchkeyDevice::openDevice(const char * inputDevicePath) { andrewm@0: // If the device is already open, close it andrewm@23: if(isOpen()) andrewm@0: closeDevice(); andrewm@0: andrewm@0: // Open the device andrewm@20: #ifdef _MSC_VER andrewm@23: // Open the serial port andrewm@28: serialHandle_ = CreateFile(inputDevicePath, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); andrewm@28: if(serialHandle_ == INVALID_HANDLE_VALUE) { andrewm@28: Logger::writeToLog("Unable to open serial port " + String(inputDevicePath)); andrewm@28: return false; andrewm@28: } andrewm@28: andrewm@28: // Set some serial parameters, though they don't actually affect the operation andrewm@28: // of the port since it is all native USB andrewm@28: DCB serialParams = { 0 }; andrewm@28: serialParams.DCBlength = sizeof(serialParams); andrewm@28: andrewm@28: if(!BuildCommDCBA("baud=1000000 data=8 parity=N stop=1 dtr=on rts=on", &serialParams)) { andrewm@28: Logger::writeToLog("Unable to create port settings\n"); andrewm@28: CloseHandle(serialHandle_); andrewm@28: serialHandle_ = INVALID_HANDLE_VALUE; andrewm@28: return false; andrewm@28: } andrewm@28: andrewm@28: if(!SetCommState(serialHandle_, &serialParams)) { andrewm@28: Logger::writeToLog("Unable to set comm state\n"); andrewm@28: CloseHandle(serialHandle_); andrewm@28: serialHandle_ = INVALID_HANDLE_VALUE; andrewm@28: return false; andrewm@28: } andrewm@28: andrewm@28: // Set timeouts andrewm@28: COMMTIMEOUTS timeout = { 0 }; andrewm@28: timeout.ReadIntervalTimeout = MAXDWORD; andrewm@28: timeout.ReadTotalTimeoutConstant = 0; andrewm@28: timeout.ReadTotalTimeoutMultiplier = 0; andrewm@28: timeout.WriteTotalTimeoutConstant = 0; andrewm@28: timeout.WriteTotalTimeoutMultiplier = 0; andrewm@28: andrewm@23: if(!SetCommTimeouts(serialHandle_, &timeout)) { andrewm@23: Logger::writeToLog("Unable to set timeouts\n"); andrewm@23: CloseHandle(serialHandle_); andrewm@23: serialHandle_ = INVALID_HANDLE_VALUE; andrewm@23: return false; andrewm@23: } andrewm@20: #else andrewm@0: device_ = open(inputDevicePath, O_RDWR | O_NOCTTY | O_NDELAY); andrewm@20: andrewm@21: if(device_ < 0) { andrewm@0: return false; andrewm@21: } andrewm@20: #endif andrewm@0: return true; andrewm@0: } andrewm@0: andrewm@0: // Close the touchkey serial device andrewm@0: void TouchkeyDevice::closeDevice() { andrewm@23: if(!isOpen()) andrewm@0: return; andrewm@0: andrewm@0: stopAutoGathering(); andrewm@0: keysPresent_.clear(); andrewm@20: andrewm@20: #ifdef _MSC_VER andrewm@23: CloseHandle(serialHandle_); andrewm@23: serialHandle_ = INVALID_HANDLE_VALUE; andrewm@20: #else andrewm@0: close(device_); andrewm@0: device_ = -1; andrewm@20: #endif andrewm@0: } andrewm@0: andrewm@23: bool TouchkeyDevice::isOpen() { andrewm@23: #ifdef _MSC_VER andrewm@23: return serialHandle_ != INVALID_HANDLE_VALUE; andrewm@23: #else andrewm@23: return device_ >= 0; andrewm@23: #endif andrewm@23: } andrewm@23: andrewm@0: // Check if the device is present and ready to respond. If status is not null, store the current andrewm@0: // controller status information. andrewm@0: andrewm@0: bool TouchkeyDevice::checkIfDevicePresent(int millisecondsToWait) { andrewm@0: //struct timeval startTime, currentTime; andrewm@0: double startTime, currentTime; andrewm@0: unsigned char ch; andrewm@0: bool controlSeq = false, startingFrame = false; andrewm@0: andrewm@23: if(!isOpen()) andrewm@0: return false; andrewm@22: deviceFlush(false); andrewm@22: andrewm@22: if(deviceWrite((char*)kCommandStatus, 5) < 0) { // Write status command andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "ERROR: unable to write status command. errno = " << errno << endl; andrewm@0: return false; andrewm@0: } andrewm@22: andrewm@20: andrewm@0: // Wait the specified amount of time for a response before giving up andrewm@0: startTime = Time::getMillisecondCounterHiRes(); andrewm@0: currentTime = startTime; andrewm@0: andrewm@0: while(currentTime - startTime < (double)millisecondsToWait) { andrewm@22: long count = deviceRead((char *)&ch, 1); andrewm@0: andrewm@0: if(count < 0) { // Check if an error occurred on read andrewm@0: if(errno != EAGAIN) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Unable to read from device (error " << errno << "). Aborting.\n"; andrewm@0: return false; andrewm@0: } andrewm@0: } andrewm@0: else if(count > 0) { // Data received andrewm@0: // Wait for a frame back that is of type status. We don't even care what the andrewm@0: // status is at this point, just that we got something. andrewm@0: andrewm@0: if(controlSeq) { andrewm@0: controlSeq = false; andrewm@0: if(ch == kControlCharacterFrameBegin) andrewm@0: startingFrame = true; andrewm@0: else andrewm@0: startingFrame = false; andrewm@0: } andrewm@0: else { andrewm@0: if(ch == ESCAPE_CHARACTER) { andrewm@0: controlSeq = true; andrewm@0: } andrewm@0: else if(startingFrame) { andrewm@0: if(ch == kFrameTypeStatus) { andrewm@0: ControllerStatus status; andrewm@0: unsigned char statusBuf[TOUCHKEY_MAX_FRAME_LENGTH]; andrewm@0: int statusBufLength = 0; andrewm@0: bool frameError = false; andrewm@0: andrewm@0: // Gather and parse the status frame andrewm@0: andrewm@0: while(currentTime - startTime < millisecondsToWait) { andrewm@22: count = deviceRead((char *)&ch, 1); andrewm@22: andrewm@0: if(count == 0) andrewm@0: continue; andrewm@0: if(count < 0) { andrewm@0: if(errno != EAGAIN && verbose_ >= 1) { // EAGAIN just means no data was available andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Unable to read from device (error " << errno << "). Aborting.\n"; andrewm@0: return false; andrewm@0: } andrewm@0: andrewm@0: continue; andrewm@0: } andrewm@0: andrewm@0: if(controlSeq) { andrewm@0: controlSeq = false; andrewm@0: if(ch == kControlCharacterFrameEnd) { // frame finished? andrewm@0: break; andrewm@0: } andrewm@0: else if(ch == kControlCharacterFrameError) // device telling us about an internal comm error andrewm@0: frameError = true; andrewm@0: else if(ch == ESCAPE_CHARACTER) { // double-escape means a literal escape character andrewm@0: statusBuf[statusBufLength++] = (unsigned char)ch; andrewm@0: if(statusBufLength >= TOUCHKEY_MAX_FRAME_LENGTH) { andrewm@0: frameError = true; andrewm@0: break; andrewm@0: } andrewm@0: } andrewm@0: else if(ch == kControlCharacterNak && verbose_ >= 1) { andrewm@0: cout << "Warning: received NAK\n"; andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: if(ch == ESCAPE_CHARACTER) andrewm@0: controlSeq = true; andrewm@0: else { andrewm@0: statusBuf[statusBufLength++] = (unsigned char)ch; andrewm@0: if(statusBufLength >= TOUCHKEY_MAX_FRAME_LENGTH) { andrewm@0: frameError = true; andrewm@0: break; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: currentTime = Time::getMillisecondCounterHiRes(); andrewm@0: } andrewm@0: andrewm@0: if(frameError) { andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: device present, but frame error received trying to get status.\n"; andrewm@0: } andrewm@0: else if(processStatusFrame(statusBuf, statusBufLength, &status)) { andrewm@0: // Clear keys present in preparation to read new list of keys andrewm@0: keysPresent_.clear(); andrewm@0: andrewm@0: numOctaves_ = status.octaves; andrewm@0: deviceSoftwareVersion_ = status.softwareVersionMajor; andrewm@0: deviceHardwareVersion_ = status.hardwareVersion; andrewm@0: deviceHasRGBLEDs_ = status.hasRGBLEDs; andrewm@0: lowestKeyPresentMidiNote_ = 127; andrewm@0: andrewm@0: if(verbose_ >= 1) { andrewm@0: cout << endl << "Found Device: Hardware Version " << status.hardwareVersion; andrewm@0: cout << " Software Version " << status.softwareVersionMajor << "." << status.softwareVersionMinor; andrewm@0: cout << endl << " " << status.octaves << " octaves connected" << endl; andrewm@0: } andrewm@7: andrewm@0: for(int i = 0; i < status.octaves; i++) { andrewm@0: bool foundKey = false; andrewm@0: andrewm@0: if(verbose_ >= 1) cout << " Octave " << i << ": "; andrewm@0: for(int j = 0; j < 13; j++) { andrewm@0: if(status.connectedKeys[i] & (1<= 1) cout << kKeyNames[j] << " "; andrewm@0: keysPresent_.insert(octaveNoteToIndex(i, j)); andrewm@0: foundKey = true; andrewm@0: if(octaveKeyToMidi(i, j) < lowestKeyPresentMidiNote_) andrewm@0: lowestKeyPresentMidiNote_ = octaveKeyToMidi(i, j); andrewm@0: } andrewm@0: else { andrewm@0: if(verbose_ >= 1) cout << "- "; andrewm@0: } andrewm@0: andrewm@0: } andrewm@0: andrewm@7: if(verbose_ >= 1) cout << endl; andrewm@0: } andrewm@0: andrewm@0: // Hardware version determines whether all keys have XY or not andrewm@0: if(status.hardwareVersion >= 2) { andrewm@0: expectedLengthWhite_ = kTransmissionLengthWhiteNewHardware; andrewm@0: expectedLengthBlack_ = kTransmissionLengthBlackNewHardware; andrewm@0: whiteMaxX_ = kWhiteMaxXValueNewHardware; andrewm@0: whiteMaxY_ = kWhiteMaxYValueNewHardware; andrewm@0: blackMaxX_ = kBlackMaxXValueNewHardware; andrewm@0: blackMaxY_ = kBlackMaxYValueNewHardware; andrewm@0: } andrewm@0: else { andrewm@0: expectedLengthWhite_ = kTransmissionLengthWhiteOldHardware; andrewm@0: expectedLengthBlack_ = kTransmissionLengthBlackOldHardware; andrewm@0: whiteMaxX_ = kWhiteMaxXValueOldHardware; andrewm@0: whiteMaxY_ = kWhiteMaxYValueOldHardware; andrewm@0: blackMaxX_ = 1.0; // irrelevant -- no X data andrewm@0: blackMaxY_ = kBlackMaxYValueOldHardware; andrewm@0: } andrewm@0: andrewm@0: // Software version indicates what information is available. On version andrewm@0: // 2 and greater, can indicate which is lowest sensor available. Might andrewm@0: // be different from lowest connected key. andrewm@0: if(status.softwareVersionMajor >= 2) { andrewm@0: lowestKeyPresentMidiNote_ = octaveKeyToMidi(0, status.lowestHardwareNote); andrewm@48: andrewm@48: if(status.softwareVersionMinor == 1) { andrewm@48: // Version 2.1 uses the lowest MIDI note to handle keyboards which don't andrewm@48: // begin and end at C, e.g. E-E or F-F keyboards. andrewm@48: lowestNotePerOctave_ = status.lowestHardwareNote; andrewm@48: } andrewm@48: else { andrewm@48: lowestNotePerOctave_ = 0; andrewm@48: } andrewm@0: } andrewm@0: else if(lowestKeyPresentMidiNote_ == 127) // No keys found and old device software andrewm@0: lowestKeyPresentMidiNote_ = lowestMidiNote_; andrewm@0: andrewm@48: keyboard_.setKeyboardGUIRange(lowestKeyPresentMidiNote_, lowestMidiNote_ + 12*numOctaves_ + lowestNotePerOctave_); andrewm@0: calibrationInit(12*numOctaves_ + 1); // One more for the top C andrewm@0: } andrewm@0: else { andrewm@0: if(verbose_ >= 1) cout << "Warning: device present, but received invalid status frame.\n"; andrewm@22: deviceFlush(true); andrewm@0: return false; // Yes... found the device andrewm@0: } andrewm@0: andrewm@22: deviceFlush(true); andrewm@0: return true; // Yes... found the device andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: startingFrame = false; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: currentTime = Time::getMillisecondCounterHiRes(); andrewm@0: } andrewm@0: andrewm@0: return false; andrewm@0: } andrewm@0: andrewm@0: // Start a run loop thread to receive centroid data. Returns true andrewm@0: // on success. andrewm@0: andrewm@0: bool TouchkeyDevice::startAutoGathering() { andrewm@0: // Can only start if the device is open andrewm@0: if(!isOpen()) andrewm@0: return false; andrewm@0: // Already running? andrewm@0: if(autoGathering_) andrewm@0: return true; andrewm@0: shouldStop_ = false; andrewm@0: ledShouldStop_ = false; andrewm@0: andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Starting auto centroid collection\n"; andrewm@0: andrewm@0: // Start the data input and LED threads andrewm@0: ioThread_.startThread(); andrewm@0: ledThread_.startThread(); andrewm@0: autoGathering_ = true; andrewm@0: andrewm@0: // Tell the device to start scanning for new data andrewm@22: if(deviceWrite((char*)kCommandStartScanning, 5) < 0) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "ERROR: unable to write startAutoGather command. errno = " << errno << endl; andrewm@0: } andrewm@20: andrewm@0: keyboard_.sendMessage("/touchkeys/allnotesoff", "", LO_ARGS_END); andrewm@0: if(keyboard_.gui() != 0) { andrewm@0: // Update display: touch sensing enabled, which keys connected, no current touches andrewm@0: keyboard_.gui()->setTouchSensingEnabled(true); andrewm@0: for(set::iterator it = keysPresent_.begin(); it != keysPresent_.end(); ++it) { andrewm@0: keyboard_.gui()->setTouchSensorPresentForKey(octaveKeyToMidi(indexToOctave(*it), indexToNote(*it)), true); andrewm@0: } andrewm@0: keyboard_.gui()->clearAllTouches(); andrewm@0: keyboard_.gui()->clearAnalogData(); andrewm@0: } andrewm@0: andrewm@0: return true; andrewm@0: } andrewm@0: andrewm@0: // Stop the run loop if applicable andrewm@28: void TouchkeyDevice::stopAutoGathering(bool writeStopCommandToDevice) { andrewm@0: // Check if actually running andrewm@0: if(!autoGathering_ || !isOpen()) andrewm@0: return; andrewm@0: // Stop any calibration in progress andrewm@0: calibrationAbort(); andrewm@0: andrewm@28: if(writeStopCommandToDevice) { andrewm@28: // Tell device to stop scanning andrewm@28: if(deviceWrite((char*)kCommandStopScanning, 5) < 0) { andrewm@28: if(verbose_ >= 1) andrewm@28: cout << "ERROR: unable to write stopAutoGather command. errno = " << errno << endl; andrewm@28: } andrewm@28: } andrewm@0: andrewm@0: // Setting this to true tells the run loop to exit what it's doing andrewm@0: shouldStop_ = true; andrewm@0: ledShouldStop_ = true; andrewm@0: andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Stopping auto centroid collection\n"; andrewm@0: andrewm@0: // Wait for run loop thread to finish. Set a timeout in case there's andrewm@0: // some sort of device hangup andrewm@28: if(ioThread_.getThreadId() != Thread::getCurrentThreadId()) andrewm@28: if(ioThread_.isThreadRunning()) andrewm@28: ioThread_.stopThread(3000); andrewm@28: if(ledThread_.getThreadId() != Thread::getCurrentThreadId()) andrewm@28: if(ledThread_.isThreadRunning()) andrewm@28: ledThread_.stopThread(3000); andrewm@28: if(rawDataThread_.getThreadId() != Thread::getCurrentThreadId()) andrewm@28: if(rawDataThread_.isThreadRunning()) andrewm@28: rawDataThread_.stopThread(3000); andrewm@0: andrewm@0: // Stop any currently playing notes andrewm@0: keyboard_.sendMessage("/touchkeys/allnotesoff", "", LO_ARGS_END); andrewm@0: andrewm@0: // Clear touch for all keys andrewm@0: //std::pair keyboardRange = keyboard_.keyboardRange(); andrewm@0: //for(int i = keyboardRange.first; i <= keyboardRange.second; i++) andrewm@0: for(int i = 0; i <= 127; i++) andrewm@0: if(keyboard_.key(i) != 0) andrewm@0: keyboard_.key(i)->touchOff(lastTimestamp_); andrewm@0: andrewm@0: if(keyboard_.gui() != 0) { andrewm@0: // Update display: touch sensing disabled andrewm@0: keyboard_.gui()->clearAllTouches(); andrewm@0: keyboard_.gui()->setTouchSensingEnabled(false); andrewm@0: keyboard_.gui()->clearAnalogData(); andrewm@0: } andrewm@0: andrewm@0: if(verbose_ >= 2) andrewm@0: cout << "...done.\n"; andrewm@0: andrewm@0: autoGathering_ = false; andrewm@0: } andrewm@0: andrewm@0: // Begin raw data collection from a given single key andrewm@0: andrewm@0: bool TouchkeyDevice::startRawDataCollection(int octave, int key, int mode, int scaler) { andrewm@0: if(!isOpen()) andrewm@0: return false; andrewm@0: andrewm@0: stopAutoGathering(); // Stop the thread if it's running andrewm@0: andrewm@17: // Account for the high C which is ID 12 on the top octave andrewm@17: if(octave == numOctaves_ && key == 0) { andrewm@17: octave--; andrewm@17: key = 12; andrewm@17: } andrewm@0: andrewm@0: rawDataCurrentOctave_ = octave; andrewm@0: rawDataCurrentKey_ = key; andrewm@17: rawDataCurrentMode_ = mode; andrewm@17: rawDataCurrentScaler_ = scaler; andrewm@17: rawDataShouldChangeMode_ = true; andrewm@0: andrewm@0: shouldStop_ = false; andrewm@0: rawDataThread_.startThread(); andrewm@0: andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Starting raw data collection from octave " << octave << ", key " << key << endl; andrewm@0: andrewm@0: autoGathering_ = true; andrewm@0: andrewm@0: return true; andrewm@0: } andrewm@0: andrewm@17: void TouchkeyDevice::rawDataChangeKeyAndMode(int octave, int key, int mode, int scaler) { andrewm@17: // Account for the high C which is ID 12 on the top octave andrewm@17: if(octave == numOctaves_ && key == 0) { andrewm@17: octave--; andrewm@17: key = 12; andrewm@17: } andrewm@17: andrewm@17: rawDataCurrentOctave_ = octave; andrewm@17: rawDataCurrentKey_ = key; andrewm@17: rawDataCurrentMode_ = mode; andrewm@17: rawDataCurrentScaler_ = scaler; andrewm@17: rawDataShouldChangeMode_ = true; andrewm@17: } andrewm@17: andrewm@0: // Set the scan interval in milliseconds. Returns true on success. andrewm@0: andrewm@0: bool TouchkeyDevice::setScanInterval(int intervalMilliseconds) { andrewm@0: if(!isOpen()) andrewm@0: return false; andrewm@0: if(intervalMilliseconds <= 0 || intervalMilliseconds > 255) andrewm@0: return false; andrewm@0: andrewm@0: unsigned char command[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, andrewm@0: kFrameTypeScanRate, (unsigned char)(intervalMilliseconds & 0xFF), ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@0: andrewm@0: // Send command andrewm@22: if(deviceWrite((char*)command, 6) < 0) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "ERROR: unable to write startRawDataCollection command. errno = " << errno << endl; andrewm@0: } andrewm@0: andrewm@0: if(verbose_ >= 2) andrewm@0: cout << "Setting scan interval to " << intervalMilliseconds << endl; andrewm@0: andrewm@0: // Return value depends on ACK or NAK received andrewm@0: return checkForAck(250); andrewm@0: } andrewm@0: andrewm@0: // Key parameters. Setting octave or key to -1 means all octaves or all keys, respectively. andrewm@0: // This controls the sensitivity of the capacitive touch sensing system on each key. andrewm@0: // It is a balance between achieving the best range of data and not saturating the sensors andrewm@0: // for the largest touches. andrewm@0: bool TouchkeyDevice::setKeySensitivity(int octave, int key, int value) { andrewm@0: unsigned char chOctave, chKey, chVal; andrewm@0: andrewm@0: if(!isOpen()) andrewm@0: return false; andrewm@0: if(octave > 255) andrewm@0: return false; andrewm@0: if(key > 12) andrewm@0: return false; andrewm@0: if(value > 255 || value < 0) andrewm@0: return false; andrewm@0: if(octave < 0) andrewm@0: chOctave = 0xFF; andrewm@0: else andrewm@0: chOctave = (unsigned char)(octave & 0xFF); andrewm@0: if(key < 0) andrewm@0: chKey = 0xFF; andrewm@0: else andrewm@0: chKey = (unsigned char)(key & 0xFF); andrewm@0: chVal = (unsigned char)value; andrewm@0: andrewm@0: unsigned char command[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, kFrameTypeSensitivity, andrewm@0: chOctave, chKey, chVal, ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@0: andrewm@0: // Send command andrewm@22: if(deviceWrite((char*)command, 8) < 0) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "ERROR: unable to write setKeySensitivity command. errno = " << errno << endl; andrewm@0: } andrewm@20: andrewm@0: if(verbose_ >= 2) andrewm@0: cout << "Setting sensitivity to " << value << endl; andrewm@0: andrewm@0: // Return value depends on ACK or NAK received andrewm@0: return checkForAck(250); andrewm@0: } andrewm@0: andrewm@0: // Change how the calculated centroids are scaled to fit in a single byte. They andrewm@0: // will be right-shifted by the indicated number of bits before being transmitted. andrewm@0: bool TouchkeyDevice::setKeyCentroidScaler(int octave, int key, int value) { andrewm@0: unsigned char chOctave, chKey, chVal; andrewm@0: andrewm@0: if(!isOpen()) andrewm@0: return false; andrewm@0: if(octave > 255) andrewm@0: return false; andrewm@0: if(key > 12) andrewm@0: return false; andrewm@0: if(value > 7 || value < 0) andrewm@0: return false; andrewm@0: if(octave < 0) andrewm@0: chOctave = 0xFF; andrewm@0: else andrewm@0: chOctave = (unsigned char)(octave & 0xFF); andrewm@0: if(key < 0) andrewm@0: chKey = 0xFF; andrewm@0: else andrewm@0: chKey = (unsigned char)(key & 0xFF); andrewm@0: chVal = (unsigned char)value; andrewm@0: andrewm@0: unsigned char command[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, kFrameTypeSizeScaler, andrewm@0: chOctave, chKey, chVal, ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@0: andrewm@0: // Send command andrewm@22: if(deviceWrite((char*)command, 8) < 0) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "ERROR: unable to write setKeyCentroidScaler command. errno = " << errno << endl; andrewm@0: } andrewm@22: andrewm@0: if(verbose_ >= 2) andrewm@0: cout << "Setting size scaler to " << value << endl; andrewm@0: andrewm@0: // Return value depends on ACK or NAK received andrewm@0: return checkForAck(250); andrewm@0: } andrewm@0: andrewm@0: // Set the minimum size of a centroid calculated on the key which is considered andrewm@0: // "real" and not noise. andrewm@0: bool TouchkeyDevice::setKeyMinimumCentroidSize(int octave, int key, int value) { andrewm@0: unsigned char chOctave, chKey, chValHi, chValLo; andrewm@0: andrewm@0: if(!isOpen()) andrewm@0: return false; andrewm@0: if(octave > 255) andrewm@0: return false; andrewm@0: if(key > 12) andrewm@0: return false; andrewm@0: if(value > 0xFFFF || value < 0) andrewm@0: return false; andrewm@0: if(octave < 0) andrewm@0: chOctave = 0xFF; andrewm@0: else andrewm@0: chOctave = (unsigned char)(octave & 0xFF); andrewm@0: if(key < 0) andrewm@0: chKey = 0xFF; andrewm@0: else andrewm@0: chKey = (unsigned char)(key & 0xFF); andrewm@0: chValHi = (unsigned char)((value >> 8) & 0xFF); andrewm@0: chValLo = (unsigned char)(value & 0xFF); andrewm@0: andrewm@0: unsigned char command[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, kFrameTypeMinimumSize, andrewm@0: chOctave, chKey, chValHi, chValLo, ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@0: andrewm@0: // Send command andrewm@22: if(deviceWrite((char*)command, 9) < 0) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "ERROR: unable to write setKeyMinimumCentroidSize command. errno = " << errno << endl; andrewm@0: } andrewm@20: andrewm@0: if(verbose_ >= 2) andrewm@0: cout << "Setting minimum centroid size to " << value << endl; andrewm@0: andrewm@0: // Return value depends on ACK or NAK received andrewm@0: return checkForAck(250); andrewm@0: } andrewm@0: andrewm@0: // Set the noise threshold for individual sensor pads: the reading must exceed andrewm@0: // the background value by this amount to be considered an actual touch. andrewm@0: bool TouchkeyDevice::setKeyNoiseThreshold(int octave, int key, int value) { andrewm@0: unsigned char chOctave, chKey, chVal; andrewm@0: andrewm@0: if(octave > 255) andrewm@0: return false; andrewm@0: if(key > 12) andrewm@0: return false; andrewm@0: if(value > 255 || value < 0) andrewm@0: return false; andrewm@0: if(octave < 0) andrewm@0: chOctave = 0xFF; andrewm@0: else andrewm@0: chOctave = (unsigned char)(octave & 0xFF); andrewm@0: if(key < 0) andrewm@0: chKey = 0xFF; andrewm@0: else andrewm@0: chKey = (unsigned char)(key & 0xFF); andrewm@0: chVal = (unsigned char)value; andrewm@0: andrewm@0: unsigned char command[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, kFrameTypeNoiseThreshold, andrewm@0: chOctave, chKey, chVal, ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@0: andrewm@0: // Send command andrewm@22: if(deviceWrite((char*)command, 8) < 0) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "ERROR: unable to write setKeyNoiseThreshold command. errno = " << errno << endl; andrewm@0: } andrewm@20: andrewm@0: if(verbose_ >= 2) andrewm@0: cout << "Setting noise threshold to " << value << endl; andrewm@0: andrewm@0: // Return value depends on ACK or NAK received andrewm@0: return checkForAck(250); andrewm@0: } andrewm@0: andrewm@19: // Update the baseline sensor values on the given key andrewm@19: bool TouchkeyDevice::setKeyUpdateBaseline(int octave, int key) { andrewm@19: unsigned char baselineCommand[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, andrewm@19: kFrameTypeSendI2CCommand, (unsigned char)octave, (unsigned char)key, andrewm@19: 2 /* xmit */, 0 /* response */, 0 /* command offset */, 6 /* baseline update */, andrewm@19: ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@19: andrewm@19: // Send command andrewm@22: if(deviceWrite((char*)baselineCommand, 11) < 0) { andrewm@19: if(verbose_ >= 1) andrewm@19: cout << "ERROR: unable to write baseline update command. errno = " << errno << endl; andrewm@19: } andrewm@20: andrewm@19: if(verbose_ >= 2) andrewm@19: cout << "Updating baseline on octave " << octave << " key " << key << endl; andrewm@19: andrewm@19: checkForAck(100); andrewm@19: andrewm@19: unsigned char commandPrepareRead[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, andrewm@19: kFrameTypeSendI2CCommand, (unsigned char)octave, (unsigned char)key, andrewm@19: 1 /* xmit */, 0 /* response */, 6 /* data offset */, andrewm@19: ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@19: andrewm@22: if(deviceWrite((char*)commandPrepareRead, 10) < 0) { andrewm@19: if(verbose_ >= 1) andrewm@19: cout << "ERROR: unable to write prepareRead command. errno = " << errno << endl; andrewm@19: } andrewm@20: andrewm@19: // Return value depends on ACK or NAK received andrewm@19: return checkForAck(100); andrewm@19: } andrewm@19: andrewm@0: // Jump to the built-in bootloader of the TouchKeys device andrewm@0: void TouchkeyDevice::jumpToBootloader() { andrewm@53: // The command includes a 4-byte magic number to avoid a corrupt packet accidentally triggering the jump andrewm@0: unsigned char command[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, kFrameTypeEnterSelfProgramMode, andrewm@53: 0xA1, 0xB2, 0xC3, 0xD4, ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@0: andrewm@0: // Send command andrewm@53: if(deviceWrite((char*)command, 9) < 0) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "ERROR: unable to write jumpToBootloader command. errno = " << errno << endl; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Set the LED color for the given MIDI note (if RGB LEDs are present). This method andrewm@0: // does not directly communicate with the device, but it schedules an update to take andrewm@0: // place in the relevant thread. andrewm@0: void TouchkeyDevice::rgbledSetColor(const int midiNote, const float red, const float green, const float blue) { andrewm@0: RGBLEDUpdate updateStructure; andrewm@0: andrewm@0: updateStructure.allLedsOff = false; andrewm@0: updateStructure.midiNote = midiNote; andrewm@0: andrewm@0: // Convert 0-1 floating point range to 0-255 andrewm@0: updateStructure.red = (int)(red * 4095.0); andrewm@0: updateStructure.green = (int)(green * 4095.0); andrewm@0: updateStructure.blue = (int)(blue * 4095.0); andrewm@0: andrewm@0: ledUpdateQueue_.push_front(updateStructure); andrewm@0: } andrewm@0: andrewm@0: // Same as rgbledSetColor() but uses HSV format color instead of RGB andrewm@0: void TouchkeyDevice::rgbledSetColorHSV(const int midiNote, const float hue, const float saturation, const float value) { andrewm@0: float red = 0, green = 0, blue = 0; andrewm@0: float chroma = value * saturation; andrewm@0: andrewm@0: // Hue will lie on one of 6 segments from 0 to 1; convert this from 0 to 6. andrewm@0: float hueSegment = hue * 6.0; andrewm@0: float x = chroma * (1.0 - fabsf(fmodf(hueSegment, 2.0) - 1.0)); andrewm@0: andrewm@0: if(hueSegment < 1.0) { andrewm@0: red = chroma; andrewm@0: green = x; andrewm@0: blue = 0; andrewm@0: } andrewm@0: else if(hueSegment < 2.0) { andrewm@0: red = x; andrewm@0: green = chroma; andrewm@0: blue = 0; andrewm@0: } andrewm@0: else if(hueSegment < 3.0) { andrewm@0: red = 0; andrewm@0: green = chroma; andrewm@0: blue = x; andrewm@0: } andrewm@0: else if(hueSegment < 4.0) { andrewm@0: red = 0; andrewm@0: green = x; andrewm@0: blue = chroma; andrewm@0: } andrewm@0: else if(hueSegment < 5.0) { andrewm@0: red = x; andrewm@0: green = 0; andrewm@0: blue = chroma; andrewm@0: } andrewm@0: else { andrewm@0: red = chroma; andrewm@0: green = 0; andrewm@0: blue = x; andrewm@0: } andrewm@0: andrewm@0: rgbledSetColor(midiNote, red, green, blue); andrewm@0: } andrewm@0: andrewm@0: // Set all RGB LEDs off (if RGB LEDs are present). This method does not andrewm@0: // directly communicate with the device, but it schedules an update to take andrewm@0: // place in the relevant thread. andrewm@0: void TouchkeyDevice::rgbledAllOff() { andrewm@0: RGBLEDUpdate updateStructure; andrewm@0: andrewm@0: updateStructure.allLedsOff = true; andrewm@0: updateStructure.midiNote = 0; andrewm@0: updateStructure.red = 0; andrewm@0: updateStructure.green = 0; andrewm@0: updateStructure.blue = 0; andrewm@0: andrewm@0: ledUpdateQueue_.push_front(updateStructure); andrewm@0: } andrewm@0: andrewm@0: // Set the color of a given RGB LED (piano scanner boards only). LEDs are numbered from 0-24 andrewm@0: // starting at left. Boards are numbered 0-3 starting at left. andrewm@0: bool TouchkeyDevice::internalRGBLEDSetColor(const int device, const int led, const int red, const int green, const int blue) { andrewm@0: if(!isOpen()) andrewm@0: return false; andrewm@0: if(!deviceHasRGBLEDs_) andrewm@0: return false; andrewm@0: if(device < 0 || device > 3) andrewm@0: return false; andrewm@0: if(led < 0 || led > 24) andrewm@0: return false; andrewm@0: if(red < 0 || red > 4095) andrewm@0: return false; andrewm@0: if(green < 0 || green > 4095) andrewm@0: return false; andrewm@0: if(blue < 0 || blue > 4095) andrewm@0: return false; andrewm@0: andrewm@0: unsigned char command[17]; // 11 bytes + possibly 6 doubled characters andrewm@0: andrewm@0: // There's a chance that one of the bytes will come out to ESCAPE_CHARACTER (0xFE) depending andrewm@0: // on LED color. We need to double up any bytes that come in that way. andrewm@0: andrewm@0: command[0] = ESCAPE_CHARACTER; andrewm@0: command[1] = kControlCharacterFrameBegin; andrewm@0: command[2] = kFrameTypeRGBLEDSetColors; andrewm@0: andrewm@0: int byte, location = 3; andrewm@0: andrewm@0: byte = (((unsigned char)device & 0xFF) << 6) | (unsigned char)led; andrewm@0: command[location++] = byte; andrewm@0: if(byte == ESCAPE_CHARACTER) andrewm@0: command[location++] = byte; andrewm@0: byte = (red >> 4) & 0xFF; andrewm@0: command[location++] = byte; andrewm@0: if(byte == ESCAPE_CHARACTER) andrewm@0: command[location++] = byte; andrewm@0: byte = ((red << 4) & 0xF0) | ((green >> 8) & 0x0F); andrewm@0: command[location++] = byte; andrewm@0: if(byte == ESCAPE_CHARACTER) andrewm@0: command[location++] = byte; andrewm@0: byte = (green & 0xFF); andrewm@0: command[location++] = byte; andrewm@0: if(byte == ESCAPE_CHARACTER) andrewm@0: command[location++] = byte; andrewm@0: byte = (blue >> 4) & 0xFF; andrewm@0: command[location++] = byte; andrewm@0: if(byte == ESCAPE_CHARACTER) andrewm@0: command[location++] = byte; andrewm@0: byte = (blue << 4) & 0xF0; andrewm@0: command[location++] = byte; andrewm@0: if(byte == ESCAPE_CHARACTER) andrewm@0: command[location++] = byte; andrewm@0: command[location++] = ESCAPE_CHARACTER; andrewm@0: command[location++] = kControlCharacterFrameEnd; andrewm@0: andrewm@0: // Send command andrewm@22: if(deviceWrite((char*)command, location) < 0) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "ERROR: unable to write setRGBLEDColor command. errno = " << errno << endl; andrewm@0: } andrewm@0: andrewm@0: if(verbose_ >= 3) andrewm@0: cout << "Setting RGB LED color for device " << device << ", led " << led << endl; andrewm@0: andrewm@0: // Return value depends on ACK or NAK received andrewm@0: return true; //checkForAck(20); andrewm@0: } andrewm@0: andrewm@0: // Turn off all RGB LEDs on a given board andrewm@0: bool TouchkeyDevice::internalRGBLEDAllOff() { andrewm@0: if(!isOpen()) andrewm@0: return false; andrewm@0: if(!deviceHasRGBLEDs_) andrewm@0: return false; andrewm@0: andrewm@0: unsigned char command[5]; andrewm@0: andrewm@0: command[0] = ESCAPE_CHARACTER; andrewm@0: command[1] = kControlCharacterFrameBegin; andrewm@0: command[2] = kFrameTypeRGBLEDAllOff; andrewm@0: command[3] = ESCAPE_CHARACTER; andrewm@0: command[4] = kControlCharacterFrameEnd; andrewm@0: andrewm@0: // Send command andrewm@22: if(deviceWrite((char*)command, 5) < 0) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "ERROR: unable to write setRGBLEDAllOff command. errno = " << errno << endl; andrewm@0: } andrewm@22: andrewm@0: if(verbose_ >= 3) andrewm@0: cout << "Turning off all RGB LEDs" << endl; andrewm@0: andrewm@0: // Return value depends on ACK or NAK received andrewm@0: return true; //checkForAck(20); andrewm@0: } andrewm@0: andrewm@0: // Get board number for MIDI note andrewm@0: int TouchkeyDevice::internalRGBLEDMIDIToBoardNumber(const int midiNote) { andrewm@0: // lowestMidiNote_ holds the very bottom LED on the bottom board. The boards andrewm@0: // go up by two-octave sets from there. The top board has one extra LED (high C). andrewm@0: andrewm@0: if(midiNote > lowestMidiNote_ + 96) andrewm@0: return -1; andrewm@0: if(midiNote >= lowestMidiNote_ + 72) andrewm@0: return 3; andrewm@0: else if(midiNote >= lowestMidiNote_ + 48) andrewm@0: return 2; andrewm@0: else if(midiNote >= lowestMidiNote_ + 24) andrewm@0: return 1; andrewm@0: else if(midiNote >= lowestMidiNote_) andrewm@0: return 0; andrewm@0: return -1; andrewm@0: } andrewm@0: andrewm@0: // Get LED number for MIDI note (within a board) andrewm@0: int TouchkeyDevice::internalRGBLEDMIDIToLEDNumber(const int midiNote) { andrewm@0: // Take the note number relative to the lowest note of the whole device. andrewm@0: // Once it's located within a board (2-octaves each), the offset gives us the LED number. andrewm@0: // However, the lowest board works differently as it only has 15 LEDs which start at A, andrewm@0: // not at C. andrewm@0: const int midiNoteOffset = midiNote - lowestMidiNote_; andrewm@0: andrewm@0: if(midiNoteOffset < 9) // Below the bottom A, hence invalid andrewm@0: return -1; andrewm@0: if(midiNoteOffset < 24) { andrewm@0: // Within 2 octaves of bottom --> lowest board --> adjust for 15 LEDs on board andrewm@0: return midiNoteOffset - 9; andrewm@0: } andrewm@0: else if(midiNoteOffset < 48) { andrewm@0: // Board 1 andrewm@0: return midiNoteOffset - 24; andrewm@0: } andrewm@0: else if(midiNoteOffset < 72) { andrewm@0: // Board 2 andrewm@0: return midiNoteOffset - 48; andrewm@0: } andrewm@0: else if(midiNoteOffset < 97) { andrewm@0: // Board 3 includes a top C (index 24) andrewm@0: return midiNoteOffset - 72; andrewm@0: } andrewm@0: return -1; andrewm@0: } andrewm@0: andrewm@0: // ***** Calibration Methods ***** andrewm@0: andrewm@0: // Start calibrating selected keys and pedals. If argument is NULL, assume it applies to all keys. andrewm@0: void TouchkeyDevice::calibrationStart(std::vector* keysToCalibrate) { andrewm@0: if(keysToCalibrate == 0) { andrewm@0: for(int i = 0; i < keyCalibratorsLength_; i++) andrewm@0: keyCalibrators_[i]->calibrationStart(); andrewm@0: } andrewm@0: else { andrewm@0: std::vector::iterator it; andrewm@0: for(it = keysToCalibrate->begin(); it != keysToCalibrate->end(); it++) { andrewm@0: if(*it >= 0 && *it < keyCalibratorsLength_) andrewm@0: keyCalibrators_[*it]->calibrationStart(); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: calibrationInProgress_ = true; andrewm@0: } andrewm@0: andrewm@0: // Finish the current calibration in progress. Pass it on to all Calibrators, and the ones that weren't andrewm@0: // calibrating will just ignore it. andrewm@0: void TouchkeyDevice::calibrationFinish() { andrewm@0: bool calibratedAtLeastOneKey = false; andrewm@0: andrewm@0: for(int i = 0; i < keyCalibratorsLength_; i++) { andrewm@0: // Check if calibration was successful andrewm@0: if(keyCalibrators_[i]->calibrationFinish()) { andrewm@0: calibratedAtLeastOneKey = true; andrewm@0: // Update the display if available andrewm@0: if(keyboard_.gui() != 0) { andrewm@0: keyboard_.gui()->setAnalogCalibrationStatusForKey(i + lowestMidiNote_, true); andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: calibrationInProgress_ = false; andrewm@0: isCalibrated_ = calibratedAtLeastOneKey; andrewm@0: } andrewm@0: andrewm@0: // Abort a calibration in progress, without saving its results. Pass it on to all Calibrators. andrewm@0: void TouchkeyDevice::calibrationAbort() { andrewm@0: for(int i = 0; i < keyCalibratorsLength_; i++) andrewm@0: keyCalibrators_[i]->calibrationAbort(); andrewm@0: andrewm@0: calibrationInProgress_ = false; andrewm@0: } andrewm@0: andrewm@0: // Clear the existing calibration, reverting to an uncalibrated state. andrewm@0: void TouchkeyDevice::calibrationClear() { andrewm@0: for(int i = 0; i < keyCalibratorsLength_; i++) { andrewm@0: keyCalibrators_[i]->calibrationClear(); andrewm@0: if(keyboard_.gui() != 0) { andrewm@0: keyboard_.gui()->setAnalogCalibrationStatusForKey(i + lowestMidiNote_, false); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: calibrationInProgress_ = false; andrewm@0: isCalibrated_ = false; andrewm@0: } andrewm@0: andrewm@0: // Save calibration data to a file andrewm@0: bool TouchkeyDevice::calibrationSaveToFile(std::string const& filename) { andrewm@0: int i; andrewm@0: andrewm@0: if(!isCalibrated()) { andrewm@0: std::cerr << "TouchKeys not calibrated, so can't save calibration data.\n"; andrewm@0: return false; andrewm@0: } andrewm@0: andrewm@0: // Create an XML structure and save it to file. andrewm@0: try { andrewm@0: XmlElement baseElement("TouchkeyDeviceCalibration"); andrewm@0: bool savedValidData = false; andrewm@0: andrewm@0: for(i = 0; i < keyCalibratorsLength_; i++) { andrewm@0: XmlElement *calibrationElement = baseElement.createNewChildElement("Key"); andrewm@0: if(calibrationElement == 0) andrewm@0: continue; andrewm@0: andrewm@0: calibrationElement->setAttribute("id", i); andrewm@0: andrewm@0: // Tell each individual calibrator to add its data to the XML tree andrewm@0: if(keyCalibrators_[i]->saveToXml(*calibrationElement)) { andrewm@0: savedValidData = true; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: if(!savedValidData) { andrewm@0: std::cerr << "TouchkeyDevice: unable to find valid calibration data to save.\n"; andrewm@0: throw 1; andrewm@0: } andrewm@0: andrewm@0: // Now save the generated tree to a file andrewm@0: andrewm@0: if(!baseElement.writeToFile(File(filename.c_str()), "")) { andrewm@0: std::cerr << "TouchkeyDevice: could not write calibration file " << filename << "\n"; andrewm@0: throw 1; andrewm@0: } andrewm@0: andrewm@0: //lastCalibrationFile_ = filename; andrewm@0: } andrewm@0: catch(...) { andrewm@0: return false; andrewm@0: } andrewm@0: andrewm@0: return true; andrewm@0: } andrewm@0: andrewm@0: // Load calibration from a file andrewm@0: bool TouchkeyDevice::calibrationLoadFromFile(std::string const& filename) { andrewm@0: //int i, j; andrewm@0: andrewm@0: calibrationClear(); andrewm@0: andrewm@0: // Open the file and read the new values andrewm@0: try { andrewm@0: XmlDocument doc(File(filename.c_str())); andrewm@0: XmlElement *baseElement = doc.getDocumentElement(); andrewm@0: XmlElement *deviceCalibrationElement, *calibratorElement; andrewm@0: andrewm@0: if(baseElement == 0) { andrewm@0: std::cerr << "TouchkeyDevice: unable to load patch table file: \"" << filename << "\". Error was:\n"; andrewm@0: std::cerr << doc.getLastParseError() << std::endl; andrewm@0: throw 1; andrewm@0: } andrewm@0: andrewm@0: // All calibration data is encapsulated within the root element andrewm@0: deviceCalibrationElement = baseElement->getChildByName("TouchkeyDeviceCalibration"); andrewm@0: if(deviceCalibrationElement == 0) { andrewm@0: std::cerr << "TouchkeyDevice: malformed calibration file, aborting.\n"; andrewm@0: delete baseElement; andrewm@0: throw 1; andrewm@0: } andrewm@0: andrewm@0: // Go through and find each key's calibration information andrewm@0: calibratorElement = deviceCalibrationElement->getChildByName("Key"); andrewm@0: if(calibratorElement == 0) { andrewm@0: std::cerr << "TouchkeyDevice: warning: no keys found\n"; andrewm@0: } andrewm@0: else { andrewm@0: while(calibratorElement != 0) { andrewm@0: int keyId; andrewm@0: andrewm@0: if(calibratorElement->hasAttribute("id")) { andrewm@0: keyId = calibratorElement->getIntAttribute("id"); andrewm@0: if(keyId >= 0 && keyId < keyCalibratorsLength_) andrewm@0: keyCalibrators_[keyId]->loadFromXml(*calibratorElement); andrewm@0: } andrewm@0: andrewm@0: calibratorElement = calibratorElement->getNextElementWithTagName("Key"); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: calibrationInProgress_ = false; andrewm@0: isCalibrated_ = true; andrewm@0: if(keyboard_.gui() != 0) { andrewm@0: for(int i = lowestMidiNote_; i < lowestMidiNote_ + 12*numOctaves_; i++) { andrewm@0: keyboard_.gui()->setAnalogCalibrationStatusForKey(i, true); andrewm@0: } andrewm@0: } andrewm@0: //lastCalibrationFile_ = filename; andrewm@0: andrewm@0: delete baseElement; andrewm@0: } andrewm@0: catch(...) { andrewm@0: return false; andrewm@0: } andrewm@0: andrewm@0: // TODO: reset key states? andrewm@0: andrewm@0: return true; andrewm@0: } andrewm@0: andrewm@0: // Initialize the calibrators andrewm@0: void TouchkeyDevice::calibrationInit(int numberOfCalibrators) { andrewm@0: if(keyCalibrators_ != 0) andrewm@0: calibrationDeinit(); andrewm@0: if(numberOfCalibrators <= 0) andrewm@0: return; andrewm@0: keyCalibratorsLength_ = numberOfCalibrators; andrewm@0: andrewm@0: // Initialize the calibrator array andrewm@0: keyCalibrators_ = (PianoKeyCalibrator **)malloc(keyCalibratorsLength_ * sizeof(PianoKeyCalibrator*)); andrewm@0: andrewm@0: for(int i = 0; i < keyCalibratorsLength_; i++) { andrewm@0: keyCalibrators_[i] = new PianoKeyCalibrator(true, 0); andrewm@0: } andrewm@0: andrewm@0: calibrationClear(); andrewm@0: } andrewm@0: andrewm@0: // Free the initialized calibrators andrewm@0: void TouchkeyDevice::calibrationDeinit() { andrewm@0: if(keyCalibrators_ == 0) andrewm@0: return; andrewm@0: andrewm@0: for(int i = 0; i < keyCalibratorsLength_; i++) { andrewm@0: if(keyCalibrators_[i] != 0) andrewm@0: delete keyCalibrators_[i]; andrewm@0: keyCalibrators_[i] = 0; andrewm@0: } andrewm@0: free(keyCalibrators_); andrewm@0: andrewm@0: keyCalibratorsLength_ = 0; andrewm@0: isCalibrated_ = calibrationInProgress_ = false; andrewm@0: } andrewm@0: andrewm@0: // Update the lowest MIDI note of the TouchKeys device andrewm@0: void TouchkeyDevice::setLowestMidiNote(int note) { andrewm@0: // If running, save the value in a temporary holding place until andrewm@0: // the data gathering thread makes the update. Otherwise update right away. andrewm@0: // This avoids things changing during data processing and other threading problems. andrewm@0: if(isAutoGathering()) andrewm@0: updatedLowestMidiNote_ = note; andrewm@0: else { andrewm@0: lowestKeyPresentMidiNote_ += (note - lowestMidiNote_); andrewm@0: lowestMidiNote_ = updatedLowestMidiNote_ = note; andrewm@0: if(isOpen()) andrewm@48: keyboard_.setKeyboardGUIRange(lowestKeyPresentMidiNote_, lowestMidiNote_ + 12*numOctaves_ + lowestNotePerOctave_); andrewm@0: } andrewm@0: } andrewm@0: andrewm@48: // Convert an octave and key designation into a MIDI note andrewm@48: int TouchkeyDevice::octaveKeyToMidi(int octave, int key) { andrewm@48: int midi = lowestMidiNote_ + octave*12 + key; andrewm@48: andrewm@48: if(lowestNotePerOctave_ == 0) andrewm@48: return midi; andrewm@48: andrewm@48: // For keyboards which do not change octaves at C (e.g. E-E and F-F keyboards), andrewm@48: // the lowest note numbers are actually one octave higher (e.g. an octave might start andrewm@48: // at E, meaning C-Eb are part of the next higher octave). andrewm@48: // Also, the "top C" (key 12) has a special designation as being the top of andrewm@48: // whichever note the keyboard began at. andrewm@48: andrewm@48: if(key == 12) andrewm@48: midi = lowestMidiNote_ + octave*12 + key + lowestNotePerOctave_; andrewm@48: else if(key < lowestNotePerOctave_) andrewm@48: midi += 12; andrewm@48: andrewm@48: return midi; andrewm@48: } andrewm@48: andrewm@0: // Loop for sending LED updates to the device, which must happen andrewm@0: // in a separate thread from data collection so the device's capacity andrewm@0: // to process incoming data doesn't gate its transmission of sensor data andrewm@0: void TouchkeyDevice::ledUpdateLoop(DeviceThread *thread) { andrewm@0: andrewm@0: // Run until told to stop, looking for updates to send to the board andrewm@0: while(!shouldStop_ && !ledShouldStop_ && !thread->threadShouldExit()) { andrewm@0: while(!ledUpdateQueue_.empty()) { andrewm@0: // Get the update andrewm@0: RGBLEDUpdate& updateStructure = ledUpdateQueue_.back(); andrewm@0: andrewm@0: if(updateStructure.allLedsOff) { andrewm@0: internalRGBLEDAllOff(); andrewm@0: } andrewm@0: else { andrewm@0: // Convert MIDI note number to board/LED pair. If valid, send to device. andrewm@0: int board = internalRGBLEDMIDIToBoardNumber(updateStructure.midiNote); andrewm@0: int led = internalRGBLEDMIDIToLEDNumber(updateStructure.midiNote); andrewm@0: andrewm@0: if(board >= 0 && board <= 3 && led >= 0) andrewm@0: internalRGBLEDSetColor(board, led, updateStructure.red, updateStructure.green, updateStructure.blue); andrewm@0: } andrewm@0: andrewm@0: // Remove the update we just transmitted andrewm@0: ledUpdateQueue_.pop_back(); andrewm@0: } andrewm@0: andrewm@22: Thread::sleep(20); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Main run loop, which runs in its own thread andrewm@0: void TouchkeyDevice::runLoop(DeviceThread *thread) { andrewm@0: unsigned char buffer[1024]; // Raw data from device andrewm@0: unsigned char frame[TOUCHKEY_MAX_FRAME_LENGTH]; // Accumulated frame of data andrewm@0: int frameLength; andrewm@0: bool controlSeq = false, inFrame = false, frameError = false; andrewm@0: andrewm@0: /* struct timeval currentTime; andrewm@0: unsigned long long currentTicks = 0, lastTicks = 0; andrewm@0: int currentNote = 21;*/ andrewm@0: andrewm@0: // Continuously read from the input device. Read as much data as is available, up to andrewm@0: // 1024 bytes at a time. If no data is available, wait 0.5ms before trying again. USB andrewm@0: // data comes in every 1ms, so this guarantees no more than a 1ms wait for data, and often less. andrewm@0: andrewm@0: while(!shouldStop_ && !thread->threadShouldExit()) { andrewm@0: andrewm@0: /* andrewm@0: // This code for RGBLED testing andrewm@0: gettimeofday(¤tTime, 0); andrewm@0: andrewm@0: currentTicks = currentTime.tv_sec * 1000000ULL + currentTime.tv_usec; andrewm@0: if(currentTicks - lastTicks > 50000ULL) { andrewm@0: lastTicks = currentTicks; andrewm@0: rgbledSetColor(currentNote, 0, 0, 0); andrewm@0: currentNote++; andrewm@0: if(currentNote > highestMidiNote()) { andrewm@0: rgbledAllOff(); andrewm@0: currentNote = 21; andrewm@0: } andrewm@0: rgbledSetColorHSV(currentNote, (float)(currentNote - 21)/(float)(highestMidiNote() - 21), 1.0, 1.0); andrewm@0: } andrewm@0: */ andrewm@22: long count = deviceRead((char *)buffer, 1024); andrewm@20: andrewm@0: if(count == 0) { andrewm@20: #ifdef _MSC_VER andrewm@22: Thread::sleep(1); andrewm@20: #else andrewm@0: usleep(500); andrewm@20: #endif andrewm@0: continue; andrewm@0: } andrewm@0: if(count < 0) { andrewm@0: if(errno != EAGAIN) { // EAGAIN just means no data was available andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Unable to read from device (error " << errno << "). Aborting.\n"; andrewm@28: stopAutoGathering(false); andrewm@28: //shouldStop_ = true; andrewm@0: } andrewm@0: andrewm@20: #ifdef _MSC_VER andrewm@22: Thread::sleep(1); andrewm@20: #else andrewm@0: usleep(500); andrewm@20: #endif andrewm@0: continue; andrewm@0: } andrewm@0: andrewm@0: // Process the received data andrewm@0: andrewm@0: for(int i = 0; i < count; i++) { andrewm@0: unsigned char ch = buffer[i]; andrewm@0: andrewm@0: if(inFrame) { andrewm@0: // Receiving a frame andrewm@0: andrewm@0: if(controlSeq) { andrewm@0: controlSeq = false; andrewm@0: if(ch == kControlCharacterFrameEnd) { // frame finished? andrewm@0: inFrame = false; andrewm@0: processFrame(frame, frameLength); andrewm@0: } andrewm@0: else if(ch == kControlCharacterFrameError) { // device telling us about an internal comm error andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: received frame error, continuing anyway.\n"; andrewm@0: frameError = true; andrewm@0: } andrewm@0: else if(ch == ESCAPE_CHARACTER) { // double-escape means a literal escape character andrewm@0: frame[frameLength++] = ch; andrewm@0: if(frameLength >= TOUCHKEY_MAX_FRAME_LENGTH) { andrewm@0: inFrame = false; andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: ignoring frame exceeding length limit " << (int)TOUCHKEY_MAX_FRAME_LENGTH << endl; andrewm@0: } andrewm@0: } andrewm@0: else if(ch == kControlCharacterNak && verbose_ >= 1) { andrewm@0: // TODO: pass this on to a checkForAck() call andrewm@0: cout << "Warning: received NAK (while receiving frame)\n"; andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: if(ch == ESCAPE_CHARACTER) andrewm@0: controlSeq = true; andrewm@0: else { andrewm@0: frame[frameLength++] = ch; andrewm@0: if(frameLength >= TOUCHKEY_MAX_FRAME_LENGTH) { andrewm@0: inFrame = false; andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: ignoring frame exceeding length limit " << (int)TOUCHKEY_MAX_FRAME_LENGTH << endl; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: // Waiting for a frame beginning control sequence andrewm@0: andrewm@0: if(controlSeq) { andrewm@0: controlSeq = false; andrewm@0: if(ch == kControlCharacterFrameBegin) { andrewm@0: inFrame = true; andrewm@0: frameLength = 0; andrewm@0: frameError = false; andrewm@0: } andrewm@0: else if(ch == kControlCharacterNak && verbose_ >= 1) { andrewm@0: // TODO: pass this on to a checkForAck() call andrewm@0: cout << "Warning: received NAK (while waiting for frame)\n"; andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: if(ch == ESCAPE_CHARACTER) andrewm@0: controlSeq = true; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Main run loop for gathering raw data from a particular key, used for debugging andrewm@0: // and testing purposes andrewm@0: void TouchkeyDevice::rawDataRunLoop(DeviceThread *thread) { andrewm@0: unsigned char buffer[1024]; // Raw data from device andrewm@0: unsigned char frame[TOUCHKEY_MAX_FRAME_LENGTH]; // Accumulated frame of data andrewm@0: int frameLength; andrewm@0: bool controlSeq = false, inFrame = false, frameError = false; andrewm@0: andrewm@0: unsigned char gatherDataCommand[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, andrewm@0: kFrameTypeSendI2CCommand, (unsigned char)rawDataCurrentOctave_, (unsigned char)rawDataCurrentKey_, andrewm@0: 0 /* xmit */, 26 /* response */, andrewm@0: ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@0: andrewm@0: //struct timeval currentTime; andrewm@0: double currentTime = 0, lastTime = 0; andrewm@0: //unsigned long long currentTicks = 0, lastTicks = 0; andrewm@0: andrewm@0: // Continuously read from the input device. Read as much data as is available, up to andrewm@0: // 1024 bytes at a time. If no data is available, wait 0.5ms before trying again. USB andrewm@0: // data comes in every 1ms, so this guarantees no more than a 1ms wait for data, and often less. andrewm@0: andrewm@0: while(!shouldStop_ && !thread->threadShouldExit()) { andrewm@0: // Every 100ms, request raw data from the active key andrewm@0: currentTime = Time::getMillisecondCounterHiRes(); andrewm@0: andrewm@17: if(currentTime - lastTime > 50.0) { andrewm@0: lastTime = currentTime; andrewm@17: andrewm@17: // Check if we need to choose a new key or mode andrewm@17: if(rawDataShouldChangeMode_) { andrewm@17: // Prepare the key and update the command andrewm@17: rawDataPrepareCollection(rawDataCurrentOctave_, rawDataCurrentKey_, rawDataCurrentMode_, rawDataCurrentScaler_); andrewm@17: gatherDataCommand[3] = rawDataCurrentOctave_; andrewm@17: gatherDataCommand[4] = rawDataCurrentKey_; andrewm@17: } andrewm@17: andrewm@0: // Request data andrewm@22: if(deviceWrite((char*)gatherDataCommand, 9) < 0) { andrewm@7: if(verbose_ >= 1) andrewm@17: cout << "ERROR: unable to write gather data command. errno = " << errno << endl; andrewm@0: } andrewm@0: } andrewm@20: andrewm@22: long count = deviceRead((char *)buffer, 1024); andrewm@20: andrewm@0: if(count == 0) { andrewm@20: #ifdef _MSC_VER andrewm@22: Thread::sleep(1); andrewm@20: #else andrewm@0: usleep(500); andrewm@20: #endif andrewm@0: continue; andrewm@0: } andrewm@0: if(count < 0) { andrewm@0: if(errno != EAGAIN) { // EAGAIN just means no data was available andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Unable to read from device (error " << errno << "). Aborting.\n"; andrewm@28: stopAutoGathering(false); andrewm@28: //shouldStop_ = true; andrewm@0: } andrewm@0: andrewm@20: #ifdef _MSC_VER andrewm@22: Thread::sleep(1); andrewm@20: #else andrewm@0: usleep(500); andrewm@20: #endif andrewm@0: continue; andrewm@0: } andrewm@0: andrewm@0: // Process the received data andrewm@0: andrewm@0: for(int i = 0; i < count; i++) { andrewm@0: unsigned char ch = buffer[i]; andrewm@0: andrewm@0: if(inFrame) { andrewm@0: // Receiving a frame andrewm@0: andrewm@0: if(controlSeq) { andrewm@0: controlSeq = false; andrewm@0: if(ch == kControlCharacterFrameEnd) { // frame finished? andrewm@0: inFrame = false; andrewm@0: processFrame(frame, frameLength); andrewm@0: } andrewm@0: else if(ch == kControlCharacterFrameError) { // device telling us about an internal comm error andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: received frame error, continuing anyway.\n"; andrewm@0: frameError = true; andrewm@0: } andrewm@0: else if(ch == ESCAPE_CHARACTER) { // double-escape means a literal escape character andrewm@0: frame[frameLength++] = ch; andrewm@0: if(frameLength >= TOUCHKEY_MAX_FRAME_LENGTH) { andrewm@0: inFrame = false; andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: ignoring frame exceeding length limit " << (int)TOUCHKEY_MAX_FRAME_LENGTH << endl; andrewm@0: } andrewm@0: } andrewm@0: else if(ch == kControlCharacterNak && verbose_ >= 1) { andrewm@0: // TODO: pass this on to a checkForAck() call andrewm@0: cout << "Warning: received NAK (while receiving frame)\n"; andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: if(ch == ESCAPE_CHARACTER) andrewm@0: controlSeq = true; andrewm@0: else { andrewm@0: frame[frameLength++] = ch; andrewm@0: if(frameLength >= TOUCHKEY_MAX_FRAME_LENGTH) { andrewm@0: inFrame = false; andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: ignoring frame exceeding length limit " << (int)TOUCHKEY_MAX_FRAME_LENGTH << endl; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: // Waiting for a frame beginning control sequence andrewm@0: andrewm@0: if(controlSeq) { andrewm@0: controlSeq = false; andrewm@0: if(ch == kControlCharacterFrameBegin) { andrewm@0: inFrame = true; andrewm@0: frameLength = 0; andrewm@0: frameError = false; andrewm@0: } andrewm@0: else if(ch == kControlCharacterNak && verbose_ >= 1) { andrewm@0: // TODO: pass this on to a checkForAck() call andrewm@0: cout << "Warning: received NAK (while waiting for frame)\n"; andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: if(ch == ESCAPE_CHARACTER) andrewm@0: controlSeq = true; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Process the contents of a frame that has been received from the device andrewm@0: void TouchkeyDevice::processFrame(unsigned char * const frame, int length) { andrewm@0: if(length == 0) // Empty frame --> nothing to do here andrewm@0: return; andrewm@0: andrewm@0: switch(frame[0]) { // First character gives frame type andrewm@0: case kFrameTypeCentroid: andrewm@0: if(verbose_ >= 3) andrewm@0: cout << "Received centroid data\n"; andrewm@0: processCentroidFrame(&frame[1], length - 1); andrewm@0: break; andrewm@0: case kFrameTypeRawKeyData: andrewm@0: if(verbose_ >= 3) andrewm@0: cout << "Received raw key data\n"; andrewm@0: processRawDataFrame(&frame[1], length - 1); andrewm@0: break; andrewm@0: case kFrameTypeAnalog: andrewm@0: if(verbose_ >= 3) andrewm@0: cout << "Received analog data\n"; andrewm@0: processAnalogFrame(&frame[1], length - 1); andrewm@0: break; andrewm@0: case kFrameTypeErrorMessage: andrewm@0: if(verbose_ >= 3) andrewm@0: cout << "Received error data\n"; andrewm@0: processErrorMessageFrame(&frame[1], length-1); andrewm@0: break; andrewm@0: case kFrameTypeI2CResponse: andrewm@0: if(verbose_ >= 3) andrewm@0: cout << "Received I2C response\n"; andrewm@0: processI2CResponseFrame(&frame[1], length - 1); andrewm@0: break; andrewm@0: case kFrameTypeStatus: andrewm@0: default: andrewm@0: if(verbose_ >= 3) andrewm@0: cout << "Received frame type " << (int)frame[0] << endl; andrewm@0: break; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Process a frame of data containing centroid values (the default mode of scanning) andrewm@0: void TouchkeyDevice::processCentroidFrame(unsigned char * const buffer, const int bufferLength) { andrewm@0: int frame, octave, bufferIndex; andrewm@0: andrewm@0: // Old and new generation devices structure the frame differently. andrewm@0: if((deviceSoftwareVersion_ <= 0 && bufferLength < 3) || (deviceSoftwareVersion_ > 0 && bufferLength < 5)) { andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: ignoring malformed centroid frame of " << bufferLength << " bytes, less than minimum 3\n"; andrewm@0: if(verbose_ >= 2) { andrewm@0: cout << " Contents: "; andrewm@0: hexDump(cout, buffer, bufferLength); andrewm@0: cout << endl; andrewm@0: } andrewm@0: return; andrewm@0: } andrewm@0: andrewm@0: if(verbose_ >= 4) { andrewm@0: cout << "Centroid frame contents: "; andrewm@0: hexDump(cout, buffer, bufferLength); andrewm@0: cout << endl; andrewm@0: } andrewm@0: andrewm@0: // Parse the octave and timestamp differently depending on hardware version andrewm@0: if(deviceSoftwareVersion_ > 0) { andrewm@0: octave = buffer[0]; // First byte is octave andrewm@0: andrewm@0: // Frame is stored as 32-bit little endian value andrewm@0: frame = buffer[1] + ((int)buffer[2] << 8) + ((int)buffer[3] << 16) + ((int)buffer[4] << 24); andrewm@0: bufferIndex = 5; andrewm@0: andrewm@0: if(verbose_ >= 3) andrewm@0: cout << "Centroid frame octave " << octave << " timestamp " << frame << endl; andrewm@0: } andrewm@0: else { andrewm@0: frame = (buffer[0] << 8) + buffer[1]; // First two bytes give us the timestamp in milliseconds (mod 2^16) andrewm@0: octave = buffer[2]; // Third byte tells us which octave of keys is being addressed andrewm@0: bufferIndex = 3; andrewm@0: } andrewm@0: andrewm@0: // Convert from device frame number (expressed in USB 1ms SOF intervals) to a system andrewm@0: // timestamp that can be synchronized with other data streams andrewm@0: lastTimestamp_ = timestampSynchronizer_.synchronizedTimestamp(frame); andrewm@0: andrewm@0: //ioMutex_.enter(); andrewm@0: andrewm@0: while(bufferIndex < bufferLength) { andrewm@0: // First byte tells us the number of the key (0-12); next bytes hold the data frame andrewm@0: int key = (int)buffer[bufferIndex++]; andrewm@0: int bytesParsed = processKeyCentroid(frame,octave, key, lastTimestamp_, &buffer[bufferIndex], bufferLength - bufferIndex); andrewm@0: andrewm@0: if(bytesParsed < 0) { andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: malformed data frame (parsing key " << key << " at byte " << bufferIndex << ")\n"; andrewm@0: andrewm@0: if(verbose_ >= 2) { andrewm@0: cout << "--> Data: "; andrewm@0: hexDump(cout, buffer, bufferLength); andrewm@0: cout << endl; andrewm@0: } andrewm@0: andrewm@0: break; andrewm@0: } andrewm@0: andrewm@0: bufferIndex += bytesParsed; andrewm@0: } andrewm@0: andrewm@0: if(updatedLowestMidiNote_ != lowestMidiNote_) { andrewm@0: int keyPresentDifference = (lowestKeyPresentMidiNote_ - lowestMidiNote_); andrewm@0: andrewm@0: lowestMidiNote_ = updatedLowestMidiNote_; andrewm@0: lowestKeyPresentMidiNote_ = lowestMidiNote_ + keyPresentDifference; andrewm@0: andrewm@0: // Turn off all existing touches before changing the octave andrewm@0: // so we don't end up with orphan touches when the "off" message is andrewm@0: // sent to a different octave than the "on" andrewm@0: for(int i = 0; i <= 127; i++) andrewm@0: if(keyboard_.key(i) != 0) andrewm@0: if(keyboard_.key(i)->touchIsActive()) andrewm@0: keyboard_.key(i)->touchOff(lastTimestamp_); andrewm@0: andrewm@48: keyboard_.setKeyboardGUIRange(lowestKeyPresentMidiNote_, lowestMidiNote_ + 12*numOctaves_ + lowestNotePerOctave_); andrewm@0: } andrewm@0: andrewm@0: //ioMutex_.exit(); andrewm@0: } andrewm@0: andrewm@0: // Process a frame containing raw key data, whose configuration was set with startRawDataCollection() andrewm@0: // First byte holds the octave that the data came from. andrewm@0: andrewm@0: void TouchkeyDevice::processRawDataFrame(unsigned char * const buffer, const int bufferLength) { andrewm@0: int octave = buffer[0]; andrewm@0: andrewm@0: if(verbose_ >= 3) andrewm@0: cout << "Raw data frame from octave " << octave << " contains " << bufferLength - 1 << " samples\n"; andrewm@0: andrewm@0: if(verbose_ >= 4) { andrewm@0: cout << " "; andrewm@0: hexDump(cout, &buffer[1], bufferLength - 1); andrewm@0: cout << endl; andrewm@0: } andrewm@17: andrewm@17: // Change the first byte to contain the note number this data is expected to have come andrewm@17: // from (based on which key we are presently querying) andrewm@17: buffer[0] = lowestMidiNote_ + (rawDataCurrentOctave_ * 12 + rawDataCurrentKey_); andrewm@0: andrewm@0: // Send raw data as an OSC blob andrewm@0: lo_blob b = lo_blob_new(bufferLength, buffer); andrewm@0: keyboard_.sendMessage("/touchkeys/rawbytes", "b", b, LO_ARGS_END); andrewm@0: lo_blob_free(b); andrewm@0: } andrewm@0: andrewm@0: // Extract the floating-point centroid data for a key from packed character input. andrewm@0: // Send OSC features as appropriate andrewm@0: andrewm@17: int TouchkeyDevice::processKeyCentroid(int frame, int octave, int key, timestamp_type timestamp, unsigned char * buffer, int maxLength) { andrewm@0: int touchCount = 0; andrewm@0: andrewm@0: float sliderPosition[3]; andrewm@0: float sliderPositionH; andrewm@0: float sliderSize[3]; andrewm@0: andrewm@0: int bytesParsed; andrewm@0: andrewm@0: if(key < 0 || key > 12 || maxLength < 1) andrewm@0: return -1; andrewm@0: andrewm@0: int white = (kKeyColor[key] == kKeyColorWhite); andrewm@0: int midiNote = octaveKeyToMidi(octave, key); andrewm@0: andrewm@0: // Check that the received data is actually valid and not left over from a previous scan (which andrewm@0: // can happen when the scan rate is too high). 0x88 is a special "warning" marker for this case andrewm@0: // since it will never be part of a valid centroid. andrewm@0: andrewm@0: if(buffer[0] == 0x88) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Warning: octave " << octave << " key " << key << " data is not ready. Check scan rate.\n"; andrewm@0: if(deviceSoftwareVersion_ >= 1) andrewm@0: return white ? expectedLengthWhite_ : expectedLengthBlack_; andrewm@0: else andrewm@0: return 1; andrewm@0: } andrewm@0: andrewm@0: // A value of 0xFF means that no touch is active, and no further data will be present on this key. andrewm@0: andrewm@0: if(buffer[0] == 0xFF && deviceSoftwareVersion_ <= 0) { andrewm@0: bytesParsed = 1; andrewm@0: sliderPosition[0] = sliderPosition[1] = sliderPosition[2] = -1.0; andrewm@0: sliderSize[0] = sliderSize[1] = sliderSize[2] = 0.0; andrewm@0: sliderPositionH = -1.0; andrewm@0: andrewm@0: if(verbose_ >= 4) { andrewm@0: cout << "Octave " << octave << " Key " << key << " (TS " << timestamp << "): ff\n"; andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: bytesParsed = white ? expectedLengthWhite_ : expectedLengthBlack_; andrewm@0: andrewm@0: if(bytesParsed > maxLength) // Make sure there's enough buffer left to process this key andrewm@0: return -1; andrewm@0: andrewm@0: int rawSliderPosition[3]; andrewm@0: int rawSliderPositionH; andrewm@0: andrewm@0: rawSliderPosition[0] = (((buffer[0] & 0xF0) << 4) + buffer[1]); andrewm@0: rawSliderPosition[1] = (((buffer[0] & 0x0F) << 8) + buffer[2]); andrewm@0: rawSliderPosition[2] = (((buffer[3] & 0xF0) << 4) + buffer[4]); andrewm@0: andrewm@0: if(deviceHardwareVersion_ >= 2) andrewm@0: { andrewm@0: // Always an H value with version 2 sensor hardware andrewm@0: rawSliderPositionH = (((buffer[3] & 0x0F) << 8) + buffer[5]); andrewm@0: andrewm@0: if(white) { andrewm@0: for(int i = 0; i < 3; i++) { andrewm@0: if(rawSliderPosition[i] != 0x0FFF) { // 0x0FFF means no touch andrewm@0: sliderPosition[i] = (float)rawSliderPosition[i] / whiteMaxY_; andrewm@0: sliderSize[i] = (float)buffer[i + 6] / kSizeMaxValue; andrewm@0: touchCount++; andrewm@0: } andrewm@0: else { andrewm@0: sliderPosition[i] = -1.0; andrewm@0: sliderSize[i] = 0.0; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: for(int i = 0; i < 3; i++) { andrewm@0: if(rawSliderPosition[i] != 0x0FFF) { // 0x0FFF means no touch andrewm@0: sliderPosition[i] = (float)rawSliderPosition[i] / blackMaxY_; andrewm@0: sliderSize[i] = (float)buffer[i + 6] / kSizeMaxValue; andrewm@0: touchCount++; andrewm@0: } andrewm@0: else { andrewm@0: sliderPosition[i] = -1.0; andrewm@0: sliderSize[i] = 0.0; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: else andrewm@0: { andrewm@0: // H value only on white keys with version 0-1 sensor hardware andrewm@0: andrewm@0: if(white) { andrewm@0: rawSliderPositionH = (((buffer[3] & 0x0F) << 8) + buffer[5]); andrewm@0: andrewm@0: for(int i = 0; i < 3; i++) { andrewm@0: if(rawSliderPosition[i] != 0x0FFF) { // 0x0FFF means no touch andrewm@0: sliderPosition[i] = (float)rawSliderPosition[i] / whiteMaxY_; andrewm@0: sliderSize[i] = (float)buffer[i + 6] / kSizeMaxValue; andrewm@0: touchCount++; andrewm@0: } andrewm@0: else { andrewm@0: sliderPosition[i] = -1.0; andrewm@0: sliderSize[i] = 0.0; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: rawSliderPositionH = 0x0FFF; andrewm@0: andrewm@0: for(int i = 0; i < 3; i++) { andrewm@0: if(rawSliderPosition[i] != 0x0FFF) { // 0x0FFF means no touch andrewm@0: sliderPosition[i] = (float)rawSliderPosition[i] / blackMaxY_; andrewm@0: sliderSize[i] = (float)buffer[i + 5] / kSizeMaxValue; andrewm@0: touchCount++; andrewm@0: } andrewm@0: else { andrewm@0: sliderPosition[i] = -1.0; andrewm@0: sliderSize[i] = 0.0; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: if(rawSliderPositionH != 0x0FFF) { andrewm@0: sliderPositionH = (float)rawSliderPositionH / whiteMaxX_ ; andrewm@0: } andrewm@0: else andrewm@0: sliderPositionH = -1.0; andrewm@0: andrewm@0: if(verbose_ >= 4) { andrewm@0: cout << "Octave " << octave << " Key " << key << ": "; andrewm@0: hexDump(cout, buffer, white ? expectedLengthWhite_ : expectedLengthBlack_); andrewm@0: cout << endl; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Sanity check: do we have the PianoKey structure available to receive this data? andrewm@0: // If not, no need to proceed further. andrewm@0: if(keyboard_.key(midiNote) == 0) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Warning: No PianoKey available for touchkey MIDI note " << midiNote << endl; andrewm@0: return bytesParsed; andrewm@0: } andrewm@0: andrewm@0: // From here on out, grab the performance data mutex so no MIDI events can show up in the middle andrewm@0: ScopedLock ksl(keyboard_.performanceDataMutex_); andrewm@0: andrewm@0: // Turn off touch activity on this key if there's no active touches andrewm@0: if(touchCount == 0) { andrewm@0: if(keyboard_.key(midiNote)->touchIsActive()) andrewm@0: { andrewm@0: keyboard_.key(midiNote)->touchOff(timestamp); andrewm@0: KeyTouchFrame newFrame(0, sliderPosition, sliderSize, sliderPositionH, white); andrewm@0: andrewm@0: if (loggingActive_) andrewm@0: { andrewm@0: //////////////////////////////////////////////////////// andrewm@0: //////////////////////////////////////////////////////// andrewm@0: //////////////////// BEGIN LOGGING ///////////////////// andrewm@0: andrewm@0: keyTouchLog_.write((char*)×tamp, sizeof(timestamp_type)); andrewm@0: keyTouchLog_.write((char*)&frame, sizeof(int)); andrewm@0: keyTouchLog_.write((char*)&midiNote, sizeof(int)); andrewm@0: keyTouchLog_.write((char*)&newFrame, sizeof(KeyTouchFrame)); andrewm@0: andrewm@0: ///////////////////// END LOGGING ////////////////////// andrewm@0: //////////////////////////////////////////////////////// andrewm@0: //////////////////////////////////////////////////////// andrewm@0: } andrewm@0: andrewm@0: // Send raw OSC message if enabled andrewm@0: if(sendRawOscMessages_) { andrewm@0: keyboard_.sendMessage("/touchkeys/raw-off", "iii", andrewm@0: octave, key, frame, andrewm@0: LO_ARGS_END ); andrewm@0: } andrewm@0: andrewm@0: } andrewm@0: andrewm@0: return bytesParsed; andrewm@0: } andrewm@0: andrewm@0: // At this point, construct a new frame with this data and pass it to the PianoKey for andrewm@0: // further processing. Leave the ID fields empty; these are state-dependent and will andrewm@0: // be worked out based on the previous frames. andrewm@0: andrewm@0: KeyTouchFrame newFrame(touchCount, sliderPosition, sliderSize, sliderPositionH, white); andrewm@0: andrewm@0: keyboard_.key(midiNote)->touchInsertFrame(newFrame, timestamp); andrewm@0: andrewm@0: andrewm@0: if (loggingActive_) andrewm@0: { andrewm@0: //////////////////////////////////////////////////////// andrewm@0: //////////////////////////////////////////////////////// andrewm@0: //////////////////// BEGIN LOGGING ///////////////////// andrewm@0: andrewm@0: keyTouchLog_.write((char*)×tamp, sizeof(timestamp_type)); andrewm@0: keyTouchLog_.write((char*)&frame, sizeof(int)); andrewm@0: keyTouchLog_.write((char*)&midiNote, sizeof(int)); andrewm@0: keyTouchLog_.write((char*)&newFrame, sizeof(KeyTouchFrame)); andrewm@0: andrewm@0: ///////////////////// END LOGGING ////////////////////// andrewm@0: //////////////////////////////////////////////////////// andrewm@0: //////////////////////////////////////////////////////// andrewm@0: } andrewm@0: andrewm@0: // Send raw OSC message if enabled andrewm@0: if(sendRawOscMessages_) { andrewm@0: keyboard_.sendMessage("/touchkeys/raw", "iiifffffff", andrewm@0: octave, key, frame, andrewm@0: sliderPosition[0], andrewm@0: sliderSize[0], andrewm@0: sliderPosition[1], andrewm@0: sliderSize[1], andrewm@0: sliderPosition[2], andrewm@0: sliderSize[2], andrewm@0: sliderPositionH, andrewm@0: LO_ARGS_END ); andrewm@0: } andrewm@0: andrewm@0: // Verbose logging of key info andrewm@0: if(verbose_ >= 3) { andrewm@0: cout << "Octave " << octave << " Key " << key << " (TS " << timestamp << "): "; andrewm@0: cout << sliderPositionH << " "; andrewm@0: cout << sliderPosition[0] << " " << sliderPosition[1] << " " << sliderPosition[2] << " "; andrewm@0: cout << sliderSize[0] << " " << sliderSize[1] << " " << sliderSize[2] << endl; andrewm@0: } andrewm@0: andrewm@0: return bytesParsed; andrewm@0: } andrewm@0: andrewm@0: // Process a frame of data containing analog values (i.e. key angle, Z-axis). These andrewm@0: // always come as a group for a whole board, and should be parsed apart into individual keys andrewm@0: void TouchkeyDevice::processAnalogFrame(unsigned char * const buffer, const int bufferLength) { andrewm@0: // Format: [Octave] [TS0] [TS1] [TS2] [TS3] [Key0L] [Key0H] [Key1L] [Key1H] ... [Key24L] [Key24H] andrewm@0: // ... (more frames) andrewm@0: // [TS0] [TS1] [TS2] [TS3] [Key0L] [Key0H] [Key1L] [Key1H] ... [Key24L] [Key24H] andrewm@0: andrewm@0: if(bufferLength < 1) { andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: ignoring malformed analog frame of " << bufferLength << " bytes, less than minimum 1\n"; andrewm@0: return; andrewm@0: } andrewm@0: andrewm@0: int octave = buffer[0]; andrewm@0: int board = octave / 2; andrewm@0: int frame; andrewm@0: int bufferIndex = 1; andrewm@0: int midiNote, value; andrewm@0: andrewm@0: // Parse the buffer one frame at a time andrewm@0: while(bufferIndex < bufferLength) { andrewm@0: if(bufferLength - bufferIndex < 54) { andrewm@0: // This condition indicates a malformed analog frame (not enough data) andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: ignoring extra analog data of " << bufferLength - bufferIndex << " bytes, less than full frame 54 (total " << bufferLength << ")\n"; andrewm@0: break; andrewm@0: } andrewm@0: andrewm@0: // Find the timestamp (i.e. frame ID generated by the device). 32-bit little-endian. andrewm@0: frame = buffer[bufferIndex] + ((int)buffer[bufferIndex+1] << 8) + andrewm@0: ((int)buffer[bufferIndex+2] << 16) + ((int)buffer[bufferIndex+3] << 24); andrewm@0: andrewm@0: // Check the timestamp against the last frame from this board to see if any frames have been dropped andrewm@0: if(frame > analogLastFrame_[board] + 1) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "WARNING: dropped frame(s) on board " << board << " at " << frame << " (last was " << analogLastFrame_[board] << ")" << endl; andrewm@0: } andrewm@0: else if(frame < analogLastFrame_[board] + 1) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "WARNING: repeat frame(s) on board " << board << " at " << frame << " (last was " << analogLastFrame_[board] << ")" << endl; andrewm@0: } andrewm@0: analogLastFrame_[board] = frame; andrewm@0: andrewm@0: // TESTING andrewm@0: /*if(verbose_ >= 3 || (frame % 500 == 0)) andrewm@0: cout << "Analog frame octave " << octave << " timestamp " << frame << endl; andrewm@0: if(verbose_ >= 4 || (frame % 500 == 0)) { andrewm@0: cout << "Values: "; andrewm@0: for(int i = 0; i < 25; i++) { andrewm@0: cout << std::setw(5) << (((signed char)buffer[i*2 + 6])*256 + buffer[i*2 + 5]) << " "; andrewm@0: } andrewm@0: cout << endl; andrewm@0: }*/ andrewm@0: andrewm@0: // Process key values individually and add them to the keyboard data structure andrewm@0: for(int key = 0; key < 25; key++) { andrewm@0: // Every analog frame contains 25 values, however only the top board actually uses all 25 andrewm@0: // sensors. There are several "high C" values in the lower boards (i.e. key == 24) which andrewm@0: // do not correspond to real sensors. These should be ignored. andrewm@0: if(key == 24 && octave != numberOfOctaves() - 2) andrewm@0: continue; andrewm@0: andrewm@0: midiNote = octaveKeyToMidi(octave, key); andrewm@0: andrewm@0: // Check that this note is in range to the available calibrators and keys. andrewm@0: if(keyboard_.key(midiNote) == 0 || (octave*12 + key) >= keyCalibratorsLength_ || midiNote < 21) andrewm@0: continue; andrewm@0: andrewm@0: // Pull the value out from the packed buffer (little endian 16 bit) andrewm@0: value = (((signed char)buffer[key*2 + 6])*256 + buffer[key*2 + 5]); andrewm@0: andrewm@0: // Calibrate the value, assuming the calibrator is ready and running andrewm@0: key_position calibratedPosition = keyCalibrators_[octave*12 + key]->evaluate(value); andrewm@0: if(!missing_value::isMissing(calibratedPosition)) { andrewm@0: timestamp_type timestamp = timestampSynchronizer_.synchronizedTimestamp(frame); andrewm@0: keyboard_.key(midiNote)->insertSample(calibratedPosition, timestamp); andrewm@0: } andrewm@0: else if(keyboard_.gui() != 0){ andrewm@0: andrewm@0: //keyboard_.key(midiNote)->insertSample((float)value / 4096.0, timestampSynchronizer_.synchronizedTimestamp(frame)); andrewm@0: andrewm@0: // Update the GUI but don't actually save the value since it's uncalibrated andrewm@0: keyboard_.gui()->setAnalogValueForKey(midiNote, (float)value / kTouchkeyAnalogValueMax); andrewm@0: andrewm@7: if(keyCalibrators_[octave*12 + key]->calibrationStatus() == kPianoKeyCalibrated) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "key " << midiNote << " calibrated but missing (raw value " << value << ")\n"; andrewm@7: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: if(loggingActive_) { andrewm@0: analogLog_.write((char*)&buffer[0], 1); // Octave number andrewm@0: analogLog_.write((char*)&buffer[bufferIndex], 54); andrewm@0: } andrewm@0: andrewm@0: // Skip to next frame andrewm@0: bufferIndex += 54; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Process a frame containing a human-readable (and machine-coded) error message generated andrewm@0: // internally by the device andrewm@0: void TouchkeyDevice::processErrorMessageFrame(unsigned char * const buffer, const int bufferLength) { andrewm@0: char msg[256]; andrewm@0: int len = bufferLength - 5; andrewm@0: andrewm@0: // Error on error message frame! andrewm@0: if(bufferLength < 5) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Warning: received error message frame of " << bufferLength << " bytes, less than minimum 5\n"; andrewm@0: return; andrewm@0: } andrewm@0: andrewm@0: // Limit length of string for safety reasons andrewm@0: if(len > 256) andrewm@0: len = 256; andrewm@0: memcpy(msg, &buffer[5], len * sizeof(char)); andrewm@0: msg[len - 1] = '\0'; andrewm@0: andrewm@0: // Print the error andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Error frame received: " << msg << endl; andrewm@0: andrewm@0: // Dump the buffer containing error coding information andrewm@0: if(verbose_ >= 2) { andrewm@0: cout << "Contents: "; andrewm@0: hexDump(cout, buffer, 5); andrewm@0: cout << endl; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Process a frame containing a response to an I2C command. We can use this to gather andrewm@0: // raw information from the key. andrewm@0: void TouchkeyDevice::processI2CResponseFrame(unsigned char * const buffer, const int bufferLength) { andrewm@0: // Format: [octave] [key] [length] andrewm@0: andrewm@0: if(bufferLength < 3) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Warning: received I2C response frame of " << bufferLength << " bytes, less than minimum 3\n"; andrewm@0: return; andrewm@0: } andrewm@0: andrewm@0: int octave = buffer[0]; andrewm@0: int key = buffer[1]; andrewm@0: int responseLength = buffer[2]; andrewm@0: andrewm@0: if(bufferLength < responseLength + 3) { andrewm@7: if(verbose_ >= 1) { andrewm@7: cout << "Warning: received malformed I2C response (octave " << octave << ", key " << key << ", length " << responseLength; andrewm@7: cout << ") but only " << bufferLength - 3 << " bytes of data\n"; andrewm@7: } andrewm@17: if(verbose_ >= 4) { andrewm@17: cout << " "; andrewm@17: hexDump(cout, &buffer[3], bufferLength - 3); andrewm@17: cout << endl; andrewm@17: } andrewm@0: andrewm@0: responseLength = bufferLength - 3; andrewm@0: } andrewm@0: else { andrewm@0: if(verbose_ >= 3) { andrewm@0: cout << "I2C response from octave " << octave << ", key " << key << ", length " << responseLength << endl; andrewm@0: } andrewm@17: if(verbose_ >= 4) { andrewm@17: cout << " "; andrewm@17: hexDump(cout, &buffer[3], responseLength); andrewm@17: cout << endl; andrewm@17: } andrewm@0: } andrewm@0: andrewm@0: if(sensorDisplay_ != 0) { andrewm@0: // Copy response data to display andrewm@0: vector data; andrewm@0: andrewm@0: for(int i = 3; i < responseLength + 3; i++) { andrewm@0: data.push_back(buffer[i]); andrewm@0: } andrewm@0: sensorDisplay_->setDisplayData(data); andrewm@0: } andrewm@17: andrewm@17: // Change the first byte to contain the note number this data is expected to have come andrewm@17: // from (based on which key we are presently querying) andrewm@17: buffer[2] = lowestMidiNote_ + (octave * 12 + key); andrewm@17: andrewm@17: // Send raw data as an OSC blob andrewm@17: lo_blob b = lo_blob_new(responseLength + 1, &buffer[2]); andrewm@17: keyboard_.sendMessage("/touchkeys/rawbytes", "b", b, LO_ARGS_END); andrewm@17: lo_blob_free(b); andrewm@0: } andrewm@0: andrewm@0: // Parse raw data from a status request. Buffer should start immediately after the andrewm@0: // frame type byte, and processing will finish either at the end of the expected buffer, andrewm@0: // or at the given length, whichever comes first. Returns true if a status buffer was andrewm@0: // successfully received. andrewm@0: andrewm@0: bool TouchkeyDevice::processStatusFrame(unsigned char * buffer, int maxLength, TouchkeyDevice::ControllerStatus *status) { andrewm@0: if((status == 0 || maxLength < 5) && verbose_ >= 1) { andrewm@0: cout << "Invalid status frame: "; andrewm@0: hexDump(cout, buffer, maxLength); andrewm@0: cout << endl; andrewm@0: return false; andrewm@0: } andrewm@0: andrewm@0: status->hardwareVersion = buffer[0]; andrewm@0: status->softwareVersionMajor = buffer[1]; andrewm@0: status->softwareVersionMinor = buffer[2]; andrewm@0: status->running = ((buffer[3] & kStatusFlagRunning) != 0); andrewm@0: status->octaves = buffer[4]; andrewm@0: status->connectedKeys = (unsigned int *)malloc(2*status->octaves*sizeof(unsigned int)); andrewm@0: andrewm@0: int i, oct = 0; // Get connected key information andrewm@0: if(status->softwareVersionMajor >= 2) { andrewm@0: // One extra byte holds lowest physical sensor andrewm@0: status->lowestHardwareNote = buffer[5]; andrewm@0: status->hasTouchSensors = ((buffer[3] & kStatusFlagHasI2C) != 0); andrewm@0: status->hasAnalogSensors = ((buffer[3] & kStatusFlagHasAnalog) != 0); andrewm@0: status->hasRGBLEDs = ((buffer[3] & kStatusFlagHasRGBLED) != 0); andrewm@0: i = 6; andrewm@0: } andrewm@0: else { andrewm@0: status->lowestHardwareNote = 0; andrewm@0: status->hasTouchSensors = true; andrewm@0: status->hasAnalogSensors = true; andrewm@0: status->hasRGBLEDs = true; andrewm@0: i = 5; andrewm@0: } andrewm@0: andrewm@0: while(i+1 < maxLength) { andrewm@0: status->connectedKeys[oct] = 256*buffer[i] + buffer[i+1]; andrewm@0: i += 2; andrewm@0: oct++; andrewm@0: } andrewm@0: andrewm@0: if(oct < status->octaves && verbose_ >= 1) { andrewm@0: cout << "Invalid status frame: "; andrewm@0: hexDump(cout, buffer, maxLength); andrewm@0: cout << endl; andrewm@0: return false; andrewm@0: } andrewm@0: andrewm@0: return true; andrewm@0: } andrewm@0: andrewm@17: // Prepare the indicated key for raw data collection andrewm@17: void TouchkeyDevice::rawDataPrepareCollection(int octave, int key, int mode, int scaler) { andrewm@22: Thread::sleep(10); andrewm@17: andrewm@17: // Command to set the mode of the key andrewm@17: unsigned char commandSetMode[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, andrewm@17: kFrameTypeSendI2CCommand, (unsigned char)octave, (unsigned char)key, andrewm@17: 3 /* xmit */, 0 /* response */, 0 /* command offset */, 1 /* mode */, (unsigned char)mode, andrewm@17: ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@17: andrewm@22: if(deviceWrite((char*)commandSetMode, 12) < 0) { andrewm@17: if(verbose_ >= 1) andrewm@17: cout << "ERROR: unable to write setMode command. errno = " << errno << endl; andrewm@17: } andrewm@20: andrewm@22: Thread::sleep(10); andrewm@17: andrewm@17: // Command to set the scaler of the key andrewm@17: unsigned char commandSetScaler[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, andrewm@17: kFrameTypeSendI2CCommand, (unsigned char)octave, (unsigned char)key, andrewm@17: 3 /* xmit */, 0 /* response */, 0 /* command offset */, 3 /* raw scaler */, (unsigned char)scaler, andrewm@17: ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@17: andrewm@22: if(deviceWrite((char*)commandSetScaler, 12) < 0) { andrewm@17: if(verbose_ >= 1) andrewm@17: cout << "ERROR: unable to write setMode command. errno = " << errno << endl; andrewm@17: } andrewm@19: andrewm@22: Thread::sleep(10); andrewm@17: andrewm@17: unsigned char commandPrepareRead[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, andrewm@17: kFrameTypeSendI2CCommand, (unsigned char)octave, (unsigned char)key, andrewm@17: 1 /* xmit */, 0 /* response */, 6 /* data offset */, andrewm@17: ESCAPE_CHARACTER, kControlCharacterFrameEnd}; andrewm@17: andrewm@22: if(deviceWrite((char*)commandPrepareRead, 10) < 0) { andrewm@17: if(verbose_ >= 1) andrewm@17: cout << "ERROR: unable to write prepareRead command. errno = " << errno << endl; andrewm@17: } andrewm@20: andrewm@22: Thread::sleep(10); andrewm@17: andrewm@17: rawDataShouldChangeMode_ = false; andrewm@17: } andrewm@17: andrewm@0: // Check for an ACK response from the device. Returns true if found. Returns andrewm@0: // false if NAK received, or if a timeout occurs. andrewm@0: // TODO: this implementation needs to change to not chew up other data coming in. andrewm@0: andrewm@0: bool TouchkeyDevice::checkForAck(int timeoutMilliseconds) { andrewm@0: //struct timeval startTime, currentTime; andrewm@0: bool controlSeq = false; andrewm@0: unsigned char ch; andrewm@0: andrewm@0: double startTime = Time::getMillisecondCounterHiRes(); andrewm@0: double currentTime = startTime; andrewm@0: andrewm@0: //gettimeofday(&startTime, 0); andrewm@0: //gettimeofday(¤tTime, 0); andrewm@0: andrewm@0: while(currentTime - startTime < (double)timeoutMilliseconds) { andrewm@22: long count = deviceRead((char *)&ch, 1); andrewm@20: andrewm@0: if(count < 0) { // Check if an error occurred on read andrewm@0: if(errno != EAGAIN) { andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Unable to read from device while waiting for ACK (error " << errno << "). Aborting.\n"; andrewm@0: return false; andrewm@0: } andrewm@0: } andrewm@0: else if(count > 0) { // Data received andrewm@0: // Wait for a sequence {ESCAPE_CHARACTER, ACK} or {ESCAPE_CHARACTER, NAK} andrewm@0: if(controlSeq) { andrewm@0: controlSeq = false; andrewm@0: if(ch == kControlCharacterAck) { andrewm@0: if(verbose_ >= 2) andrewm@0: cout << "Received ACK\n"; andrewm@0: return true; andrewm@0: } andrewm@0: else if(ch == kControlCharacterNak) { andrewm@0: if(verbose_ >= 1) andrewm@0: cout << "Warning: received NAK\n"; andrewm@0: return false; andrewm@0: } andrewm@0: } andrewm@0: else if(ch == ESCAPE_CHARACTER) andrewm@0: controlSeq = true; andrewm@0: } andrewm@0: andrewm@0: currentTime = Time::getMillisecondCounterHiRes(); andrewm@0: } andrewm@0: andrewm@7: if(verbose_ >= 1) andrewm@7: cout << "Error: timeout waiting for ACK\n"; andrewm@0: return false; andrewm@0: } andrewm@0: andrewm@0: // Convenience method to dump hexadecimal output andrewm@0: void TouchkeyDevice::hexDump(ostream& str, unsigned char * buffer, int length) { andrewm@0: if(length <= 0) andrewm@0: return; andrewm@0: str << std::hex << (int)buffer[0]; andrewm@0: for(int i = 1; i < length; i++) { andrewm@0: str << " " << (int)buffer[i]; andrewm@0: } andrewm@0: str << std::dec; andrewm@0: } andrewm@0: andrewm@22: // Read from the TouchKeys device andrewm@22: long TouchkeyDevice::deviceRead(char *buffer, unsigned int count) { andrewm@22: #ifdef _MSC_VER andrewm@23: int n; andrewm@23: andrewm@23: if(!ReadFile(serialHandle_, buffer, count, (LPDWORD)((void *)&n), NULL)) andrewm@23: return -1; andrewm@23: return n; andrewm@22: #else andrewm@22: return read(device_, buffer, count); andrewm@22: #endif andrewm@22: } andrewm@22: andrewm@22: // Write to the TouchKeys device andrewm@22: int TouchkeyDevice::deviceWrite(char *buffer, unsigned int count) { andrewm@23: int result; andrewm@22: andrewm@22: #ifdef _MSC_VER andrewm@23: if(!WriteFile(serialHandle_, buffer, count, (LPDWORD)((void *)&result), NULL)) andrewm@23: return -1; andrewm@22: #else andrewm@22: result = write(device_, buffer, count); andrewm@22: #endif andrewm@22: deviceDrainOutput(); andrewm@22: return result; andrewm@22: } andrewm@22: andrewm@23: // Flush (discard) the TouchKeys device input andrewm@22: void TouchkeyDevice::deviceFlush(bool bothDirections) { andrewm@22: #ifdef _MSC_VER andrewm@23: // WINDOWS_TODO (?) andrewm@22: #else andrewm@22: if(bothDirections) andrewm@22: tcflush(device_, TCIOFLUSH); andrewm@22: else andrewm@22: tcflush(device_, TCIFLUSH); // Flush device input andrewm@22: #endif andrewm@22: } andrewm@22: andrewm@23: // Flush the TouchKeys device output andrewm@22: void TouchkeyDevice::deviceDrainOutput() { andrewm@22: #ifdef _MSC_VER andrewm@23: FlushFileBuffers(serialHandle_); andrewm@22: #else andrewm@22: tcdrain(device_); andrewm@22: #endif andrewm@22: } andrewm@22: andrewm@22: andrewm@0: TouchkeyDevice::~TouchkeyDevice() { andrewm@0: if (logFileCreated_) andrewm@0: { andrewm@0: keyTouchLog_.close(); andrewm@0: analogLog_.close(); andrewm@0: } andrewm@0: andrewm@0: closeDevice(); andrewm@0: calibrationDeinit(); andrewm@0: }