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: }