# HG changeset patch # User Andrew McPherson # Date 1483396179 0 # Node ID ff5d65c69e73f10052d56e1e02521dc200742f59 # Parent 18af051648943eb3706729d35579f0a011511ad2 Updates to control passthrough, log playback, firmware update ability diff -r 18af05164894 -r ff5d65c69e73 Source/GUI/KeyboardZoneComponent.cpp --- a/Source/GUI/KeyboardZoneComponent.cpp Tue May 12 18:19:05 2015 +0100 +++ b/Source/GUI/KeyboardZoneComponent.cpp Mon Jan 02 22:29:39 2017 +0000 @@ -644,6 +644,7 @@ menu.addItem(MidiKeyboardSegment::kControlPitchWheel, "Pitch Wheel", true, keyboardSegment_->usesKeyboardPitchWheel()); menu.addItem(MidiKeyboardSegment::kControlChannelAftertouch, "Aftertouch", true, keyboardSegment_->usesKeyboardChannnelPressure()); menu.addItem(1, "CC 1 (Mod Wheel)", true, keyboardSegment_->usesKeyboardModWheel()); + menu.addItem(kKeyboardControllerRetransmitPedals, "Pedals", true, keyboardSegment_->usesKeyboardPedals()); menu.addItem(kKeyboardControllerRetransmitOthers, "Other Controllers", true, keyboardSegment_->usesKeyboardMIDIControllers()); menu.showMenuAsync(PopupMenu::Options().withTargetComponent(keyboardControllersButton), @@ -682,6 +683,9 @@ else if(result == 1) { // ModWheel == CC 1 keyboardSegment_->setUsesKeyboardModWheel(!keyboardSegment_->usesKeyboardModWheel()); } + else if(result == kKeyboardControllerRetransmitPedals) { + keyboardSegment_->setUsesKeyboardPedals(!keyboardSegment_->usesKeyboardPedals()); + } else if(result == kKeyboardControllerRetransmitOthers) { keyboardSegment_->setUsesKeyboardMIDIControllers(!keyboardSegment_->usesKeyboardMIDIControllers()); } diff -r 18af05164894 -r ff5d65c69e73 Source/GUI/KeyboardZoneComponent.h --- a/Source/GUI/KeyboardZoneComponent.h Tue May 12 18:19:05 2015 +0100 +++ b/Source/GUI/KeyboardZoneComponent.h Mon Jan 02 22:29:39 2017 +0000 @@ -122,7 +122,8 @@ enum { // Special commands for keyboard controller popup button kKeyboardControllerRetransmitOthers = 2000, - kKeyboardControllerSendPitchWheelRange + kKeyboardControllerSendPitchWheelRange, + kKeyboardControllerRetransmitPedals }; // Update list of MIDI output devices diff -r 18af05164894 -r ff5d65c69e73 Source/GUI/MainWindow.cpp --- a/Source/GUI/MainWindow.cpp Tue May 12 18:19:05 2015 +0100 +++ b/Source/GUI/MainWindow.cpp Mon Jan 02 22:29:39 2017 +0000 @@ -129,6 +129,9 @@ #ifdef ENABLE_TOUCHKEYS_SENSOR_TEST menu.addCommandItem(&commandManager_, kCommandTestTouchkeySensors); #endif +#ifdef ENABLE_TOUCHKEYS_FIRMWARE_UPDATE + menu.addCommandItem(&commandManager_, kCommandJumpToBootloader); +#endif menu.addSeparator(); menu.addCommandItem(&commandManager_, kCommandPreferences); } @@ -174,6 +177,9 @@ #ifdef ENABLE_TOUCHKEYS_SENSOR_TEST kCommandTestTouchkeySensors, #endif +#ifdef ENABLE_TOUCHKEYS_FIRMWARE_UPDATE + kCommandJumpToBootloader, +#endif kCommandPreferences, // Window @@ -267,12 +273,12 @@ case kCommandLoggingStartStop: result.setInfo("Record Log File", "Records TouchKeys and MIDI data to file", controlCategory, 0); result.setTicked(controller_.isLogging()); - result.setActive(true); + result.setActive(!controller_.isPlayingLog()); break; case kCommandLoggingPlay: result.setInfo("Play Log...", "Plays TouchKeys and MIDI from file", controlCategory, 0); - result.setTicked(false); - result.setActive(false); + result.setTicked(controller_.isPlayingLog()); + result.setActive(!controller_.isLogging()); break; case kCommandEnableExperimentalMappings: result.setInfo("Enable Experimental Mappings", "Enables mappings which are still experimental", controlCategory, 0); @@ -285,6 +291,13 @@ result.setTicked(controller_.touchkeySensorTestIsRunning()); break; #endif +#ifdef ENABLE_TOUCHKEYS_FIRMWARE_UPDATE + case kCommandJumpToBootloader: + result.setInfo("Go to Firmware Update Mode", "Puts the TouchKeys in firmware update mode", controlCategory, 0); + result.setActive(controller_.availableTouchkeyDevices().size() > 0); + result.setTicked(false); + break; +#endif case kCommandPreferences: result.setInfo("Preferences...", "General application preferences", controlCategory, 0); result.setTicked(false); @@ -337,7 +350,10 @@ controller_.startLogging(); break; case kCommandLoggingPlay: - // TODO + if(controller_.isPlayingLog()) + controller_.stopPlayingLog(); + else + controller_.playLogWithDialog(); break; case kCommandEnableExperimentalMappings: controller_.setExperimentalMappingsEnabled(!controller_.experimentalMappingsEnabled()); @@ -350,6 +366,11 @@ controller_.touchkeySensorTestStop(); break; #endif +#ifdef ENABLE_TOUCHKEYS_FIRMWARE_UPDATE + case kCommandJumpToBootloader: + controller_.touchkeyJumpToBootloader(mainComponent_.currentTouchkeysSelectedPath().toUTF8()); + break; +#endif case kCommandPreferences: controller_.showPreferencesWindow(); break; diff -r 18af05164894 -r ff5d65c69e73 Source/GUI/MainWindow.h --- a/Source/GUI/MainWindow.h Tue May 12 18:19:05 2015 +0100 +++ b/Source/GUI/MainWindow.h Mon Jan 02 22:29:39 2017 +0000 @@ -54,6 +54,7 @@ kCommandLoggingPlay, kCommandEnableExperimentalMappings, kCommandTestTouchkeySensors, + kCommandJumpToBootloader, kCommandPreferences, // Window menu diff -r 18af05164894 -r ff5d65c69e73 Source/MainApplicationController.cpp --- a/Source/MainApplicationController.cpp Tue May 12 18:19:05 2015 +0100 +++ b/Source/MainApplicationController.cpp Mon Jan 02 22:29:39 2017 +0000 @@ -40,6 +40,7 @@ oscReceiver_(0, "/touchkeys"), touchkeyController_(keyboardController_), touchkeyEmulator_(keyboardController_, oscReceiver_), + logPlayback_(0), #ifdef TOUCHKEY_ENTROPY_GENERATOR_ENABLE touchkeyEntropyGenerator_(keyboardController_), entropyGeneratorSelected_(false), @@ -59,7 +60,8 @@ preferencesWindow_(0), #endif segmentCounter_(0), - loggingActive_(false) + loggingActive_(false), + isPlayingLog_(false) { // Set our OSC controller setOscController(&keyboardController_); @@ -105,6 +107,8 @@ if(touchkeySensorTestIsRunning()) touchkeySensorTestStop(); #endif + if(logPlayback_ != 0) + delete logPlayback_; removeAllOscListeners(); midiInputController_.removeAllSegments(); // Remove segments now to avoid deletion-order problems delete mainOscController_; @@ -456,6 +460,61 @@ loggingDirectory_ = directory; } +void MainApplicationController::playLogWithDialog() { + if(isPlayingLog_) + return; + + FileChooser tkChooser ("Select TouchKeys log...", + File::nonexistent, // File::getSpecialLocation (File::userHomeDirectory), + "*.bin"); + if(tkChooser.browseForFileToOpen()) { + FileChooser midiChooser ("Select MIDI log...", + File::nonexistent, // File::getSpecialLocation (File::userHomeDirectory), + "*.bin"); + if(midiChooser.browseForFileToOpen()) { + logPlayback_ = new LogPlayback(keyboardController_, midiInputController_); + if(logPlayback_ == 0) + return; + + if(logPlayback_->openLogFiles(tkChooser.getResult().getFullPathName().toRawUTF8(), midiChooser.getResult().getFullPathName().toRawUTF8())) { + logPlayback_->startPlayback(); + isPlayingLog_ = true; +#ifndef TOUCHKEYS_NO_GUI + // Always show 88 keys for log playback since we won't know which keys were actually recorded + keyboardDisplay_.setKeyboardRange(21, 108); + if(keyboardDisplayWindow_ != 0) { + keyboardDisplayWindow_->getConstrainer()->setFixedAspectRatio(keyboardDisplay_.keyboardAspectRatio()); + + Rectangle bounds = keyboardDisplayWindow_->getBounds(); + if(bounds.getY() < 44) + bounds.setY(44); + keyboardDisplayWindow_->setBoundsConstrained(bounds); + } + showKeyboardDisplayWindow(); +#endif + } + } + } +} + +void MainApplicationController::stopPlayingLog() { + if(!isPlayingLog_) + return; + + if(logPlayback_ != 0) { + logPlayback_->stopPlayback(); + logPlayback_->closeLogFiles(); + delete logPlayback_; + logPlayback_ = 0; + } + +#ifndef TOUCHKEYS_NO_GUI + keyboardDisplay_.clearAllTouches(); +#endif + midiInputController_.allNotesOff(); + isPlayingLog_ = false; +} + // Add a new MIDI keyboard segment. This method also handles numbering of the segments MidiKeyboardSegment* MainApplicationController::midiSegmentAdd() { // For now, the segment counter increments with each new segment. Eventually, we could @@ -1202,6 +1261,31 @@ #endif // ENABLE_TOUCHKEYS_SENSOR_TEST +#ifdef ENABLE_TOUCHKEYS_FIRMWARE_UPDATE +// Put TouchKeys controller board into bootloader mode, for receiving firmware updates +// (supplied by a different utility) +bool MainApplicationController::touchkeyJumpToBootloader(const char *path) { + // First, close the existing device which stops the data autogathering + closeTouchkeyDevice(); + + // Now reopen the TouchKeys device + if(!touchkeyController_.openDevice(path)) { + touchkeyErrorMessage_ = "Failed to open"; + touchkeyErrorOccurred_ = true; + return false; + } + + touchkeyController_.jumpToBootloader(); + + // Set an "error" condition to display this message, and because + // after jumping to bootloader mode, the device will not open properly + // until it has been reset. + touchkeyErrorMessage_ = "Firmware update mode"; + touchkeyErrorOccurred_ = true; + return true; +} +#endif // ENABLE_TOUCHKEYS_FIRMWARE_UPDATE + // Return the name of a MIDI note given its number std::string MainApplicationController::midiNoteName(int noteNumber) { if(noteNumber < 0 || noteNumber > 127) diff -r 18af05164894 -r ff5d65c69e73 Source/MainApplicationController.h --- a/Source/MainApplicationController.h Tue May 12 18:19:05 2015 +0100 +++ b/Source/MainApplicationController.h Mon Jan 02 22:29:39 2017 +0000 @@ -259,6 +259,12 @@ bool isLogging() { return loggingActive_; } void setLoggingDirectory(const char *directory); + // Playback methods for log files + + void playLogWithDialog(); + void stopPlayingLog(); + bool isPlayingLog() { return isPlayingLog_; } + // *** OSC handler method (different from OSC device selection) *** bool oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data); @@ -333,6 +339,11 @@ void touchkeySensorTestResetState(); #endif +#ifdef ENABLE_TOUCHKEYS_FIRMWARE_UPDATE + // Put TouchKeys controller board into bootloader mode + bool touchkeyJumpToBootloader(const char *path); +#endif + // *** Static utility methods *** static std::string midiNoteName(int noteNumber); static int midiNoteNumberForName(std::string const& name); @@ -353,6 +364,7 @@ OscReceiver oscReceiver_; TouchkeyDevice touchkeyController_; TouchkeyOscEmulator touchkeyEmulator_; + LogPlayback *logPlayback_; #ifdef TOUCHKEY_ENTROPY_GENERATOR_ENABLE TouchkeyEntropyGenerator touchkeyEntropyGenerator_; bool entropyGeneratorSelected_; @@ -385,7 +397,7 @@ int segmentCounter_; // Logging info - bool loggingActive_; + bool loggingActive_, isPlayingLog_; std::string loggingDirectory_; }; diff -r 18af05164894 -r ff5d65c69e73 Source/Mappings/Vibrato/TouchkeyVibratoMapping.cpp --- a/Source/Mappings/Vibrato/TouchkeyVibratoMapping.cpp Tue May 12 18:19:05 2015 +0100 +++ b/Source/Mappings/Vibrato/TouchkeyVibratoMapping.cpp Mon Jan 02 22:29:39 2017 +0000 @@ -253,6 +253,7 @@ //distance = fabsf(lastY_ - onsetLocationY_); distance = lastY_ - onsetLocationY_; + //distance = 0; // TESTING } else { // Euclidean distance between points diff -r 18af05164894 -r ff5d65c69e73 Source/TouchKeys/LogPlayback.cpp --- a/Source/TouchKeys/LogPlayback.cpp Tue May 12 18:19:05 2015 +0100 +++ b/Source/TouchKeys/LogPlayback.cpp Mon Jan 02 22:29:39 2017 +0000 @@ -101,6 +101,8 @@ timestampOffset_ = playbackScheduler_.currentTimestamp() - firstMidiTimestamp; } + cout << "Touch " << firstTouchTimestamp << " MIDI " << firstMidiTimestamp << " offset " << timestampOffset_ << endl; + playing_ = true; paused_ = false; @@ -277,7 +279,7 @@ } } - readNextTouchFrame(); + newTouchFound = readNextTouchFrame(); } if(!newTouchFound) { // EOF or error @@ -297,8 +299,12 @@ // TODO: handle playback rate // Play the most recent stored touch frame - if(nextMidi_.size() >= 3) - midiInputController_.handleIncomingMidiMessage(0, MidiMessage(nextMidi_[0], nextMidi_[1], nextMidi_[2])); + if(nextMidi_.size() >= 3) { + if((nextMidi_[0] & 0xF0) == 0xD0) // channel aftertouch has 2 bytes + midiInputController_.handleIncomingMidiMessage(0, MidiMessage(nextMidi_[0], nextMidi_[1])); + else + midiInputController_.handleIncomingMidiMessage(0, MidiMessage(nextMidi_[0], nextMidi_[1], nextMidi_[2])); + } //midiInputController_.rtMidiCallback(nextMidiTimestamp_ - lastMidiTimestamp_, &nextMidi_, 0); lastMidiTimestamp_ = nextMidiTimestamp_; @@ -306,8 +312,12 @@ // Go through next touch frames and send them as long as the timestamp is not in the future while(newMidiEventFound && (nextMidiTimestamp_ + timestampOffset_) <= playbackScheduler_.currentTimestamp()) { - if(nextMidi_.size() >= 3) - midiInputController_.handleIncomingMidiMessage(0, MidiMessage(nextMidi_[0], nextMidi_[1], nextMidi_[2])); + if(nextMidi_.size() >= 3) { + if((nextMidi_[0] & 0xF0) == 0xD0) // channel aftertouch has 2 bytes + midiInputController_.handleIncomingMidiMessage(0, MidiMessage(nextMidi_[0], nextMidi_[1])); + else + midiInputController_.handleIncomingMidiMessage(0, MidiMessage(nextMidi_[0], nextMidi_[1], nextMidi_[2])); + } //midiInputController_.rtMidiCallback(nextMidiTimestamp_ - lastMidiTimestamp_, &nextMidi_, 0); lastMidiTimestamp_ = nextMidiTimestamp_; diff -r 18af05164894 -r ff5d65c69e73 Source/TouchKeys/MidiKeyboardSegment.cpp --- a/Source/TouchKeys/MidiKeyboardSegment.cpp Tue May 12 18:19:05 2015 +0100 +++ b/Source/TouchKeys/MidiKeyboardSegment.cpp Mon Jan 02 22:29:39 2017 +0000 @@ -42,6 +42,7 @@ #undef DEBUG_MIDI_KEYBOARD_SEGMENT const int MidiKeyboardSegment::kMidiControllerDamperPedal = 64; +const int MidiKeyboardSegment::kMidiControllerSostenutoPedal = 66; const int MidiKeyboardSegment::kPedalActiveValue = 64; // Factores to use @@ -57,7 +58,8 @@ noteMin_(0), noteMax_(127), outputChannelLowest_(0), outputTransposition_(0), damperPedalEnabled_(true), touchkeyStandaloneMode_(false), usesKeyboardChannelPressure_(false), usesKeyboardPitchWheel_(false), - usesKeyboardModWheel_(false), usesKeyboardMidiControllers_(false), + usesKeyboardModWheel_(false), usesKeyboardPedals_(true), + usesKeyboardMidiControllers_(false), pitchWheelRange_(2.0), useVoiceStealing_(false) { // Register for OSC messages from the internal keyboard source @@ -163,6 +165,7 @@ // If in polyphonic mode, send to all channels; otherwise send only // to the channel in question. void MidiKeyboardSegment::sendMidiPitchWheelRange() { + // MPE-TODO if(mode_ == ModePolyphonic) { for(int i = outputChannelLowest_; i < outputChannelLowest_ + retransmitMaxPolyphony_; i++) sendMidiPitchWheelRangeHelper(i); @@ -216,6 +219,8 @@ setModeMonophonic(); else if(mode == ModePolyphonic) setModePolyphonic(); + else if(mode == ModeMPE) + setModeMPE(); else setModeOff(); } @@ -260,6 +265,28 @@ modePolyphonicSetupHelper(); } +void MidiKeyboardSegment::setModeMPE() { + // First turn off any notes in the current mode + allNotesOff(); + + // MPE-TODO some things need to be set to master-zone retransmit + // also reset pitch wheel value to 0 since it's sent separately + setAllControllerActionsTo(kControlActionBroadcast); + + // Register a callback for touchkey data. When we get a note-on message, + // we request this callback occur once touch data is available. In this mode, + // we know the eventual channel before any touch data ever occurs: thus, we + // only listen to the MIDI onset itself, which happens after all the touch + // data is sent out. + addOscListener("/midi/noteon"); + + mode_ = ModeMPE; + + // MPE-TODO + // Set RPN 6 to enable MPE with the appropriate zone + +} + // Set the maximum polyphony, affecting polyphonic mode only void MidiKeyboardSegment::setPolyphony(int polyphony) { // First turn off any notes if this affects current polyphonic mode @@ -273,7 +300,13 @@ retransmitMaxPolyphony_ = 16; else retransmitMaxPolyphony_ = polyphony; - modePolyphonicSetupHelper(); + + // MPE-TODO + // Send RPN 6 to change the zone configuration + // -- maybe in modePolyphonicSetupHelper() + + if(mode_ == ModePolyphonic) + modePolyphonicSetupHelper(); } // Set whether the damper pedal is enabled or not @@ -287,6 +320,13 @@ damperPedalEnabled_ = enable; } +// Set the lowest output channel +void MidiKeyboardSegment::setOutputChannelLowest(int ch) { + // FIXME this is probably broken for polyphonic mode! + // MPE-TODO: send new RPN 6 for disabling old zone and creating new one + outputChannelLowest_ = ch; +} + // Handle an incoming MIDI message void MidiKeyboardSegment::midiHandlerMethod(MidiInput* source, const MidiMessage& message) { // Log the timestamps of note onsets and releases, regardless of the mode @@ -300,7 +340,8 @@ // (damper pedal enabled) && (pedal is down) && (polyphonic mode) // In this condition, onsets will be removed when note goes off if(message.getNoteNumber() >= 0 && message.getNoteNumber() < 128) { - if(!damperPedalEnabled_ || controllerValues_[kMidiControllerDamperPedal] < kPedalActiveValue || mode_ != ModePolyphonic) { + if(!damperPedalEnabled_ || controllerValues_[kMidiControllerDamperPedal] < kPedalActiveValue || + (mode_ != ModePolyphonic && mode_ != ModeMPE)) { noteOnsetTimestamps_[message.getNoteNumber()] = 0; } } @@ -321,8 +362,17 @@ } if(message.getControllerNumber() >= 0 && message.getControllerNumber() < 128) { - if((message.getControllerNumber() == 1 && usesKeyboardModWheel_) || - (message.getControllerNumber() != 1 && usesKeyboardMidiControllers_)) { + if(message.getControllerNumber() == 1 && usesKeyboardModWheel_) { + controllerValues_[message.getControllerNumber()] = message.getControllerValue(); + handleControlChangeRetransit(message.getControllerNumber(), message); + } + else if(message.getControllerNumber() >= 64 && message.getControllerNumber() <= 69 + && usesKeyboardPedals_) { + // MPE-TODO send this on master zone + controllerValues_[message.getControllerNumber()] = message.getControllerValue(); + handleControlChangeRetransit(message.getControllerNumber(), message); + } + else if(usesKeyboardMidiControllers_) { controllerValues_[message.getControllerNumber()] = message.getControllerValue(); handleControlChangeRetransit(message.getControllerNumber(), message); } @@ -336,8 +386,13 @@ } else if(message.isPitchWheel()) { if(usesKeyboardPitchWheel_) { - controllerValues_[kControlPitchWheel] = message.getPitchWheelValue(); - handleControlChangeRetransit(kControlPitchWheel, message); + if(mode_ == ModeMPE) { + // MPE-TODO send this on master zone instead of putting it into the calculations + } + else { + controllerValues_[kControlPitchWheel] = message.getPitchWheelValue(); + handleControlChangeRetransit(kControlPitchWheel, message); + } } } else { @@ -352,6 +407,9 @@ case ModePolyphonic: modePolyphonicHandler(source, message); break; + case ModeMPE: + modeMPEHandler(source, message); + break; case ModeOff: default: // Ignore message @@ -389,8 +447,8 @@ } } - if(mode_ == ModePolyphonic) { - modePolyphonicNoteOnCallback(path, types, numValues, values); + if(mode_ == ModePolyphonic || mode_ == ModeMPE) { + modePolyphonicMPENoteOnCallback(path, types, numValues, values); } return true; @@ -541,14 +599,15 @@ } else if(!strcmp(path, "/set-controller-pass")) { // Set which controllers to pass through - // Arguments: (channel pressure), (pitch wheel), (mod wheel), (other CCs) + // Arguments: (channel pressure), (pitch wheel), (mod wheel), (pedals), (other CCs) - if(numValues >= 4) { - if(types[0] == 'i' && types[1] == 'i' && types[2] == 'i' && types[3] == 'i') { + if(numValues >= 5) { + if(types[0] == 'i' && types[1] == 'i' && types[2] == 'i' && types[3] == 'i' && types[4] == 'i') { setUsesKeyboardChannelPressure(values[0]->i != 0); setUsesKeyboardPitchWheel(values[1]->i != 0); setUsesKeyboardModWheel(values[2]->i != 0); - setUsesKeyboardMIDIControllers(values[3]->i != 0); + setUsesKeyboardPedals(values[3]->i != 0); + setUsesKeyboardMIDIControllers(values[4]->i != 0); return OscTransmitter::createSuccessMessage(); } @@ -590,6 +649,8 @@ setModeMonophonic(); else if(!strncmp(mode, "poly", 4)) setModePolyphonic(); + else if(!strncmp(mode, "mpe", 3)) + setModeMPE(); else return OscTransmitter::createFailureMessage(); @@ -861,6 +922,7 @@ properties.setValue("usesKeyboardChannelPressure", usesKeyboardChannelPressure_); properties.setValue("usesKeyboardPitchWheel", usesKeyboardPitchWheel_); properties.setValue("usesKeyboardModWheel", usesKeyboardModWheel_); + properties.setValue("usesKeyboardPedals", usesKeyboardPedals_); properties.setValue("usesKeyboardMidiControllers", usesKeyboardMidiControllers_); properties.setValue("pitchWheelRange", pitchWheelRange_); properties.setValue("retransmitMaxPolyphony", retransmitMaxPolyphony_); @@ -904,6 +966,8 @@ setModeMonophonic(); else if(mode == ModePolyphonic) setModePolyphonic(); + else if(mode == ModeMPE) + setModeMPE(); else // Off or unknown setModeOff(); if(!properties.containsKey("channelMask")) @@ -933,6 +997,10 @@ if(!properties.containsKey("usesKeyboardModWheel")) return false; usesKeyboardModWheel_ = properties.getBoolValue("usesKeyboardModWheel"); + if(properties.containsKey("usesKeyboardPedals")) + usesKeyboardPedals_ = properties.getBoolValue("usesKeyboardPedals"); + else + usesKeyboardPedals_ = false; // For backwards compatibility with older versions if(!properties.containsKey("usesKeyboardMidiControllers")) return false; usesKeyboardMidiControllers_ = properties.getBoolValue("usesKeyboardMidiControllers"); @@ -941,7 +1009,7 @@ pitchWheelRange_ = properties.getDoubleValue("pitchWheelRange"); if(!properties.containsKey("retransmitMaxPolyphony")) return false; - retransmitMaxPolyphony_ = properties.getIntValue("retransmitMaxPolyphony"); + setPolyphony(properties.getIntValue("retransmitMaxPolyphony")); if(!properties.containsKey("useVoiceStealing")) return false; useVoiceStealing_ = properties.getBoolValue("useVoiceStealing"); @@ -1141,6 +1209,22 @@ void MidiKeyboardSegment::modePolyphonicNoteOn(unsigned char note, unsigned char velocity) { int newChannel = -1; +#ifdef DEBUG_MIDI_KEYBOARD_SEGMENT + cout << "Channels available: "; + for(set::iterator it = retransmitChannelsAvailable_.begin(); + it != retransmitChannelsAvailable_.end(); ++it) { + cout << *it << " "; + } + cout << endl; + + cout << "Channels allocated: "; + for(map::iterator it = retransmitChannelForNote_.begin(); + it != retransmitChannelForNote_.end(); ++it) { + cout << it->second << "(" << it->first << ") "; + } + cout << endl; +#endif + if(retransmitNotesHeldInPedal_.count(note) > 0) { // For notes that are still sounding in the pedal, reuse the same MIDI channel // they had before. @@ -1173,13 +1257,6 @@ cout << "Stealing note " << oldNote << " from pedal for note " << (int)note << endl; #endif modePolyphonicNoteOff(oldNote, true); - if(oldChannel >= 0) { - //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControllerDamperPedal, 0); - //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControlAllNotesOff, 0); - //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControlAllSoundOff, 0); - //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControllerDamperPedal, - // controllerValues_[kMidiControllerDamperPedal]); - } } } @@ -1202,13 +1279,6 @@ cout << "Stealing note " << oldNote << " for note " << (int)note << endl; #endif modePolyphonicNoteOff(oldNote, true); - if(oldChannel >= 0) { - //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControllerDamperPedal, 0); - //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControlAllNotesOff, 0); - //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControlAllSoundOff, 0); - //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControllerDamperPedal, - // controllerValues_[kMidiControllerDamperPedal]); - } } else { // No channels available. Print a warning and finish @@ -1247,12 +1317,31 @@ if(keyboard_.key(note) != 0) { keyboard_.key(note)->midiNoteOff(this, keyboard_.schedulerCurrentTimestamp()); } - - // Send a Note Off message to the appropriate channel - if(midiOutputController_ != 0) { - midiOutputController_->sendNoteOff(outputPortNumber_, retransmitChannelForNote_[note], note + outputTransposition_); - } - + + int oldNoteChannel = retransmitChannelForNote_[note]; + + if(midiOutputController_ != 0) { + if(forceOff) { + // To silence a note, we need to clear any pedals that might be holding it + if(controllerValues_[kMidiControllerDamperPedal] >= kPedalActiveValue) { + midiOutputController_->sendControlChange(outputPortNumber_, oldNoteChannel, + kMidiControllerDamperPedal, 0); + } + if(controllerValues_[kMidiControllerSostenutoPedal] >= kPedalActiveValue) { + midiOutputController_->sendControlChange(outputPortNumber_, oldNoteChannel, + kMidiControllerSostenutoPedal, 0); + } + + // Send All Notes Off and All Sound Off + midiOutputController_->sendControlChange(outputPortNumber_, oldNoteChannel, kMidiControlAllNotesOff, 0); + midiOutputController_->sendControlChange(outputPortNumber_, oldNoteChannel, kMidiControlAllSoundOff, 0); + } + else { + // Send a Note Off message to the appropriate channel + midiOutputController_->sendNoteOff(outputPortNumber_, oldNoteChannel, note + outputTransposition_); + } + } + // If the pedal is enabled and currently active, don't re-enable this channel // just yet. Instead, let the note continue ringing until we have to steal it later. if(damperPedalEnabled_ && controllerValues_[kMidiControllerDamperPedal] >= kPedalActiveValue && !forceOff) { @@ -1267,6 +1356,20 @@ if(note >= 0 && note < 128) noteOnsetTimestamps_[note] = 0; } + + if(forceOff) { + // Now re-enable any pedals that we might have temporarily lifted on this channel + if(controllerValues_[kMidiControllerDamperPedal] >= kPedalActiveValue) { + midiOutputController_->sendControlChange(outputPortNumber_, oldNoteChannel, + kMidiControllerDamperPedal, + controllerValues_[kMidiControllerDamperPedal]); + } + if(controllerValues_[kMidiControllerSostenutoPedal] >= kPedalActiveValue) { + midiOutputController_->sendControlChange(outputPortNumber_, oldNoteChannel, + kMidiControllerSostenutoPedal, + controllerValues_[kMidiControllerSostenutoPedal]); + } + } } // Callback function after we request a note on. PianoKey class will respond @@ -1274,7 +1377,7 @@ // indicating an absence of touch data. Once we receive this, we can send the // MIDI note on message. -void MidiKeyboardSegment::modePolyphonicNoteOnCallback(const char *path, const char *types, int numValues, lo_arg **values) { +void MidiKeyboardSegment::modePolyphonicMPENoteOnCallback(const char *path, const char *types, int numValues, lo_arg **values) { if(numValues < 3) // Sanity check: first 3 values hold MIDI information return; if(types[0] != 'i' || types[1] != 'i' || types[2] != 'i') @@ -1298,10 +1401,29 @@ } } +// MPE (Multidimensional Polyphonic Expression): Each incoming note gets its own unique MIDI channel. +// Like polyphonic mode but implementing the details of the MPE specification which differ subtly +// from a straightforward polyphonic allocation +void MidiKeyboardSegment::modeMPEHandler(MidiInput* source, const MidiMessage& message) { + // MPE-TODO +} + +// Handle note on message in MPE mode. Allocate a new channel +// for this note and rebroadcast it. +void MidiKeyboardSegment::modeMPENoteOn(unsigned char note, unsigned char velocity) { + // MPE-TODO + // allocate notes to channels like polyphonic mode, with certain changes: + // -- round-robin as default rather than first available + // -- different stealing behaviour: + // ---- when no channels are available, add to an existing one with the fewest sounding notes + // ---- old note doesn't need to be turned off, but it could(?) have its mappings disabled +} + // Private helper method to handle changes in polyphony void MidiKeyboardSegment::modePolyphonicSetupHelper() { - if(retransmitMaxPolyphony_ > 16) - retransmitMaxPolyphony_ = 16; // Limit polyphony to 16 (number of MIDI channels + // Limit polyphony to 16 (number of MIDI channels) or fewer if starting above channel 1 + if(retransmitMaxPolyphony_ + outputChannelLowest_ > 16) + retransmitMaxPolyphony_ = 16 - outputChannelLowest_; retransmitChannelsAvailable_.clear(); for(int i = outputChannelLowest_; i < outputChannelLowest_ + retransmitMaxPolyphony_; i++) retransmitChannelsAvailable_.insert(i); @@ -1394,6 +1516,7 @@ // retransit or not to outgoing MIDI channels depending on the current behaviour defined in // controllerActions_. void MidiKeyboardSegment::handleControlChangeRetransit(int controllerNumber, const MidiMessage& message) { + // MPE-TODO need a new mode for sending on master zone, e.g. for pitch wheel if(midiOutputController_ == 0) return; if(controllerActions_[controllerNumber] == kControlActionPassthrough) { diff -r 18af05164894 -r ff5d65c69e73 Source/TouchKeys/MidiKeyboardSegment.h --- a/Source/TouchKeys/MidiKeyboardSegment.h Tue May 12 18:19:05 2015 +0100 +++ b/Source/TouchKeys/MidiKeyboardSegment.h Mon Jan 02 22:29:39 2017 +0000 @@ -48,6 +48,7 @@ class MidiKeyboardSegment : public OscHandler { private: static const int kMidiControllerDamperPedal; + static const int kMidiControllerSostenutoPedal; static const int kPedalActiveValue; public: @@ -56,7 +57,8 @@ ModeOff = 0, ModePassThrough, ModeMonophonic, - ModePolyphonic + ModePolyphonic, + ModeMPE }; // The MIDI Pitch Wheel is not handled by control change like the others, @@ -132,6 +134,16 @@ } } + bool usesKeyboardPedals() { return usesKeyboardPedals_; } + void setUsesKeyboardPedals(bool use) { + usesKeyboardPedals_ = use; + // Reset to default if not using + if(!use) { + // MIDI CCs 64 to 69 are for pedals + for(int i = 64; i <= 69; i++) + controllerValues_[i] = 0; + } + } bool usesKeyboardMIDIControllers() { return usesKeyboardMidiControllers_; } void setUsesKeyboardMIDIControllers(bool use) { @@ -175,6 +187,7 @@ void setModePassThrough(); void setModeMonophonic(); void setModePolyphonic(); + void setModeMPE(); // Get/set polyphony and voice stealing for polyphonic mode int polyphony() { return retransmitMaxPolyphony_; } @@ -188,7 +201,7 @@ // Set the minimum MIDI channel that should be used for output (0-15) int outputChannelLowest() { return outputChannelLowest_; } - void setOutputChannelLowest(int ch) { outputChannelLowest_ = ch; } + void setOutputChannelLowest(int ch); // Get set the output transposition in semitones, relative to input MIDI notes int outputTransposition() { return outputTransposition_; } @@ -266,7 +279,10 @@ void modePolyphonicHandler(MidiInput* source, const MidiMessage& message); void modePolyphonicNoteOn(unsigned char note, unsigned char velocity); void modePolyphonicNoteOff(unsigned char note, bool forceOff = false); - void modePolyphonicNoteOnCallback(const char *path, const char *types, int numValues, lo_arg **values); + void modePolyphonicMPENoteOnCallback(const char *path, const char *types, int numValues, lo_arg **values); + + void modeMPEHandler(MidiInput* source, const MidiMessage& message); + void modeMPENoteOn(unsigned char note, unsigned char velocity); // Helper functions for polyphonic mode void modePolyphonicSetupHelper(); @@ -303,6 +319,7 @@ bool usesKeyboardChannelPressure_; // Whether this segment passes aftertouch from the keyboard bool usesKeyboardPitchWheel_; // Whether this segment passes pitchwheel from the keyboard bool usesKeyboardModWheel_; // Whether this segment passes CC 1 (mod wheel) from keyboard + bool usesKeyboardPedals_; // Whether this segment passes CCs 64-69 (pedals) from the keyboard bool usesKeyboardMidiControllers_; // Whether this segment passes other controllers float pitchWheelRange_; // Range of MIDI pitch wheel (in semitones) diff -r 18af05164894 -r ff5d65c69e73 Source/TouchKeys/TouchkeyDevice.cpp --- a/Source/TouchKeys/TouchkeyDevice.cpp Tue May 12 18:19:05 2015 +0100 +++ b/Source/TouchKeys/TouchkeyDevice.cpp Mon Jan 02 22:29:39 2017 +0000 @@ -771,11 +771,12 @@ // Jump to the built-in bootloader of the TouchKeys device void TouchkeyDevice::jumpToBootloader() { + // The command includes a 4-byte magic number to avoid a corrupt packet accidentally triggering the jump unsigned char command[] = {ESCAPE_CHARACTER, kControlCharacterFrameBegin, kFrameTypeEnterSelfProgramMode, - ESCAPE_CHARACTER, kControlCharacterFrameEnd}; + 0xA1, 0xB2, 0xC3, 0xD4, ESCAPE_CHARACTER, kControlCharacterFrameEnd}; // Send command - if(deviceWrite((char*)command, 5) < 0) { + if(deviceWrite((char*)command, 9) < 0) { if(verbose_ >= 1) cout << "ERROR: unable to write jumpToBootloader command. errno = " << errno << endl; } diff -r 18af05164894 -r ff5d65c69e73 Source/TouchKeys/TouchkeyDevice.h --- a/Source/TouchKeys/TouchkeyDevice.h Tue May 12 18:19:05 2015 +0100 +++ b/Source/TouchKeys/TouchkeyDevice.h Mon Jan 02 22:29:39 2017 +0000 @@ -111,6 +111,7 @@ kFrameTypeMonitorRawFromKey = 138, kFrameTypeUpdateBaselines = 139, // Reinitialize baseline values kFrameTypeRescanKeyboard = 140, // Rescan what keys are connected + kFrameTypeEncapsulatedMIDI = 167, // MIDI messages to pass to MIDI standalone firmware kFrameTypeRGBLEDSetColors = 168, // Set RGBLEDs of given index to specific values kFrameTypeRGBLEDAllOff = 169, // All LEDs off kFrameTypeEnterISPMode = 192,