changeset 46:78b9808a2c65

New features in release angle mapping, plus bugfixes on Linux (thanks Martin).
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Wed, 24 Sep 2014 00:29:18 +0100
parents 518027b4a3eb
children e314677c49f6
files Builds/Linux/Makefile Builds/Linux32/Makefile JuceLibraryCode/AppConfig.h JuceLibraryCode/BinaryData.cpp JuceLibraryCode/BinaryData.h JuceLibraryCode/JuceHeader.h Source/Display/KeyboardDisplay.cpp Source/GUI/GraphicsDisplayWindow.h Source/GUI/PreferencesWindow.h Source/MainApplicationController.h Source/Mappings/KeyDivision/TouchkeyKeyDivisionMapping.cpp Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMapping.cpp Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMapping.h Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingExtendedEditor.cpp Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingExtendedEditor.h Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.cpp Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.h Source/TouchKeys/PianoKey.cpp Source/TouchKeys/PianoKeyboard.cpp TouchKeys.jucer
diffstat 20 files changed, 1441 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- a/Builds/Linux/Makefile	Wed Aug 27 13:18:27 2014 +0100
+++ b/Builds/Linux/Makefile	Wed Sep 24 00:29:18 2014 +0100
@@ -18,14 +18,15 @@
     TARGET_ARCH := -march=native
   endif
 
-  CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
+  CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -D "JUCE_APP_VERSION=0.1.0" -D "JUCE_APP_VERSION_HEX=0x100" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
   CFLAGS += $(CPPFLAGS) $(TARGET_ARCH) -g -ggdb -O0
   CXXFLAGS += $(CFLAGS)
   LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -L/usr/X11R6/lib/ -lGL -lX11 -lXext -lXinerama -lasound -ldl -lfreetype -lpthread -lrt /usr/local/lib/liblo.a
   LDDEPS :=
-  RESFLAGS :=  -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
+  RESFLAGS :=  -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -D "JUCE_APP_VERSION=0.1.0" -D "JUCE_APP_VERSION_HEX=0x100" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
   TARGET := TouchKeys
   BLDCMD = $(CXX) -o $(OUTDIR)/$(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH)
+  CLEANCMD = rm -rf $(OUTDIR)/$(TARGET) $(OBJDIR)
 endif
 
 ifeq ($(CONFIG),Release)
@@ -38,14 +39,15 @@
     TARGET_ARCH := -march=native
   endif
 
-  CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
+  CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -D "JUCE_APP_VERSION=0.1.0" -D "JUCE_APP_VERSION_HEX=0x100" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
   CFLAGS += $(CPPFLAGS) $(TARGET_ARCH) -Os
   CXXFLAGS += $(CFLAGS)
   LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -fvisibility=hidden -L/usr/X11R6/lib/ -lGL -lX11 -lXext -lXinerama -lasound -ldl -lfreetype -lpthread -lrt /usr/local/lib/liblo.a
   LDDEPS :=
-  RESFLAGS :=  -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
+  RESFLAGS :=  -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -D "JUCE_APP_VERSION=0.1.0" -D "JUCE_APP_VERSION_HEX=0x100" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
   TARGET := TouchKeys
   BLDCMD = $(CXX) -o $(OUTDIR)/$(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH)
+  CLEANCMD = rm -rf $(OUTDIR)/$(TARGET) $(OBJDIR)
 endif
 
 OBJECTS := \
@@ -58,6 +60,7 @@
   $(OBJDIR)/TouchkeyVibratoMappingShortEditor_27ad15dd.o \
   $(OBJDIR)/TouchkeyVibratoMapping_ea5c5156.o \
   $(OBJDIR)/TouchkeyVibratoMappingFactory_f90040de.o \
+  $(OBJDIR)/TouchkeyReleaseAngleMappingExtendedEditor_cdb58770.o \
   $(OBJDIR)/TouchkeyReleaseAngleMapping_170b0b0a.o \
   $(OBJDIR)/TouchkeyReleaseAngleMappingFactory_9052f4aa.o \
   $(OBJDIR)/TouchkeyPitchBendMappingShortEditor_6afc649d.o \
@@ -129,9 +132,7 @@
 
 clean:
 	@echo Cleaning TouchKeys
-	-@rm -f $(OUTDIR)/$(TARGET)
-	-@rm -rf $(OBJDIR)/*
-	-@rm -rf $(OBJDIR)
+	@$(CLEANCMD)
 
 strip:
 	@echo Stripping TouchKeys
@@ -182,6 +183,11 @@
 	@echo "Compiling TouchkeyVibratoMappingFactory.cpp"
 	@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
 
+$(OBJDIR)/TouchkeyReleaseAngleMappingExtendedEditor_cdb58770.o: ../../Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingExtendedEditor.cpp
+	-@mkdir -p $(OBJDIR)
+	@echo "Compiling TouchkeyReleaseAngleMappingExtendedEditor.cpp"
+	@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
+
 $(OBJDIR)/TouchkeyReleaseAngleMapping_170b0b0a.o: ../../Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMapping.cpp
 	-@mkdir -p $(OBJDIR)
 	@echo "Compiling TouchkeyReleaseAngleMapping.cpp"
--- a/Builds/Linux32/Makefile	Wed Aug 27 13:18:27 2014 +0100
+++ b/Builds/Linux32/Makefile	Wed Sep 24 00:29:18 2014 +0100
@@ -18,14 +18,15 @@
     TARGET_ARCH := -march=native
   endif
 
-  CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_BCF8FE09=1" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
+  CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_BCF8FE09=1" -D "JUCE_APP_VERSION=0.1.0" -D "JUCE_APP_VERSION_HEX=0x100" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
   CFLAGS += $(CPPFLAGS) $(TARGET_ARCH) -g -ggdb -O0 -m32
   CXXFLAGS += $(CFLAGS)
   LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -L/usr/X11R6/lib/ -lGL -lX11 -lXext -lXinerama -lasound -ldl -lfreetype -lpthread -lrt -L/usr/lib/i386-linux-gnu -L/usr/lib/i386-linux-gnu/mesa -m32 /usr/local/lib/liblo32.a
   LDDEPS :=
-  RESFLAGS :=  -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_BCF8FE09=1" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
+  RESFLAGS :=  -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_BCF8FE09=1" -D "JUCE_APP_VERSION=0.1.0" -D "JUCE_APP_VERSION_HEX=0x100" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
   TARGET := TouchKeys
   BLDCMD = $(CXX) -o $(OUTDIR)/$(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH)
+  CLEANCMD = rm -rf $(OUTDIR)/$(TARGET) $(OBJDIR)
 endif
 
 ifeq ($(CONFIG),Release)
@@ -38,14 +39,15 @@
     TARGET_ARCH := -march=native
   endif
 
-  CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_BCF8FE09=1" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
+  CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_BCF8FE09=1" -D "JUCE_APP_VERSION=0.1.0" -D "JUCE_APP_VERSION_HEX=0x100" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
   CFLAGS += $(CPPFLAGS) $(TARGET_ARCH) -Os -m32
   CXXFLAGS += $(CFLAGS)
   LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -fvisibility=hidden -L/usr/X11R6/lib/ -lGL -lX11 -lXext -lXinerama -lasound -ldl -lfreetype -lpthread -lrt -L/usr/lib/i386-linux-gnu -L/usr/lib/i386-linux-gnu/mesa -m32 /usr/local/lib/liblo32.a
   LDDEPS :=
-  RESFLAGS :=  -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_BCF8FE09=1" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
+  RESFLAGS :=  -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_BCF8FE09=1" -D "JUCE_APP_VERSION=0.1.0" -D "JUCE_APP_VERSION_HEX=0x100" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../juce/modules
   TARGET := TouchKeys
   BLDCMD = $(CXX) -o $(OUTDIR)/$(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH)
+  CLEANCMD = rm -rf $(OUTDIR)/$(TARGET) $(OBJDIR)
 endif
 
 OBJECTS := \
@@ -58,6 +60,7 @@
   $(OBJDIR)/TouchkeyVibratoMappingShortEditor_27ad15dd.o \
   $(OBJDIR)/TouchkeyVibratoMapping_ea5c5156.o \
   $(OBJDIR)/TouchkeyVibratoMappingFactory_f90040de.o \
+  $(OBJDIR)/TouchkeyReleaseAngleMappingExtendedEditor_cdb58770.o \
   $(OBJDIR)/TouchkeyReleaseAngleMapping_170b0b0a.o \
   $(OBJDIR)/TouchkeyReleaseAngleMappingFactory_9052f4aa.o \
   $(OBJDIR)/TouchkeyPitchBendMappingShortEditor_6afc649d.o \
@@ -129,9 +132,7 @@
 
 clean:
 	@echo Cleaning TouchKeys
-	-@rm -f $(OUTDIR)/$(TARGET)
-	-@rm -rf $(OBJDIR)/*
-	-@rm -rf $(OBJDIR)
+	@$(CLEANCMD)
 
 strip:
 	@echo Stripping TouchKeys
@@ -182,6 +183,11 @@
 	@echo "Compiling TouchkeyVibratoMappingFactory.cpp"
 	@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
 
+$(OBJDIR)/TouchkeyReleaseAngleMappingExtendedEditor_cdb58770.o: ../../Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingExtendedEditor.cpp
+	-@mkdir -p $(OBJDIR)
+	@echo "Compiling TouchkeyReleaseAngleMappingExtendedEditor.cpp"
+	@$(CXX) $(CXXFLAGS) -o "$@" -c "$<"
+
 $(OBJDIR)/TouchkeyReleaseAngleMapping_170b0b0a.o: ../../Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMapping.cpp
 	-@mkdir -p $(OBJDIR)
 	@echo "Compiling TouchkeyReleaseAngleMapping.cpp"
--- a/JuceLibraryCode/AppConfig.h	Wed Aug 27 13:18:27 2014 +0100
+++ b/JuceLibraryCode/AppConfig.h	Wed Sep 24 00:29:18 2014 +0100
@@ -151,5 +151,9 @@
  //#define JUCE_WEB_BROWSER
 #endif
 
+#ifndef    JUCE_ENABLE_LIVE_CONSTANT_EDITOR
+ //#define JUCE_ENABLE_LIVE_CONSTANT_EDITOR
+#endif
+
 
 #endif  // __JUCE_APPCONFIG_IJU11L__
--- a/JuceLibraryCode/BinaryData.cpp	Wed Aug 27 13:18:27 2014 +0100
+++ b/JuceLibraryCode/BinaryData.cpp	Wed Sep 24 00:29:18 2014 +0100
@@ -429,8 +429,6 @@
     return 0;
 }
 
-const int namedResourceListSize = 3;
-
 const char* namedResourceList[] =
 {
     "tkicon128_png",
--- a/JuceLibraryCode/BinaryData.h	Wed Aug 27 13:18:27 2014 +0100
+++ b/JuceLibraryCode/BinaryData.h	Wed Sep 24 00:29:18 2014 +0100
@@ -4,6 +4,9 @@
 
 */
 
+#ifndef BINARYDATA_H_20480568_INCLUDED
+#define BINARYDATA_H_20480568_INCLUDED
+
 namespace BinaryData
 {
     extern const char*   tkicon128_png;
@@ -19,9 +22,11 @@
     extern const char* namedResourceList[];
 
     // Number of elements in the namedResourceList array.
-    extern const int namedResourceListSize;
+    const int namedResourceListSize = 3;
 
     // If you provide the name of one of the binary resource variables above, this function will
     // return the corresponding data and its size (or a null pointer if the name isn't found).
     const char* getNamedResource (const char* resourceNameUTF8, int& dataSizeInBytes) throw();
 }
+
+#endif
--- a/JuceLibraryCode/JuceHeader.h	Wed Aug 27 13:18:27 2014 +0100
+++ b/JuceLibraryCode/JuceHeader.h	Wed Sep 24 00:29:18 2014 +0100
@@ -32,11 +32,13 @@
  using namespace juce;
 #endif
 
+#if ! JUCE_DONT_DECLARE_PROJECTINFO
 namespace ProjectInfo
 {
     const char* const  projectName    = "TouchKeys";
     const char* const  versionString  = "0.1.0";
     const int          versionNumber  = 0x100;
 }
+#endif
 
 #endif   // __APPHEADERFILE_IJU11L__
--- a/Source/Display/KeyboardDisplay.cpp	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/Display/KeyboardDisplay.cpp	Wed Sep 24 00:29:18 2014 +0100
@@ -159,14 +159,27 @@
 	// Draw the keys themselves first, with analog values if present, then draw the touches
 	for(int key = lowestMidiNote_; key <= highestMidiNote_; key++) {
 		if(keyShape(key) >= 0) {
-			// White keys: draw and move the frame over for the next key
-			drawWhiteKey(0, 0, keyShape(key), key == lowestMidiNote_, key == highestMidiNote_,
-						 /*(key == currentHighlightedKey_) ||*/ midiActiveForKey_[key], keyDivisionsForNote_[key]);
-            // Analog slider should be centered with respect to the back of the white key
-            if(analogSensorsPresent_ && keyShape(key) >= 0) {
-                float sliderOffset = kWhiteKeyBackOffsets[keyShape(key)] + (kWhiteKeyBackWidths[keyShape(key)] - kAnalogSliderWidth) * 0.5;
-                drawAnalogSlider(sliderOffset, kWhiteKeyFrontLength + kWhiteKeyBackLength + kAnalogSliderVerticalSpacing,
-                                 analogValueIsCalibratedForKey_[key], true, analogValueForKey_[key]);
+            if(key < 0 || key > 127) {
+                // Safety check
+                drawWhiteKey(0, 0, keyShape(key), key == lowestMidiNote_, key == highestMidiNote_,
+                             false, 1);
+                // Analog slider should be centered with respect to the back of the white key
+                if(analogSensorsPresent_ && keyShape(key) >= 0) {
+                    float sliderOffset = kWhiteKeyBackOffsets[keyShape(key)] + (kWhiteKeyBackWidths[keyShape(key)] - kAnalogSliderWidth) * 0.5;
+                    drawAnalogSlider(sliderOffset, kWhiteKeyFrontLength + kWhiteKeyBackLength + kAnalogSliderVerticalSpacing,
+                                     false, true, 0);
+                }
+            }
+            else {
+                // White keys: draw and move the frame over for the next key
+                drawWhiteKey(0, 0, keyShape(key), key == lowestMidiNote_, key == highestMidiNote_,
+                             /*(key == currentHighlightedKey_) ||*/ midiActiveForKey_[key], keyDivisionsForNote_[key]);
+                // Analog slider should be centered with respect to the back of the white key
+                if(analogSensorsPresent_ && keyShape(key) >= 0) {
+                    float sliderOffset = kWhiteKeyBackOffsets[keyShape(key)] + (kWhiteKeyBackWidths[keyShape(key)] - kAnalogSliderWidth) * 0.5;
+                    drawAnalogSlider(sliderOffset, kWhiteKeyFrontLength + kWhiteKeyBackLength + kAnalogSliderVerticalSpacing,
+                                     analogValueIsCalibratedForKey_[key], true, analogValueForKey_[key]);
+                }
             }
 			glTranslatef(kWhiteKeyFrontWidth + kInterKeySpacing, 0, 0);
 		}
@@ -177,10 +190,21 @@
 			float offsetV = kWhiteKeyFrontLength + kWhiteKeyBackLength - kBlackKeyLength;
 
 			glTranslatef(offsetH, offsetV, 0.0);
-			drawBlackKey(0, 0, /*(key == currentHighlightedKey_) ||*/ midiActiveForKey_[key], keyDivisionsForNote_[key]);
-            if(analogSensorsPresent_) {
-                drawAnalogSlider((kBlackKeyWidth - kAnalogSliderWidth) * 0.5, kBlackKeyLength + kAnalogSliderVerticalSpacing,
-                                 analogValueIsCalibratedForKey_[key], false, analogValueForKey_[key]);
+            
+            if(key < 0 || key > 127) {
+                // Safety check
+                drawBlackKey(0, 0, false, 1);
+                if(analogSensorsPresent_) {
+                    drawAnalogSlider((kBlackKeyWidth - kAnalogSliderWidth) * 0.5, kBlackKeyLength + kAnalogSliderVerticalSpacing,
+                                     false, false, 0);
+                }
+            }
+            else {
+                drawBlackKey(0, 0, /*(key == currentHighlightedKey_) ||*/ midiActiveForKey_[key], keyDivisionsForNote_[key]);
+                if(analogSensorsPresent_) {
+                    drawAnalogSlider((kBlackKeyWidth - kAnalogSliderWidth) * 0.5, kBlackKeyLength + kAnalogSliderVerticalSpacing,
+                                     analogValueIsCalibratedForKey_[key], false, analogValueForKey_[key]);
+                }
             }
 			glTranslatef(-offsetH, -offsetV, 0.0);
 		}
--- a/Source/GUI/GraphicsDisplayWindow.h	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/GUI/GraphicsDisplayWindow.h	Wed Sep 24 00:29:18 2014 +0100
@@ -37,7 +37,7 @@
 {
 public:
     GraphicsDisplayWindow(String name, KeyboardDisplay& display)
-    : DocumentWindow(name, Colours::lightgrey, DocumentWindow::allButtons),
+    : DocumentWindow(name, Colours::lightgrey, DocumentWindow::allButtons, false),
       display_(display)
     {
         // Initialize an OpenGL graphics object as the content with a default size
--- a/Source/GUI/PreferencesWindow.h	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/GUI/PreferencesWindow.h	Wed Sep 24 00:29:18 2014 +0100
@@ -22,7 +22,7 @@
 {
 public:
     PreferencesWindow(MainApplicationController& controller)
-    : DocumentWindow("Preferences", Colours::lightgrey, DocumentWindow::allButtons)
+    : DocumentWindow("Preferences", Colours::lightgrey, DocumentWindow::allButtons, false)
     {
         // Make a new preferences component
         preferencesComponent_ = new PreferencesComponent();
--- a/Source/MainApplicationController.h	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/MainApplicationController.h	Wed Sep 24 00:29:18 2014 +0100
@@ -54,7 +54,7 @@
 
 #ifndef TOUCHKEYS_NO_GUI
 #include "GUI/GraphicsDisplayWindow.h"
-#include "GUI/PReferencesWindow.h"
+#include "GUI/PreferencesWindow.h"
 class KeyboardTesterDisplay;
 #endif
 
@@ -232,6 +232,8 @@
     void setKeyboardDisplayWindow(DocumentWindow *window) { keyboardDisplayWindow_ = window; }
     void showKeyboardDisplayWindow() {
         if(keyboardDisplayWindow_ != 0) {
+            keyboardDisplayWindow_->addToDesktop(keyboardDisplayWindow_->getDesktopWindowStyleFlags() 
+						 | ComponentPeer::windowHasCloseButton);
             keyboardDisplayWindow_->setVisible(true);
             keyboardDisplayWindow_->toFront(true);
         }
@@ -239,6 +241,8 @@
     void setPreferencesWindow(PreferencesWindow *window) { preferencesWindow_ = window; }
     void showPreferencesWindow() {
         if(preferencesWindow_ != 0) {
+            preferencesWindow_->addToDesktop(preferencesWindow_->getDesktopWindowStyleFlags() 
+					     | ComponentPeer::windowHasCloseButton);
             preferencesWindow_->setVisible(true);
             preferencesWindow_->toFront(true);
         }
--- a/Source/Mappings/KeyDivision/TouchkeyKeyDivisionMapping.cpp	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/Mappings/KeyDivision/TouchkeyKeyDivisionMapping.cpp	Wed Sep 24 00:29:18 2014 +0100
@@ -29,7 +29,7 @@
 
 const int TouchkeyKeyDivisionMapping::kDefaultNumberOfSegments = 2;
 const timestamp_diff_type TouchkeyKeyDivisionMapping::kDefaultDetectionTimeout = milliseconds_to_timestamp(25.0);
-const int TouchkeyKeyDivisionMapping::kDefaultDetectionParameter = kDetectionParameterYPosition;
+const int TouchkeyKeyDivisionMapping::kDefaultDetectionParameter = kDetectionParameterYPositionAndNumberOfTouches;
 const int TouchkeyKeyDivisionMapping::kDefaultRetriggerNumFrames = 2;
 
 // Main constructor takes references/pointers from objects which keep track
@@ -41,7 +41,7 @@
                                                      Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker)
 : TouchkeyBaseMapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker),
 numberOfSegments_(kDefaultNumberOfSegments), candidateSegment_(-1), detectedSegment_(-1), defaultSegment_(0),
-detectionParameter_(kDefaultDetectionParameter), retriggerable_(false), retriggerNumFrames_(kDefaultRetriggerNumFrames),
+detectionParameter_(kDefaultDetectionParameter), retriggerable_(true), retriggerNumFrames_(kDefaultRetriggerNumFrames),
 retriggerKeepsVelocity_(true),
 midiNoteOnTimestamp_(missing_value<timestamp_type>::missing()), timeout_(kDefaultDetectionTimeout),
 lastNumActiveTouches_(-1)
--- a/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMapping.cpp	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMapping.cpp	Wed Sep 24 00:29:18 2014 +0100
@@ -23,14 +23,20 @@
 */
 
 #include "TouchkeyReleaseAngleMapping.h"
+#include "TouchkeyReleaseAngleMappingFactory.h"
 #include "../MappingFactory.h"
 #include "../../TouchKeys/MidiOutputController.h"
 #include "../MappingScheduler.h"
 
+#define DEBUG_RELEASE_ANGLE_MAPPING
+
 // Class constants
 const int TouchkeyReleaseAngleMapping::kDefaultFilterBufferLength = 30;
 const timestamp_diff_type TouchkeyReleaseAngleMapping::kDefaultMaxLookbackTime = milliseconds_to_timestamp(100);
 
+const float TouchkeyReleaseAngleMapping::kDefaultUpMinimumAngle = 1.0;
+const float TouchkeyReleaseAngleMapping::kDefaultDownMinimumAngle = 1.0;
+
 // Main constructor takes references/pointers from objects which keep track
 // of touch location, continuous key position and the state detected from that
 // position. The PianoKeyboard object is strictly required as it gives access to
@@ -39,16 +45,13 @@
 TouchkeyReleaseAngleMapping::TouchkeyReleaseAngleMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node<KeyTouchFrame>* touchBuffer,
                                                          Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker)
 : TouchkeyBaseMapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker, false),
+  upEnabled_(true), downEnabled_(true), upMinimumAngle_(kDefaultUpMinimumAngle), downMinimumAngle_(kDefaultDownMinimumAngle),
   pastSamples_(kDefaultFilterBufferLength), maxLookbackTime_(kDefaultMaxLookbackTime)
 {
+    for(int i = 0; i < RELEASE_ANGLE_MAX_SEQUENCE_LENGTH; i++)
+        upNotes_[i] = downNotes_[i] = upVelocities_[i] = downVelocities_[i] = 0;
 }
 
-// Copy constructor
-/*TouchkeyReleaseAngleMapping::TouchkeyReleaseAngleMapping(TouchkeyReleaseAngleMapping const& obj)
-: TouchkeyBaseMapping(obj), pastSamples_(obj.pastSamples_), maxLookbackTime_(obj.maxLookbackTime_)
-{
-}*/
-
 // Reset state back to defaults
 void TouchkeyReleaseAngleMapping::reset() {
     ScopedLock sl(sampleBufferMutex_);
@@ -62,6 +65,64 @@
     // Message is only sent at release; resend may not apply here.
 }
 
+// Parameters for release angle algorithm
+void TouchkeyReleaseAngleMapping::setWindowSize(float windowSize) {
+    // This was passed in in milliseconds and needs to be converted to a timestamp type
+    maxLookbackTime_ = milliseconds_to_timestamp(windowSize);
+}
+
+void TouchkeyReleaseAngleMapping::setUpMessagesEnabled(bool enable) {
+    upEnabled_ = enable;
+}
+
+void TouchkeyReleaseAngleMapping::setDownMessagesEnabled(bool enable) {
+    downEnabled_ = enable;
+}
+
+void TouchkeyReleaseAngleMapping::setUpMinimumAngle(float minAngle) {
+    upMinimumAngle_ = fabsf(minAngle);
+}
+
+void TouchkeyReleaseAngleMapping::setUpNote(int sequence, int note) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return;
+    if(note < 0 || note > 127)
+        upNotes_[sequence] = 0;
+    else
+        upNotes_[sequence] = note;
+}
+
+void TouchkeyReleaseAngleMapping::setUpVelocity(int sequence, int velocity) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return;
+    if(velocity < 0 || velocity > 127)
+        upVelocities_[sequence] = 0;
+    else
+        upVelocities_[sequence] = velocity;
+}
+
+void TouchkeyReleaseAngleMapping::setDownMinimumAngle(float minAngle) {
+    downMinimumAngle_ = fabsf(minAngle);
+}
+
+void TouchkeyReleaseAngleMapping::setDownNote(int sequence, int note) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return;
+    if(note < 0 || note > 127)
+        downNotes_[sequence] = 0;
+    else
+        downNotes_[sequence] = note;
+}
+
+void TouchkeyReleaseAngleMapping::setDownVelocity(int sequence, int velocity) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return;
+    if(velocity < 0 || velocity > 127)
+        downVelocities_[sequence] = 0;
+    else
+        downVelocities_[sequence] = velocity;
+}
+
 // This method receives data from the touch buffer or possibly the continuous key angle (not used here)
 void TouchkeyReleaseAngleMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) {
     if(who == touchBuffer_) {
@@ -85,7 +146,11 @@
     return nextScheduledTimestamp_;
 }
 
-void TouchkeyReleaseAngleMapping::processRelease(timestamp_type timestamp) {
+void TouchkeyReleaseAngleMapping::midiNoteOffReceived(int channel) {
+    processRelease();
+}
+
+void TouchkeyReleaseAngleMapping::processRelease(/*timestamp_type timestamp*/) {
     if(!noteIsOn_) {
         return;
     }
@@ -96,15 +161,16 @@
     float calculatedVelocity = missing_value<float>::missing();
     bool touchWasOn = false;
     
-    
-    //std::cout << "processRelease begin = " << pastSamples_.beginIndex() << " end = " << pastSamples_.endIndex() << "\n";
-    
     if(!pastSamples_.empty()) {
         Node<KeyTouchFrame>::size_type index = pastSamples_.endIndex() - 1;
         Node<KeyTouchFrame>::size_type mostRecentTouchPresentIndex = pastSamples_.endIndex() - 1;
+        timestamp_type lastTimestamp = pastSamples_.timestampAt(index);
+        
         while(index >= pastSamples_.beginIndex()) {
-            //std::cout << "examining sample " << index << " with " << pastSamples_[index].count << " touches and time diff " << timestamp - pastSamples_.timestampAt(index) << "\n";
-            if(timestamp - pastSamples_.timestampAt(index) >= maxLookbackTime_)
+#ifdef DEBUG_RELEASE_ANGLE_MAPPING
+            std::cout << "examining sample " << index << " with " << pastSamples_[index].count << " touches and time diff " << lastTimestamp - pastSamples_.timestampAt(index) << "\n";
+#endif
+            if(lastTimestamp - pastSamples_.timestampAt(index) >= maxLookbackTime_)
                 break;
             if(pastSamples_[index].count == 0) {
                 if(touchWasOn) {
@@ -129,8 +195,6 @@
         if(index < pastSamples_.beginIndex())
             index =  pastSamples_.beginIndex();
         
-        //std::cout << "done\n";
-        
         // Need at least two points for this calculation to work
         timestamp_type endingTimestamp = pastSamples_.timestampAt(mostRecentTouchPresentIndex);
         timestamp_type startingTimestamp = pastSamples_.timestampAt(index);
@@ -140,32 +204,78 @@
             calculatedVelocity = (endingPosition - startingPosition) / (endingTimestamp - startingTimestamp);
         }
         else { // DEBUG
+#ifdef DEBUG_RELEASE_ANGLE_MAPPING
             std::cout << "Found 0 timestamp difference on key release (indices " << index << " and " << pastSamples_.endIndex() - 1 << "\n";
+#endif
         }
     }
-    else
+    else {
+#ifdef DEBUG_RELEASE_ANGLE_MAPPING
         std::cout << "Found empty touch buffer on key release\n";
+#endif
+    }
     
     sampleBufferMutex_.exit();
     
     if(!missing_value<float>::isMissing(calculatedVelocity)) {
+#ifdef DEBUG_RELEASE_ANGLE_MAPPING
         std::cout << "Found release velocity " << calculatedVelocity << " on note " << noteNumber_ << std::endl;
+#endif
         sendReleaseAngleMessage(calculatedVelocity);
     }
 
     
-    // Check if we're suppose to clean up now
+    // Check if we're supposed to clean up now
     finished_ = true;
     if(finishRequested_)
         acknowledgeFinish();
     // KLUDGE
 }
 
-#define TROMBONE
 void TouchkeyReleaseAngleMapping::sendReleaseAngleMessage(float releaseAngle, bool force) {
     if(force || !suspended_) {
         keyboard_.sendMessage("/touchkeys/releaseangle", "if", noteNumber_, releaseAngle, LO_ARGS_END);
         
+        if(keyboard_.midiOutputController() == 0)
+            return;
+        
+        int port = static_cast<TouchkeyReleaseAngleMappingFactory*>(factory_)->segment().outputPort();
+        int ch = keyboard_.key(noteNumber_)->midiChannel();
+        
+        // Check if the release angle exceeds either the up or down threshold
+        if(releaseAngle > 0 && fabs(releaseAngle) >= upMinimumAngle_ && upEnabled_) {
+#ifdef DEBUG_RELEASE_ANGLE_MAPPING
+            std::cout << "Send up-release messages for note " << noteNumber_ << " on channel " << ch << "\n";
+#endif
+            // Send key switches: note on and note off in reverse orders
+            for(int i = 0; i < RELEASE_ANGLE_MAX_SEQUENCE_LENGTH; i++) {
+                if(upNotes_[i] != 0)
+                    keyboard_.midiOutputController()->sendNoteOn(port, ch, upNotes_[i], upVelocities_[i]);
+            }
+            
+            for(int i = RELEASE_ANGLE_MAX_SEQUENCE_LENGTH - 1; i >= 0; i--) {
+                if(upNotes_[i] != 0)
+                    keyboard_.midiOutputController()->sendNoteOff(port, ch, upNotes_[i]);
+            }
+        }
+        else if(releaseAngle < 0 && fabs(releaseAngle) >= downMinimumAngle_ && downEnabled_) {
+#ifdef DEBUG_RELEASE_ANGLE_MAPPING
+            std::cout << "Send down-release messages for note " << noteNumber_ << " on channel " << ch << "\n";
+#endif
+            // Send key switches: note on and note off in reverse orders
+            for(int i = 0; i < RELEASE_ANGLE_MAX_SEQUENCE_LENGTH; i++) {
+                if(downNotes_[i] != 0)
+                    keyboard_.midiOutputController()->sendNoteOn(port, ch, downNotes_[i], downVelocities_[i]);
+            }
+            
+            for(int i = RELEASE_ANGLE_MAX_SEQUENCE_LENGTH - 1; i >= 0; i--) {
+                if(downNotes_[i] != 0)
+                    keyboard_.midiOutputController()->sendNoteOff(port, ch, downNotes_[i]);
+            }
+        }
+        
+        // TODO: delayed release
+        
 #ifdef TROMBONE
         // KLUDGE: figure out how to do this more elegantly
         if(keyboard_.midiOutputController() != 0) {
--- a/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMapping.h	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMapping.h	Wed Sep 24 00:29:18 2014 +0100
@@ -27,10 +27,14 @@
 
 #include "../TouchkeyBaseMapping.h"
 
+#define RELEASE_ANGLE_MAX_SEQUENCE_LENGTH 16
+
 // This class handles the detection of finger motion specifically at
 // note release, which can be used to trigger specific release effects.
 
 class TouchkeyReleaseAngleMapping : public TouchkeyBaseMapping {
+    friend class TouchkeyReleaseAngleMappingFactory;
+    
 private:
     // Default values
     /*constexpr static const int kDefaultFilterBufferLength = 30;
@@ -39,6 +43,9 @@
     static const int kDefaultFilterBufferLength;
     static const timestamp_diff_type kDefaultMaxLookbackTime;
     
+    static const float kDefaultUpMinimumAngle;
+    static const float kDefaultDownMinimumAngle;
+    
 public:
 	// ***** Constructors *****
 	
@@ -57,6 +64,17 @@
     // Resend the current state of all parameters
     void resend();
     
+    // Parameters for release angle algorithm
+    void setWindowSize(float windowSize);
+    void setUpMessagesEnabled(bool enable);
+    void setDownMessagesEnabled(bool enable);
+    void setUpMinimumAngle(float minAngle);
+    void setUpNote(int sequence, int note);
+    void setUpVelocity(int sequence, int velocity);
+    void setDownMinimumAngle(float minAngle);
+    void setDownNote(int sequence, int note);
+    void setDownVelocity(int sequence, int velocity);
+    
 	// ***** Evaluators *****
     
     // This method receives triggers whenever events occur in the touch data or the
@@ -67,10 +85,13 @@
     // This method handles the OSC message transmission. It should be run in the Scheduler
     // thread provided by PianoKeyboard.
     timestamp_type performMapping();
+    
+    // Called when MIDI note release happens
+    void midiNoteOffReceived(int channel);
 
     // ***** Specific Methods *****
     // Process the release by calculating the angle
-    void processRelease(timestamp_type timestamp);
+    void processRelease(/*timestamp_type timestamp*/);
     
     timestamp_type releaseKeySwitch();
     
@@ -81,6 +102,14 @@
     
 	// ***** Member Variables *****
     
+    bool upEnabled_, downEnabled_;          // Whether messages are enabled for upward and downward releases
+    float upMinimumAngle_;                  // Minimum release angle for trigger for up...
+    float downMinimumAngle_;                // ...and down cases
+    int upNotes_[RELEASE_ANGLE_MAX_SEQUENCE_LENGTH];       // Notes and velocities to send on upward
+    int upVelocities_[RELEASE_ANGLE_MAX_SEQUENCE_LENGTH];  // and downward release
+    int downNotes_[RELEASE_ANGLE_MAX_SEQUENCE_LENGTH];
+    int downVelocities_[RELEASE_ANGLE_MAX_SEQUENCE_LENGTH];
+    
     Node<KeyTouchFrame> pastSamples_;           // Locations of touch
     timestamp_diff_type maxLookbackTime_;       // How long to look backwards to find release velocity
     CriticalSection sampleBufferMutex_;         // Mutex to protect threaded access to sample buffer
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingExtendedEditor.cpp	Wed Sep 24 00:29:18 2014 +0100
@@ -0,0 +1,785 @@
+/*
+  ==============================================================================
+
+  This is an automatically generated GUI class created by the Introjucer!
+
+  Be careful when adding custom code to these files, as only the code within
+  the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
+  and re-saved.
+
+  Created with Introjucer version: 3.1.0
+
+  ------------------------------------------------------------------------------
+
+  The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions"
+  Copyright 2004-13 by Raw Material Software Ltd.
+
+  ==============================================================================
+*/
+
+//[Headers] You can add your own extra header files here...
+//[/Headers]
+
+#include "TouchkeyReleaseAngleMappingExtendedEditor.h"
+
+
+//[MiscUserDefs] You can add your own user definitions and misc code here...
+//[/MiscUserDefs]
+
+//==============================================================================
+TouchkeyReleaseAngleMappingExtendedEditor::TouchkeyReleaseAngleMappingExtendedEditor (TouchkeyReleaseAngleMappingFactory& factory)
+    : factory_(factory)
+{
+    addAndMakeVisible (titleLabel = new Label ("title label",
+                                               TRANS("Release Angle Mapping (Zone N, #M)")));
+    titleLabel->setFont (Font (15.00f, Font::bold));
+    titleLabel->setJustificationType (Justification::centredLeft);
+    titleLabel->setEditable (false, false, false);
+    titleLabel->setColour (TextEditor::textColourId, Colours::black);
+    titleLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (presetLabel = new Label ("preset label",
+                                                TRANS("Preset:")));
+    presetLabel->setFont (Font (15.00f, Font::plain));
+    presetLabel->setJustificationType (Justification::centredLeft);
+    presetLabel->setEditable (false, false, false);
+    presetLabel->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (presetComboBox = new ComboBox ("parameter combo box"));
+    presetComboBox->setEditableText (false);
+    presetComboBox->setJustificationType (Justification::centredLeft);
+    presetComboBox->setTextWhenNothingSelected (String::empty);
+    presetComboBox->setTextWhenNoChoicesAvailable (TRANS("(no choices)"));
+    presetComboBox->addListener (this);
+
+    addAndMakeVisible (presetLabel2 = new Label ("preset label",
+                                                 TRANS("Window Length:")));
+    presetLabel2->setFont (Font (15.00f, Font::plain));
+    presetLabel2->setJustificationType (Justification::centredLeft);
+    presetLabel2->setEditable (false, false, false);
+    presetLabel2->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel2->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (windowLengthEditor = new TextEditor ("range low text editor"));
+    windowLengthEditor->setMultiLine (false);
+    windowLengthEditor->setReturnKeyStartsNewLine (false);
+    windowLengthEditor->setReadOnly (false);
+    windowLengthEditor->setScrollbarsShown (true);
+    windowLengthEditor->setCaretVisible (true);
+    windowLengthEditor->setPopupMenuEnabled (true);
+    windowLengthEditor->setText (String::empty);
+
+    addAndMakeVisible (presetLabel3 = new Label ("preset label",
+                                                 TRANS("ms. before release")));
+    presetLabel3->setFont (Font (15.00f, Font::plain));
+    presetLabel3->setJustificationType (Justification::centredLeft);
+    presetLabel3->setEditable (false, false, false);
+    presetLabel3->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel3->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (presetLabel4 = new Label ("preset label",
+                                                 TRANS("Release Moving Up")));
+    presetLabel4->setFont (Font (15.00f, Font::bold));
+    presetLabel4->setJustificationType (Justification::centredLeft);
+    presetLabel4->setEditable (false, false, false);
+    presetLabel4->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel4->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (upMinSpeedEditor = new TextEditor ("up release speed editor"));
+    upMinSpeedEditor->setMultiLine (false);
+    upMinSpeedEditor->setReturnKeyStartsNewLine (false);
+    upMinSpeedEditor->setReadOnly (false);
+    upMinSpeedEditor->setScrollbarsShown (true);
+    upMinSpeedEditor->setCaretVisible (true);
+    upMinSpeedEditor->setPopupMenuEnabled (true);
+    upMinSpeedEditor->setText (String::empty);
+
+    addAndMakeVisible (presetLabel5 = new Label ("preset label",
+                                                 TRANS("Min. release speed:")));
+    presetLabel5->setFont (Font (15.00f, Font::plain));
+    presetLabel5->setJustificationType (Justification::centredRight);
+    presetLabel5->setEditable (false, false, false);
+    presetLabel5->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel5->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (upNote1Editor = new TextEditor ("up note 1 editor"));
+    upNote1Editor->setMultiLine (false);
+    upNote1Editor->setReturnKeyStartsNewLine (false);
+    upNote1Editor->setReadOnly (false);
+    upNote1Editor->setScrollbarsShown (true);
+    upNote1Editor->setCaretVisible (true);
+    upNote1Editor->setPopupMenuEnabled (true);
+    upNote1Editor->setText (String::empty);
+
+    addAndMakeVisible (presetLabel6 = new Label ("preset label",
+                                                 TRANS("Send notes:")));
+    presetLabel6->setFont (Font (15.00f, Font::plain));
+    presetLabel6->setJustificationType (Justification::centredRight);
+    presetLabel6->setEditable (false, false, false);
+    presetLabel6->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel6->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (upNote2Editor = new TextEditor ("up note 2 editor"));
+    upNote2Editor->setMultiLine (false);
+    upNote2Editor->setReturnKeyStartsNewLine (false);
+    upNote2Editor->setReadOnly (false);
+    upNote2Editor->setScrollbarsShown (true);
+    upNote2Editor->setCaretVisible (true);
+    upNote2Editor->setPopupMenuEnabled (true);
+    upNote2Editor->setText (String::empty);
+
+    addAndMakeVisible (upNote3Editor = new TextEditor ("up note 3 editor"));
+    upNote3Editor->setMultiLine (false);
+    upNote3Editor->setReturnKeyStartsNewLine (false);
+    upNote3Editor->setReadOnly (false);
+    upNote3Editor->setScrollbarsShown (true);
+    upNote3Editor->setCaretVisible (true);
+    upNote3Editor->setPopupMenuEnabled (true);
+    upNote3Editor->setText (String::empty);
+
+    addAndMakeVisible (presetLabel7 = new Label ("preset label",
+                                                 TRANS("With velocities:")));
+    presetLabel7->setFont (Font (15.00f, Font::plain));
+    presetLabel7->setJustificationType (Justification::centredRight);
+    presetLabel7->setEditable (false, false, false);
+    presetLabel7->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel7->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (upVelocity1Editor = new TextEditor ("up velocity 1 editor"));
+    upVelocity1Editor->setMultiLine (false);
+    upVelocity1Editor->setReturnKeyStartsNewLine (false);
+    upVelocity1Editor->setReadOnly (false);
+    upVelocity1Editor->setScrollbarsShown (true);
+    upVelocity1Editor->setCaretVisible (true);
+    upVelocity1Editor->setPopupMenuEnabled (true);
+    upVelocity1Editor->setText (String::empty);
+
+    addAndMakeVisible (upVelocity2Editor = new TextEditor ("up velocity 2 editor"));
+    upVelocity2Editor->setMultiLine (false);
+    upVelocity2Editor->setReturnKeyStartsNewLine (false);
+    upVelocity2Editor->setReadOnly (false);
+    upVelocity2Editor->setScrollbarsShown (true);
+    upVelocity2Editor->setCaretVisible (true);
+    upVelocity2Editor->setPopupMenuEnabled (true);
+    upVelocity2Editor->setText (String::empty);
+
+    addAndMakeVisible (upVelocity3Editor = new TextEditor ("up velocity 3 editor"));
+    upVelocity3Editor->setMultiLine (false);
+    upVelocity3Editor->setReturnKeyStartsNewLine (false);
+    upVelocity3Editor->setReadOnly (false);
+    upVelocity3Editor->setScrollbarsShown (true);
+    upVelocity3Editor->setCaretVisible (true);
+    upVelocity3Editor->setPopupMenuEnabled (true);
+    upVelocity3Editor->setText (String::empty);
+
+    addAndMakeVisible (presetLabel8 = new Label ("preset label",
+                                                 TRANS("Release Moving Down")));
+    presetLabel8->setFont (Font (15.00f, Font::bold));
+    presetLabel8->setJustificationType (Justification::centredLeft);
+    presetLabel8->setEditable (false, false, false);
+    presetLabel8->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel8->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (downMinSpeedEditor = new TextEditor ("down release speed editor"));
+    downMinSpeedEditor->setMultiLine (false);
+    downMinSpeedEditor->setReturnKeyStartsNewLine (false);
+    downMinSpeedEditor->setReadOnly (false);
+    downMinSpeedEditor->setScrollbarsShown (true);
+    downMinSpeedEditor->setCaretVisible (true);
+    downMinSpeedEditor->setPopupMenuEnabled (true);
+    downMinSpeedEditor->setText (String::empty);
+
+    addAndMakeVisible (presetLabel9 = new Label ("preset label",
+                                                 TRANS("Min. release speed:")));
+    presetLabel9->setFont (Font (15.00f, Font::plain));
+    presetLabel9->setJustificationType (Justification::centredRight);
+    presetLabel9->setEditable (false, false, false);
+    presetLabel9->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel9->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (downNote1Editor = new TextEditor ("down note 1 editor"));
+    downNote1Editor->setMultiLine (false);
+    downNote1Editor->setReturnKeyStartsNewLine (false);
+    downNote1Editor->setReadOnly (false);
+    downNote1Editor->setScrollbarsShown (true);
+    downNote1Editor->setCaretVisible (true);
+    downNote1Editor->setPopupMenuEnabled (true);
+    downNote1Editor->setText (String::empty);
+
+    addAndMakeVisible (presetLabel10 = new Label ("preset label",
+                                                  TRANS("Send notes:")));
+    presetLabel10->setFont (Font (15.00f, Font::plain));
+    presetLabel10->setJustificationType (Justification::centredRight);
+    presetLabel10->setEditable (false, false, false);
+    presetLabel10->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel10->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (downNote2Editor = new TextEditor ("down note 2 editor"));
+    downNote2Editor->setMultiLine (false);
+    downNote2Editor->setReturnKeyStartsNewLine (false);
+    downNote2Editor->setReadOnly (false);
+    downNote2Editor->setScrollbarsShown (true);
+    downNote2Editor->setCaretVisible (true);
+    downNote2Editor->setPopupMenuEnabled (true);
+    downNote2Editor->setText (String::empty);
+
+    addAndMakeVisible (downNote3Editor = new TextEditor ("down note 3 editor"));
+    downNote3Editor->setMultiLine (false);
+    downNote3Editor->setReturnKeyStartsNewLine (false);
+    downNote3Editor->setReadOnly (false);
+    downNote3Editor->setScrollbarsShown (true);
+    downNote3Editor->setCaretVisible (true);
+    downNote3Editor->setPopupMenuEnabled (true);
+    downNote3Editor->setText (String::empty);
+
+    addAndMakeVisible (presetLabel11 = new Label ("preset label",
+                                                  TRANS("With velocities:")));
+    presetLabel11->setFont (Font (15.00f, Font::plain));
+    presetLabel11->setJustificationType (Justification::centredRight);
+    presetLabel11->setEditable (false, false, false);
+    presetLabel11->setColour (TextEditor::textColourId, Colours::black);
+    presetLabel11->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
+
+    addAndMakeVisible (downVelocity1Editor = new TextEditor ("down velocity 1 editor"));
+    downVelocity1Editor->setMultiLine (false);
+    downVelocity1Editor->setReturnKeyStartsNewLine (false);
+    downVelocity1Editor->setReadOnly (false);
+    downVelocity1Editor->setScrollbarsShown (true);
+    downVelocity1Editor->setCaretVisible (true);
+    downVelocity1Editor->setPopupMenuEnabled (true);
+    downVelocity1Editor->setText (String::empty);
+
+    addAndMakeVisible (downVelocity2Editor = new TextEditor ("down velocity 2 editor"));
+    downVelocity2Editor->setMultiLine (false);
+    downVelocity2Editor->setReturnKeyStartsNewLine (false);
+    downVelocity2Editor->setReadOnly (false);
+    downVelocity2Editor->setScrollbarsShown (true);
+    downVelocity2Editor->setCaretVisible (true);
+    downVelocity2Editor->setPopupMenuEnabled (true);
+    downVelocity2Editor->setText (String::empty);
+
+    addAndMakeVisible (downVelocity3Editor = new TextEditor ("down velocity 3 editor"));
+    downVelocity3Editor->setMultiLine (false);
+    downVelocity3Editor->setReturnKeyStartsNewLine (false);
+    downVelocity3Editor->setReadOnly (false);
+    downVelocity3Editor->setScrollbarsShown (true);
+    downVelocity3Editor->setCaretVisible (true);
+    downVelocity3Editor->setPopupMenuEnabled (true);
+    downVelocity3Editor->setText (String::empty);
+
+    addAndMakeVisible (upEnableButton = new ToggleButton ("up enable button"));
+    upEnableButton->setButtonText (TRANS("Enable"));
+    upEnableButton->addListener (this);
+
+    addAndMakeVisible (downEnableButton = new ToggleButton ("down enable button"));
+    downEnableButton->setButtonText (TRANS("Enable"));
+    downEnableButton->addListener (this);
+
+
+    //[UserPreSize]
+    for(int i = 0; i < factory_.getNumConfigurations(); i++) {
+        presetComboBox->addItem(factory_.getConfigurationName(i).c_str(), i+1);
+    }
+    
+    windowLengthEditor->addListener(this);
+    upMinSpeedEditor->addListener(this);
+    downMinSpeedEditor->addListener(this);
+    upNote1Editor->addListener(this);
+    upNote2Editor->addListener(this);
+    upNote3Editor->addListener(this);
+    upVelocity1Editor->addListener(this);
+    upVelocity2Editor->addListener(this);
+    upVelocity3Editor->addListener(this);
+    downNote1Editor->addListener(this);
+    downNote2Editor->addListener(this);
+    downNote3Editor->addListener(this);
+    downVelocity1Editor->addListener(this);
+    downVelocity2Editor->addListener(this);
+    downVelocity3Editor->addListener(this);
+    //[/UserPreSize]
+
+    setSize (342, 328);
+
+
+    //[Constructor] You can add your own custom stuff here..
+    //[/Constructor]
+}
+
+TouchkeyReleaseAngleMappingExtendedEditor::~TouchkeyReleaseAngleMappingExtendedEditor()
+{
+    //[Destructor_pre]. You can add your own custom destruction code here..
+    //[/Destructor_pre]
+
+    titleLabel = nullptr;
+    presetLabel = nullptr;
+    presetComboBox = nullptr;
+    presetLabel2 = nullptr;
+    windowLengthEditor = nullptr;
+    presetLabel3 = nullptr;
+    presetLabel4 = nullptr;
+    upMinSpeedEditor = nullptr;
+    presetLabel5 = nullptr;
+    upNote1Editor = nullptr;
+    presetLabel6 = nullptr;
+    upNote2Editor = nullptr;
+    upNote3Editor = nullptr;
+    presetLabel7 = nullptr;
+    upVelocity1Editor = nullptr;
+    upVelocity2Editor = nullptr;
+    upVelocity3Editor = nullptr;
+    presetLabel8 = nullptr;
+    downMinSpeedEditor = nullptr;
+    presetLabel9 = nullptr;
+    downNote1Editor = nullptr;
+    presetLabel10 = nullptr;
+    downNote2Editor = nullptr;
+    downNote3Editor = nullptr;
+    presetLabel11 = nullptr;
+    downVelocity1Editor = nullptr;
+    downVelocity2Editor = nullptr;
+    downVelocity3Editor = nullptr;
+    upEnableButton = nullptr;
+    downEnableButton = nullptr;
+
+
+    //[Destructor]. You can add your own custom destruction code here..
+    //[/Destructor]
+}
+
+//==============================================================================
+void TouchkeyReleaseAngleMappingExtendedEditor::paint (Graphics& g)
+{
+    //[UserPrePaint] Add your own custom painting code here..
+    //[/UserPrePaint]
+
+    g.fillAll (Colour (0xffd2d2d2));
+
+    //[UserPaint] Add your own custom painting code here..
+    //[/UserPaint]
+}
+
+void TouchkeyReleaseAngleMappingExtendedEditor::resized()
+{
+    titleLabel->setBounds (8, 8, 424, 24);
+    presetLabel->setBounds (8, 40, 56, 24);
+    presetComboBox->setBounds (64, 40, 264, 24);
+    presetLabel2->setBounds (8, 80, 112, 24);
+    windowLengthEditor->setBounds (120, 80, 56, 24);
+    presetLabel3->setBounds (176, 80, 136, 24);
+    presetLabel4->setBounds (8, 112, 136, 24);
+    upMinSpeedEditor->setBounds (144, 136, 56, 24);
+    presetLabel5->setBounds (8, 136, 136, 24);
+    upNote1Editor->setBounds (144, 160, 56, 24);
+    presetLabel6->setBounds (8, 160, 136, 24);
+    upNote2Editor->setBounds (208, 160, 56, 24);
+    upNote3Editor->setBounds (272, 160, 56, 24);
+    presetLabel7->setBounds (8, 184, 136, 24);
+    upVelocity1Editor->setBounds (144, 184, 56, 24);
+    upVelocity2Editor->setBounds (208, 184, 56, 24);
+    upVelocity3Editor->setBounds (272, 184, 56, 24);
+    presetLabel8->setBounds (8, 216, 160, 24);
+    downMinSpeedEditor->setBounds (144, 240, 56, 24);
+    presetLabel9->setBounds (8, 240, 136, 24);
+    downNote1Editor->setBounds (144, 264, 56, 24);
+    presetLabel10->setBounds (8, 264, 136, 24);
+    downNote2Editor->setBounds (208, 264, 56, 24);
+    downNote3Editor->setBounds (272, 264, 56, 24);
+    presetLabel11->setBounds (8, 288, 136, 24);
+    downVelocity1Editor->setBounds (144, 288, 56, 24);
+    downVelocity2Editor->setBounds (208, 288, 56, 24);
+    downVelocity3Editor->setBounds (272, 288, 56, 24);
+    upEnableButton->setBounds (208, 112, 72, 24);
+    downEnableButton->setBounds (208, 216, 72, 24);
+    //[UserResized] Add your own custom resize handling here..
+    //[/UserResized]
+}
+
+void TouchkeyReleaseAngleMappingExtendedEditor::comboBoxChanged (ComboBox* comboBoxThatHasChanged)
+{
+    //[UsercomboBoxChanged_Pre]
+    //[/UsercomboBoxChanged_Pre]
+
+    if (comboBoxThatHasChanged == presetComboBox)
+    {
+        //[UserComboBoxCode_presetComboBox] -- add your combo box handling code here..
+        int index = presetComboBox->getSelectedItemIndex();
+        factory_.setCurrentConfiguration(index);
+        //[/UserComboBoxCode_presetComboBox]
+    }
+
+    //[UsercomboBoxChanged_Post]
+    //[/UsercomboBoxChanged_Post]
+}
+
+void TouchkeyReleaseAngleMappingExtendedEditor::buttonClicked (Button* buttonThatWasClicked)
+{
+    //[UserbuttonClicked_Pre]
+    //[/UserbuttonClicked_Pre]
+
+    if (buttonThatWasClicked == upEnableButton)
+    {
+        //[UserButtonCode_upEnableButton] -- add your button handler code here..
+        factory_.setUpMessagesEnabled(upEnableButton->getToggleState());
+        //[/UserButtonCode_upEnableButton]
+    }
+    else if (buttonThatWasClicked == downEnableButton)
+    {
+        //[UserButtonCode_downEnableButton] -- add your button handler code here..
+        factory_.setDownMessagesEnabled(downEnableButton->getToggleState());
+        //[/UserButtonCode_downEnableButton]
+    }
+
+    //[UserbuttonClicked_Post]
+    //[/UserbuttonClicked_Post]
+}
+
+
+
+//[MiscUserCode] You can add your own definitions of your custom methods or any other code here...
+
+void TouchkeyReleaseAngleMappingExtendedEditor::textEditorReturnKeyPressed(TextEditor &editor)
+{
+    if(&editor == windowLengthEditor) {
+        float windowLength = atof(windowLengthEditor->getText().toUTF8());
+        factory_.setWindowSize(windowLength);
+    }
+    else if(&editor == upMinSpeedEditor) {
+        float speed = atof(upMinSpeedEditor->getText().toUTF8());
+        factory_.setUpMinimumAngle(speed);
+    }
+    else if(&editor == downMinSpeedEditor) {
+        float speed = atof(downMinSpeedEditor->getText().toUTF8());
+        factory_.setDownMinimumAngle(speed);
+    }
+    else {
+        // All the other editors are int values
+        int value = atoi(editor.getText().toUTF8());
+
+        if(&editor == upNote1Editor)
+            factory_.setUpNote(0, value);
+        else if(&editor == upNote2Editor)
+            factory_.setUpNote(1, value);
+        else if(&editor == upNote3Editor)
+            factory_.setUpNote(2, value);
+        else if(&editor == upVelocity1Editor)
+            factory_.setUpVelocity(0, value);
+        else if(&editor == upVelocity2Editor)
+            factory_.setUpVelocity(1, value);
+        else if(&editor == upVelocity3Editor)
+            factory_.setUpVelocity(2, value);
+        else if(&editor == downNote1Editor)
+            factory_.setDownNote(0, value);
+        else if(&editor == downNote2Editor)
+            factory_.setDownNote(1, value);
+        else if(&editor == downNote3Editor)
+            factory_.setDownNote(2, value);
+        else if(&editor == downVelocity1Editor)
+            factory_.setDownVelocity(0, value);
+        else if(&editor == downVelocity2Editor)
+            factory_.setDownVelocity(1, value);
+        else if(&editor == downVelocity3Editor)
+            factory_.setDownVelocity(2, value);
+    }
+}
+
+void TouchkeyReleaseAngleMappingExtendedEditor::textEditorEscapeKeyPressed(TextEditor &editor)
+{
+
+}
+
+void TouchkeyReleaseAngleMappingExtendedEditor::textEditorFocusLost(TextEditor &editor)
+{
+    textEditorReturnKeyPressed(editor);
+}
+
+void TouchkeyReleaseAngleMappingExtendedEditor::synchronize()
+{
+    // Set the title label
+    titleLabel->setText(getDescriptionHelper("Release Angle Mapping"), dontSendNotification);
+
+    // Update the editors to reflect the current status
+    if(!windowLengthEditor->hasKeyboardFocus(true)) {
+        float value = factory_.getWindowSize();
+        char st[16];
+#ifdef _MSC_VER
+		_snprintf_s(st, 16, _TRUNCATE, "%.0f", value);
+#else
+        snprintf(st, 16, "%.0f", value);
+#endif
+        windowLengthEditor->setText(st);
+    }
+
+    if(!upMinSpeedEditor->hasKeyboardFocus(true)) {
+        float value = factory_.getUpMinimumAngle();
+        char st[16];
+#ifdef _MSC_VER
+		_snprintf_s(st, 16, _TRUNCATE, "%.2f", value);
+#else
+        snprintf(st, 16, "%.2f", value);
+#endif
+
+        upMinSpeedEditor->setText(st);
+    }
+
+    if(!downMinSpeedEditor->hasKeyboardFocus(true)) {
+        float value = factory_.getDownMinimumAngle();
+        char st[16];
+#ifdef _MSC_VER
+		_snprintf_s(st, 16, _TRUNCATE, "%.2f", value);
+#else
+        snprintf(st, 16, "%.2f", value);
+#endif
+
+        downMinSpeedEditor->setText(st);
+    }
+    
+    char st[16];
+    
+    if(!upNote1Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getUpNote(0));
+        upNote1Editor->setText(st);
+    }
+    if(!upNote2Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getUpNote(1));
+        upNote2Editor->setText(st);
+    }
+    if(!upNote3Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getUpNote(2));
+        upNote3Editor->setText(st);
+    }
+    if(!upVelocity1Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getUpVelocity(0));
+        upVelocity1Editor->setText(st);
+    }
+    if(!upVelocity2Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getUpVelocity(1));
+        upVelocity2Editor->setText(st);
+    }
+    if(!upVelocity3Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getUpVelocity(2));
+        upVelocity3Editor->setText(st);
+    }
+    if(!downNote1Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getDownNote(0));
+        downNote1Editor->setText(st);
+    }
+    if(!downNote2Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getDownNote(1));
+        downNote2Editor->setText(st);
+    }
+    if(!downNote3Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getDownNote(2));
+        downNote3Editor->setText(st);
+    }
+    if(!downVelocity1Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getDownVelocity(0));
+        downVelocity1Editor->setText(st);
+    }
+    if(!downVelocity2Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getDownVelocity(1));
+        downVelocity2Editor->setText(st);
+    }
+    if(!downVelocity3Editor->hasKeyboardFocus(true)) {
+        intToString(st, factory_.getDownVelocity(2));
+        downVelocity3Editor->setText(st);
+    }
+
+    upEnableButton->setToggleState(factory_.getUpMessagesEnabled(), dontSendNotification);
+    downEnableButton->setToggleState(factory_.getDownMessageEnabled(), dontSendNotification);
+
+    int configuration = factory_.getCurrentConfiguration();
+    if(configuration >= 0)
+        presetComboBox->setSelectedItemIndex(configuration, dontSendNotification);
+    else
+        presetComboBox->setText("");
+}
+
+// Return a human-readable description of this mapping for the window
+String TouchkeyReleaseAngleMappingExtendedEditor::getDescription() {
+    return getDescriptionHelper("Release Angle");
+}
+
+// Return a human-readable description of this mapping for the window
+String TouchkeyReleaseAngleMappingExtendedEditor::getDescriptionHelper(String baseName) {
+    String desc = baseName;
+
+    desc += " (Zone ";
+
+    int zone = factory_.segment().outputPort();
+    desc += zone;
+    desc += ", #";
+
+    int mappingNumber = factory_.segment().indexOfMappingFactory(&factory_);
+    desc += mappingNumber;
+    desc += ")";
+
+    return desc;
+}
+
+// Cross-platform helper function to deal with weird Visual Studio definitions...
+void TouchkeyReleaseAngleMappingExtendedEditor::intToString(char *st, int value) {
+#ifdef _MSC_VER
+    _snprintf_s(st, 16, _TRUNCATE, "%d", value);
+#else
+    snprintf(st, 16, "%d", value);
+#endif
+}
+
+//[/MiscUserCode]
+
+
+//==============================================================================
+#if 0
+/*  -- Introjucer information section --
+
+    This is where the Introjucer stores the metadata that describe this GUI layout, so
+    make changes in here at your peril!
+
+BEGIN_JUCER_METADATA
+
+<JUCER_COMPONENT documentType="Component" className="TouchkeyReleaseAngleMappingExtendedEditor"
+                 componentName="" parentClasses="public MappingEditorComponent, public TextEditor::Listener"
+                 constructorParams="TouchkeyReleaseAngleMappingFactory&amp; factory"
+                 variableInitialisers="factory_(factory)" snapPixels="8" snapActive="1"
+                 snapShown="1" overlayOpacity="0.330" fixedSize="1" initialWidth="342"
+                 initialHeight="328">
+  <BACKGROUND backgroundColour="ffd2d2d2"/>
+  <LABEL name="title label" id="2346b62ce034bea2" memberName="titleLabel"
+         virtualName="" explicitFocusOrder="0" pos="8 8 424 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Release Angle Mapping (Zone N, #M)" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="1" italic="0" justification="33"/>
+  <LABEL name="preset label" id="5ef7c1b78fdcf616" memberName="presetLabel"
+         virtualName="" explicitFocusOrder="0" pos="8 40 56 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Preset:" editableSingleClick="0" editableDoubleClick="0"
+         focusDiscardsChanges="0" fontname="Default font" fontsize="15"
+         bold="0" italic="0" justification="33"/>
+  <COMBOBOX name="parameter combo box" id="f12f6f6e31042be1" memberName="presetComboBox"
+            virtualName="" explicitFocusOrder="0" pos="64 40 264 24" editable="0"
+            layout="33" items="" textWhenNonSelected="" textWhenNoItems="(no choices)"/>
+  <LABEL name="preset label" id="6c7fb9cdad1ef615" memberName="presetLabel2"
+         virtualName="" explicitFocusOrder="0" pos="8 80 112 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Window Length:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="33"/>
+  <TEXTEDITOR name="range low text editor" id="db0f62c03a58af03" memberName="windowLengthEditor"
+              virtualName="" explicitFocusOrder="0" pos="120 80 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="preset label" id="bf72fc0576275cec" memberName="presetLabel3"
+         virtualName="" explicitFocusOrder="0" pos="176 80 136 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="ms. before release" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="33"/>
+  <LABEL name="preset label" id="737ac0c84cd35f2a" memberName="presetLabel4"
+         virtualName="" explicitFocusOrder="0" pos="8 112 136 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Release Moving Up" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="1" italic="0" justification="33"/>
+  <TEXTEDITOR name="up release speed editor" id="6d12507cf2b05308" memberName="upMinSpeedEditor"
+              virtualName="" explicitFocusOrder="0" pos="144 136 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="preset label" id="4bf2ba2e74c7f918" memberName="presetLabel5"
+         virtualName="" explicitFocusOrder="0" pos="8 136 136 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Min. release speed:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="34"/>
+  <TEXTEDITOR name="up note 1 editor" id="48bc9cb4bc2b66d7" memberName="upNote1Editor"
+              virtualName="" explicitFocusOrder="0" pos="144 160 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="preset label" id="e98f51307ccd3213" memberName="presetLabel6"
+         virtualName="" explicitFocusOrder="0" pos="8 160 136 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Send notes:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="34"/>
+  <TEXTEDITOR name="up note 2 editor" id="a1078ce66401fa26" memberName="upNote2Editor"
+              virtualName="" explicitFocusOrder="0" pos="208 160 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <TEXTEDITOR name="up note 3 editor" id="3b6be211e2c57644" memberName="upNote3Editor"
+              virtualName="" explicitFocusOrder="0" pos="272 160 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="preset label" id="cd890cf41d7607bd" memberName="presetLabel7"
+         virtualName="" explicitFocusOrder="0" pos="8 184 136 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="With velocities:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="34"/>
+  <TEXTEDITOR name="up velocity 1 editor" id="47d3d530ed72615a" memberName="upVelocity1Editor"
+              virtualName="" explicitFocusOrder="0" pos="144 184 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <TEXTEDITOR name="up velocity 2 editor" id="ff6d7b8e5b4a5bd6" memberName="upVelocity2Editor"
+              virtualName="" explicitFocusOrder="0" pos="208 184 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <TEXTEDITOR name="up velocity 3 editor" id="65dabffb4ac41d8d" memberName="upVelocity3Editor"
+              virtualName="" explicitFocusOrder="0" pos="272 184 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="preset label" id="f8444692065e742a" memberName="presetLabel8"
+         virtualName="" explicitFocusOrder="0" pos="8 216 160 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Release Moving Down" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="1" italic="0" justification="33"/>
+  <TEXTEDITOR name="down release speed editor" id="4d87b35c1bd38cfd" memberName="downMinSpeedEditor"
+              virtualName="" explicitFocusOrder="0" pos="144 240 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="preset label" id="cfa916d2ffe4090d" memberName="presetLabel9"
+         virtualName="" explicitFocusOrder="0" pos="8 240 136 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Min. release speed:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="34"/>
+  <TEXTEDITOR name="down note 1 editor" id="fe8e24594d26ec2d" memberName="downNote1Editor"
+              virtualName="" explicitFocusOrder="0" pos="144 264 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="preset label" id="56bc59cd5ceb0e2" memberName="presetLabel10"
+         virtualName="" explicitFocusOrder="0" pos="8 264 136 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="Send notes:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="34"/>
+  <TEXTEDITOR name="down note 2 editor" id="e0088e60d93fd53" memberName="downNote2Editor"
+              virtualName="" explicitFocusOrder="0" pos="208 264 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <TEXTEDITOR name="down note 3 editor" id="23571b41edd72631" memberName="downNote3Editor"
+              virtualName="" explicitFocusOrder="0" pos="272 264 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <LABEL name="preset label" id="a62bc5524128106c" memberName="presetLabel11"
+         virtualName="" explicitFocusOrder="0" pos="8 288 136 24" edTextCol="ff000000"
+         edBkgCol="0" labelText="With velocities:" editableSingleClick="0"
+         editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
+         fontsize="15" bold="0" italic="0" justification="34"/>
+  <TEXTEDITOR name="down velocity 1 editor" id="f4a94019655ea3ab" memberName="downVelocity1Editor"
+              virtualName="" explicitFocusOrder="0" pos="144 288 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <TEXTEDITOR name="down velocity 2 editor" id="b2c75ef9c586a2e2" memberName="downVelocity2Editor"
+              virtualName="" explicitFocusOrder="0" pos="208 288 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <TEXTEDITOR name="down velocity 3 editor" id="96bf0ea9075d357f" memberName="downVelocity3Editor"
+              virtualName="" explicitFocusOrder="0" pos="272 288 56 24" initialText=""
+              multiline="0" retKeyStartsLine="0" readonly="0" scrollbars="1"
+              caret="1" popupmenu="1"/>
+  <TOGGLEBUTTON name="up enable button" id="d3cb9267f9296315" memberName="upEnableButton"
+                virtualName="" explicitFocusOrder="0" pos="208 112 72 24" buttonText="Enable"
+                connectedEdges="0" needsCallback="1" radioGroupId="0" state="0"/>
+  <TOGGLEBUTTON name="down enable button" id="59af46c3b5431919" memberName="downEnableButton"
+                virtualName="" explicitFocusOrder="0" pos="208 216 72 24" buttonText="Enable"
+                connectedEdges="0" needsCallback="1" radioGroupId="0" state="0"/>
+</JUCER_COMPONENT>
+
+END_JUCER_METADATA
+*/
+#endif
+
+
+//[EndFile] You can add extra defines here...
+//[/EndFile]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingExtendedEditor.h	Wed Sep 24 00:29:18 2014 +0100
@@ -0,0 +1,115 @@
+/*
+  ==============================================================================
+
+  This is an automatically generated GUI class created by the Introjucer!
+
+  Be careful when adding custom code to these files, as only the code within
+  the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
+  and re-saved.
+
+  Created with Introjucer version: 3.1.0
+
+  ------------------------------------------------------------------------------
+
+  The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions"
+  Copyright 2004-13 by Raw Material Software Ltd.
+
+  ==============================================================================
+*/
+
+#ifndef __JUCE_HEADER_24D73157539FBFD0__
+#define __JUCE_HEADER_24D73157539FBFD0__
+
+//[Headers]     -- You can add your own extra header files here --
+#include "JuceHeader.h"
+#include "TouchkeyReleaseAngleMappingFactory.h"
+//[/Headers]
+
+
+
+//==============================================================================
+/**
+                                                                    //[Comments]
+    An auto-generated component, created by the Introjucer.
+
+    Describe your class and how it works here!
+                                                                    //[/Comments]
+*/
+class TouchkeyReleaseAngleMappingExtendedEditor  : public MappingEditorComponent,
+                                                   public TextEditor::Listener,
+                                                   public ComboBoxListener,
+                                                   public ButtonListener
+{
+public:
+    //==============================================================================
+    TouchkeyReleaseAngleMappingExtendedEditor (TouchkeyReleaseAngleMappingFactory& factory);
+    ~TouchkeyReleaseAngleMappingExtendedEditor();
+
+    //==============================================================================
+    //[UserMethods]     -- You can add your own custom methods in this section.
+    void textEditorTextChanged(TextEditor &editor) {}
+    void textEditorReturnKeyPressed(TextEditor &editor);
+    void textEditorEscapeKeyPressed(TextEditor &editor);
+    void textEditorFocusLost(TextEditor &editor);
+
+    void synchronize();
+    String getDescription();
+    //[/UserMethods]
+
+    void paint (Graphics& g);
+    void resized();
+    void comboBoxChanged (ComboBox* comboBoxThatHasChanged);
+    void buttonClicked (Button* buttonThatWasClicked);
+
+
+
+private:
+    //[UserVariables]   -- You can add your own custom variables in this section.
+    void intToString(char *st, int value);
+    
+    String getDescriptionHelper(String baseName);
+
+    TouchkeyReleaseAngleMappingFactory& factory_;
+    //[/UserVariables]
+
+    //==============================================================================
+    ScopedPointer<Label> titleLabel;
+    ScopedPointer<Label> presetLabel;
+    ScopedPointer<ComboBox> presetComboBox;
+    ScopedPointer<Label> presetLabel2;
+    ScopedPointer<TextEditor> windowLengthEditor;
+    ScopedPointer<Label> presetLabel3;
+    ScopedPointer<Label> presetLabel4;
+    ScopedPointer<TextEditor> upMinSpeedEditor;
+    ScopedPointer<Label> presetLabel5;
+    ScopedPointer<TextEditor> upNote1Editor;
+    ScopedPointer<Label> presetLabel6;
+    ScopedPointer<TextEditor> upNote2Editor;
+    ScopedPointer<TextEditor> upNote3Editor;
+    ScopedPointer<Label> presetLabel7;
+    ScopedPointer<TextEditor> upVelocity1Editor;
+    ScopedPointer<TextEditor> upVelocity2Editor;
+    ScopedPointer<TextEditor> upVelocity3Editor;
+    ScopedPointer<Label> presetLabel8;
+    ScopedPointer<TextEditor> downMinSpeedEditor;
+    ScopedPointer<Label> presetLabel9;
+    ScopedPointer<TextEditor> downNote1Editor;
+    ScopedPointer<Label> presetLabel10;
+    ScopedPointer<TextEditor> downNote2Editor;
+    ScopedPointer<TextEditor> downNote3Editor;
+    ScopedPointer<Label> presetLabel11;
+    ScopedPointer<TextEditor> downVelocity1Editor;
+    ScopedPointer<TextEditor> downVelocity2Editor;
+    ScopedPointer<TextEditor> downVelocity3Editor;
+    ScopedPointer<ToggleButton> upEnableButton;
+    ScopedPointer<ToggleButton> downEnableButton;
+
+
+    //==============================================================================
+    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TouchkeyReleaseAngleMappingExtendedEditor)
+};
+
+//[EndFile] You can add extra defines here...
+//[/EndFile]
+
+#endif   // __JUCE_HEADER_24D73157539FBFD0__
--- a/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.cpp	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.cpp	Wed Sep 24 00:29:18 2014 +0100
@@ -23,13 +23,180 @@
 */
 
 #include "TouchkeyReleaseAngleMappingFactory.h"
+#include "TouchkeyReleaseAngleMappingExtendedEditor.h"
 
 // Class constants
 const timestamp_diff_type TouchkeyReleaseAngleMappingFactory::kDefaultMaxLookbackTime = milliseconds_to_timestamp(100);
-
+const int TouchkeyReleaseAngleMappingFactory::kNumConfigurations = 2;
+const std::string TouchkeyReleaseAngleMappingFactory::kConfigurationNames[] = {
+    "Sample Modeling Trombone",
+    "Sample Modeling Trumpet"
+};
 
 TouchkeyReleaseAngleMappingFactory::TouchkeyReleaseAngleMappingFactory(PianoKeyboard &keyboard, MidiKeyboardSegment& segment)
-: TouchkeyBaseMappingFactory<TouchkeyReleaseAngleMapping>(keyboard, segment) {}
+: TouchkeyBaseMappingFactory<TouchkeyReleaseAngleMapping>(keyboard, segment),
+currentConfiguration_(-1),
+upEnabled_(true), downEnabled_(true),
+upMinimumAngle_(TouchkeyReleaseAngleMapping::kDefaultUpMinimumAngle),
+downMinimumAngle_(TouchkeyReleaseAngleMapping::kDefaultDownMinimumAngle)
+{
+    // Get default values from the first configuration
+    setCurrentConfiguration(0);
+}
+
+// Set a particular configuration; make sure the values here match the
+// defined names and indices above
+void TouchkeyReleaseAngleMappingFactory::setCurrentConfiguration(int index) {
+    if(index == 0) {
+        // Sample Modeling Trombone
+        currentConfiguration_ = 0;
+        upEnabled_ = downEnabled_ = true;
+        upMinimumAngle_ = 1.0;
+        downMinimumAngle_ = 1.5;
+        windowSizeMilliseconds_ = 100.0;
+        
+        clearNotes();
+        
+        upNotes_[0] = 36;
+        upVelocities_[0] = 64;
+        upNotes_[1] = 31;
+        upVelocities_[1] = 96;
+        
+        downNotes_[0] = 36;
+        downVelocities_[0] = 64;
+        downNotes_[1] = 33;
+        downVelocities_[1] = 80;
+    }
+    else if(index == 1) {
+        // Sample Modeling Trumpet
+        currentConfiguration_ = 1;
+        upEnabled_ = downEnabled_ = true;
+        upMinimumAngle_ = 1.0;
+        downMinimumAngle_ = 1.5;
+        windowSizeMilliseconds_ = 100.0;
+        
+        clearNotes();
+
+        upNotes_[0] = 48;
+        upVelocities_[0] = 64;
+        upNotes_[1] = 42;
+        upVelocities_[1] = 96;
+        
+        downNotes_[0] = 48;
+        downVelocities_[0] = 64;
+        downNotes_[1] = 46;
+        downVelocities_[1] = 96;
+    }
+}
+
+// Parameters for release angle algorithm
+int TouchkeyReleaseAngleMappingFactory::getUpNote(int sequence) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return 0;
+    return upNotes_[sequence];
+}
+
+int TouchkeyReleaseAngleMappingFactory::getUpVelocity(int sequence) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return 0;
+    return upVelocities_[sequence];
+}
+
+int TouchkeyReleaseAngleMappingFactory::getDownNote(int sequence) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return 0;
+    return downNotes_[sequence];
+}
+
+int TouchkeyReleaseAngleMappingFactory::getDownVelocity(int sequence) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return 0;
+    return downVelocities_[sequence];
+}
+
+void TouchkeyReleaseAngleMappingFactory::setWindowSize(float windowSize) {
+    if(windowSizeMilliseconds_ != windowSize)
+        currentConfiguration_ = -1;
+    windowSizeMilliseconds_ = windowSize;
+}
+
+void TouchkeyReleaseAngleMappingFactory::setUpMessagesEnabled(bool enable) {
+    if(upEnabled_ != enable)
+        currentConfiguration_ = -1;
+    upEnabled_ = enable;
+}
+
+void TouchkeyReleaseAngleMappingFactory::setDownMessagesEnabled(bool enable) {
+    if(downEnabled_ != enable)
+        currentConfiguration_ = -1;
+    downEnabled_ = enable;
+}
+
+void TouchkeyReleaseAngleMappingFactory::setUpMinimumAngle(float minAngle) {
+    if(upMinimumAngle_ != minAngle)
+        currentConfiguration_ = -1;
+    upMinimumAngle_ = fabsf(minAngle);
+}
+
+void TouchkeyReleaseAngleMappingFactory::setUpNote(int sequence, int note) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return;
+    if(note < 0 || note > 127)
+        upNotes_[sequence] = 0;
+    else {
+        if(upNotes_[sequence] != note)
+            currentConfiguration_ = -1;
+        upNotes_[sequence] = note;
+    }
+}
+
+void TouchkeyReleaseAngleMappingFactory::setUpVelocity(int sequence, int velocity) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return;
+    if(velocity < 0 || velocity > 127)
+        upVelocities_[sequence] = 0;
+    else {
+        if(upVelocities_[sequence] != velocity)
+            currentConfiguration_ = -1;
+        upVelocities_[sequence] = velocity;
+    }
+}
+
+void TouchkeyReleaseAngleMappingFactory::setDownMinimumAngle(float minAngle) {
+    if(downMinimumAngle_ != minAngle)
+        currentConfiguration_ = -1;
+    downMinimumAngle_ = fabsf(minAngle);
+}
+
+void TouchkeyReleaseAngleMappingFactory::setDownNote(int sequence, int note) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return;
+    if(note < 0 || note > 127)
+        downNotes_[sequence] = 0;
+    else {
+        if(downNotes_[sequence] != note)
+            currentConfiguration_ = -1;
+        downNotes_[sequence] = note;
+    }
+}
+
+void TouchkeyReleaseAngleMappingFactory::setDownVelocity(int sequence, int velocity) {
+    if(sequence < 0 || sequence >= RELEASE_ANGLE_MAX_SEQUENCE_LENGTH)
+        return;
+    if(velocity < 0 || velocity > 127)
+        downVelocities_[sequence] = 0;
+    else {
+        if(downVelocities_[sequence] != velocity)
+            currentConfiguration_ = -1;
+        downVelocities_[sequence] = velocity;
+    }
+}
+
+// ***** GUI Support *****
+
+MappingEditorComponent* TouchkeyReleaseAngleMappingFactory::createExtendedEditor() {
+    return new TouchkeyReleaseAngleMappingExtendedEditor(*this);
+}
 
 // ****** Preset Save/Load ******
 XmlElement* TouchkeyReleaseAngleMappingFactory::getPreset() {
@@ -37,7 +204,14 @@
     
     storeCommonProperties(properties);
     
-    // No properties for now
+    properties.setValue("currentConfiguration", currentConfiguration_);
+    properties.setValue("upEnabled", upEnabled_);
+    properties.setValue("downEnabled", downEnabled_);
+    properties.setValue("upMinimumAngle", upMinimumAngle_);
+    properties.setValue("downMinimumAngle", downMinimumAngle_);
+    properties.setValue("windowSizeMilliseconds", windowSizeMilliseconds_);
+
+    // TODO: set arrays of notes and velocities
     
     XmlElement* preset = properties.createXml("MappingFactory");
     preset->setAttribute("type", "ReleaseAngle");
@@ -55,13 +229,37 @@
     if(!loadCommonProperties(properties))
         return false;
     
-    // Nothing specific to do for now
+    // First check if there's a default configuration in use
+    // We can get all other parameters from that regardless of the
+    // remaining contents
+    if(properties.containsKey("currentConfiguration")) {
+        int config = properties.getIntValue("currentConfiguration");
+        if(config >= 0 && config < kNumConfigurations)
+            setCurrentConfiguration(config);
+    }
+    else {
+        if(!properties.containsKey("upEnabled") ||
+           !properties.containsKey("downEnabled") ||
+           !properties.containsKey("upMinimumAngle") ||
+           !properties.containsKey("downMinimumAngle") ||
+           !properties.containsKey("windowSizeMilliseconds"))
+            return false;
+        
+        currentConfiguration_ = -1;
+        upEnabled_ = properties.getBoolValue("upEnabled");
+        downEnabled_ = properties.getBoolValue("downEnabled");
+        upMinimumAngle_ = properties.getDoubleValue("upMinimumAngle");
+        downMinimumAngle_ = properties.getDoubleValue("downMinimumAngle");
+        windowSizeMilliseconds_ = properties.getDoubleValue("windowSizeMilliseconds");
+
+        // TODO: load arrays of notes and velocities
+    }
     
     return true;
 }
 
 // MIDI note ended: see whether the mapping was suspended and if not, execute the angle calculation
-void TouchkeyReleaseAngleMappingFactory::midiNoteOff(int noteNumber, bool touchIsOn, bool keyMotionActive,
+/*void TouchkeyReleaseAngleMappingFactory::midiNoteOff(int noteNumber, bool touchIsOn, bool keyMotionActive,
                                                      Node<KeyTouchFrame>* touchBuffer,
                                                      Node<key_position>* positionBuffer,
                                                      KeyPositionTracker* positionTracker) {
@@ -71,4 +269,26 @@
     
     // Call base class method
     TouchkeyBaseMappingFactory<TouchkeyReleaseAngleMapping>::midiNoteOff(noteNumber, touchIsOn, keyMotionActive, touchBuffer, positionBuffer, positionTracker);
+}*/
+
+void TouchkeyReleaseAngleMappingFactory::initializeMappingParameters(int noteNumber,
+                                                                     TouchkeyReleaseAngleMapping *mapping) {
+    mapping->setWindowSize(windowSizeMilliseconds_);
+    mapping->setUpMessagesEnabled(upEnabled_);
+    mapping->setDownMessagesEnabled(downEnabled_);
+    mapping->setUpMinimumAngle(upMinimumAngle_);
+    mapping->setDownMinimumAngle(downMinimumAngle_);
+    
+    for(int i = 0; i < RELEASE_ANGLE_MAX_SEQUENCE_LENGTH; i++) {
+        mapping->setUpNote(i, upNotes_[i]);
+        mapping->setUpVelocity(i, upVelocities_[i]);
+        mapping->setDownNote(i, downNotes_[i]);
+        mapping->setDownVelocity(i, downVelocities_[i]);
+    }
 }
+
+// Reset notes and velocities to defaults
+void TouchkeyReleaseAngleMappingFactory::clearNotes() {
+    for(int i = 0; i < RELEASE_ANGLE_MAX_SEQUENCE_LENGTH; i++)
+        upNotes_[i] = downNotes_[i] = upVelocities_[i] = downVelocities_[i] = 0;
+}
--- a/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.h	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingFactory.h	Wed Sep 24 00:29:18 2014 +0100
@@ -33,6 +33,9 @@
     //constexpr static const timestamp_diff_type kDefaultMaxLookbackTime = milliseconds_to_timestamp(100);
     static const timestamp_diff_type kDefaultMaxLookbackTime;
     
+    static const int kNumConfigurations;
+    static const std::string kConfigurationNames[];
+    
 public:
     // ***** Constructor *****
     
@@ -46,6 +49,50 @@
     // ***** Accessors / Modifiers *****
     virtual const std::string factoryTypeName() { return "Release\nAngle"; }
     
+    // ***** Specific Parameter Methods *****
+    
+    float getWindowSize() { return windowSizeMilliseconds_; }
+    bool getUpMessagesEnabled() { return upEnabled_; }
+    bool getDownMessageEnabled() { return downEnabled_; }
+    float getUpMinimumAngle() { return upMinimumAngle_; }
+    int getUpNote(int sequence);
+    int getUpVelocity(int sequence);
+    float getDownMinimumAngle() { return downMinimumAngle_; }
+    int getDownNote(int sequence);
+    int getDownVelocity(int sequence);
+    
+    void setWindowSize(float windowSize);
+    void setUpMessagesEnabled(bool enable);
+    void setDownMessagesEnabled(bool enable);
+    void setUpMinimumAngle(float minAngle);
+    void setUpNote(int sequence, int note);
+    void setUpVelocity(int sequence, int velocity);
+    void setDownMinimumAngle(float minAngle);
+    void setDownNote(int sequence, int note);
+    void setDownVelocity(int sequence, int velocity);
+    
+    // Methods for loading release-angle specific preset settings (different from the
+    // global preset methdos which are for save/load of files)
+    
+    int getNumConfigurations() { return kNumConfigurations; }
+    std::string getConfigurationName(int index) {
+        if(index < 0 || index >= kNumConfigurations)
+            return "";
+        return kConfigurationNames[index];
+    }
+    
+    // Returns current configuration or -1 if not a default setting
+    int getCurrentConfiguration() {
+        return currentConfiguration_;
+    }
+    void setCurrentConfiguration(int index);
+    
+    // ***** GUI Support *****
+    bool hasBasicEditor() { return false; }
+    MappingEditorComponent* createBasicEditor() { return nullptr; }
+    bool hasExtendedEditor() { return true; }
+    MappingEditorComponent* createExtendedEditor();
+    
     // ****** Preset Save/Load ******
     XmlElement* getPreset();
     bool loadPreset(XmlElement const* preset);
@@ -53,11 +100,27 @@
     // ***** State Updaters *****
     
     // Override the MIDI note off method to process the release angle
-    void midiNoteOff(int noteNumber, bool touchIsOn, bool keyMotionActive,
+    /*void midiNoteOff(int noteNumber, bool touchIsOn, bool keyMotionActive,
                      Node<KeyTouchFrame>* touchBuffer,
                      Node<key_position>* positionBuffer,
-                     KeyPositionTracker* positionTracker);
+                     KeyPositionTracker* positionTracker);*/
     
+    //void midiNoteOffReceived(int channel);
+    
+private:
+    // ***** Private Methods *****
+    void initializeMappingParameters(int noteNumber, TouchkeyReleaseAngleMapping *mapping);
+    void clearNotes();
+    
+    int currentConfiguration_;              // What configuration we're currently in
+    float windowSizeMilliseconds_;          // How long before release to consider touch data
+    bool upEnabled_, downEnabled_;          // Whether messages are enabled for upward and downward releases
+    float upMinimumAngle_;                  // Minimum release angle for trigger for up...
+    float downMinimumAngle_;                // ...and down cases
+    int upNotes_[RELEASE_ANGLE_MAX_SEQUENCE_LENGTH];       // Notes and velocities to send on upward
+    int upVelocities_[RELEASE_ANGLE_MAX_SEQUENCE_LENGTH];  // and downward release
+    int downNotes_[RELEASE_ANGLE_MAX_SEQUENCE_LENGTH];
+    int downVelocities_[RELEASE_ANGLE_MAX_SEQUENCE_LENGTH];
 };
 
 #endif /* defined(__TouchKeys__TouchkeyReleaseAngleMappingFactory__) */
--- a/Source/TouchKeys/PianoKey.cpp	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/TouchKeys/PianoKey.cpp	Wed Sep 24 00:29:18 2014 +0100
@@ -413,9 +413,6 @@
 // Note Off message from associated MIDI keyboard.  Clear all old MIDI state.
 void PianoKey::midiNoteOff(MidiKeyboardSegment *who, timestamp_type timestamp) {
 	midiNoteIsOn_ = false;
-	midiVelocity_ = 0;
-	midiChannel_ = -1;
-	midiAftertouch_.clear();
 	midiOffTimestamp_ = timestamp;
     
     if(keyboard_.mappingFactory(who) != 0)
@@ -424,6 +421,10 @@
     
 	keyboard_.sendMessage("/midi/noteoff", "ii", noteNumber_, midiChannel_, LO_ARGS_END);
     
+    midiVelocity_ = 0;
+	midiChannel_ = -1;
+	midiAftertouch_.clear();
+    
     // Update GUI if it is available
 	if(keyboard_.gui() != 0) {
 		keyboard_.gui()->setMidiActive(noteNumber_, false);
--- a/Source/TouchKeys/PianoKeyboard.cpp	Wed Aug 27 13:18:27 2014 +0100
+++ b/Source/TouchKeys/PianoKeyboard.cpp	Wed Sep 24 00:29:18 2014 +0100
@@ -26,7 +26,7 @@
 #include "PianoKeyboard.h"
 #include "TouchkeyDevice.h"
 #include "../Mappings/Mapping.h"
-#include "MidiOutputcontroller.h"
+#include "MidiOutputController.h"
 #include "../Mappings/MappingFactory.h"
 #include "../Mappings/MappingScheduler.h"
 
@@ -311,4 +311,4 @@
 		delete (*it);
     mappingScheduler_->stop();
     delete mappingScheduler_;
-}
\ No newline at end of file
+}
--- a/TouchKeys.jucer	Wed Aug 27 13:18:27 2014 +0100
+++ b/TouchKeys.jucer	Wed Sep 24 00:29:18 2014 +0100
@@ -56,6 +56,10 @@
                 resource="0" file="Source/Mappings/Vibrato/TouchkeyVibratoMappingFactory.h"/>
         </GROUP>
         <GROUP id="{E8E7436B-8FBA-DCC3-A9A3-B7FA7AF31483}" name="ReleaseAngle">
+          <FILE id="kQ9zEZ" name="TouchkeyReleaseAngleMappingExtendedEditor.cpp"
+                compile="1" resource="0" file="Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingExtendedEditor.cpp"/>
+          <FILE id="vyYFkY" name="TouchkeyReleaseAngleMappingExtendedEditor.h"
+                compile="0" resource="0" file="Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMappingExtendedEditor.h"/>
           <FILE id="JOJw40" name="TouchkeyReleaseAngleMapping.cpp" compile="1"
                 resource="0" file="Source/Mappings/ReleaseAngle/TouchkeyReleaseAngleMapping.cpp"/>
           <FILE id="Au4RmN" name="TouchkeyReleaseAngleMapping.h" compile="0"
@@ -275,7 +279,7 @@
                extraLinkerFlags="/usr/local/lib/liblo.a" smallIcon="mbX6Jp"
                bigIcon="b3DhPc">
       <CONFIGURATIONS>
-        <CONFIGURATION name="Debug" osxSDK="default" osxCompatibility="default" osxArchitecture="64BitIntel"
+        <CONFIGURATION name="Debug" osxSDK="default" osxCompatibility="10.6 SDK" osxArchitecture="64BitIntel"
                        isDebug="1" optimisation="1" targetName="TouchKeys" headerPath="/usr/local/include"/>
         <CONFIGURATION name="Release" osxSDK="default" osxCompatibility="10.6 SDK" osxArchitecture="64BitUniversal"
                        isDebug="0" optimisation="2" targetName="TouchKeys" headerPath="/usr/local/include"/>