diff --git a/.travis.yml b/.travis.yml
index b3444f4f2..928f9e940 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,7 +12,7 @@ jobs:
branches:
only:
- master
- - development
+ #- development
before_install:
- if [ "$TRAVIS_OS_NAME" = osx ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then openssl aes-256-cbc -K $encrypted_2f5d2771e3cb_key -iv $encrypted_2f5d2771e3cb_iv -in release_script/mac_only/Certificates.p12.enc -out release_script/mac_only/Certificates.p12 -d; fi
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 345ef7351..4883d17e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+# v5.2.1
+
+### Improvements
+
+- Add ability to map channels to EMG Joystick Inputs #1156
+- Fix alignment of UI objects in popup windows and EMG settings UI #1157
+- Rename "smoothing" to "window" in EMG settings UI #1158
+- Add EMG Joystick settings to Session Settings #1159
+
# v5.2.0
### Bug Fixes
diff --git a/OpenBCI_GUI/EmgSettings.pde b/OpenBCI_GUI/EmgSettings.pde
index 6661b6c4a..e4947932f 100644
--- a/OpenBCI_GUI/EmgSettings.pde
+++ b/OpenBCI_GUI/EmgSettings.pde
@@ -21,13 +21,13 @@ class EmgSettings {
}
Gson gson = new Gson();
EmgSettingsValues tempValues = gson.fromJson(fileContents.toString(), EmgSettingsValues.class);
- if (tempValues.smoothing.length != channelCount) {
+ if (tempValues.window.length != channelCount) {
outputError("Emg Settings: Loaded EMG Settings file has different number of channels than the current board.");
return false;
}
//Explicitely copy values over to avoid reference issues
//(e.g. values = tempValues "nukes" the old values object)
- values.smoothing = tempValues.smoothing;
+ values.window = tempValues.window;
values.uvLimit = tempValues.uvLimit;
values.creepIncreasing = tempValues.creepIncreasing;
values.creepDecreasing = tempValues.creepDecreasing;
diff --git a/OpenBCI_GUI/EmgSettingsEnums.pde b/OpenBCI_GUI/EmgSettingsEnums.pde
index 819802fc4..c7d195693 100644
--- a/OpenBCI_GUI/EmgSettingsEnums.pde
+++ b/OpenBCI_GUI/EmgSettingsEnums.pde
@@ -3,7 +3,7 @@ interface EmgSettingsEnum {
public String getString();
}
-public enum EmgSmoothing implements EmgSettingsEnum
+public enum EmgWindow implements EmgSettingsEnum
{
ONE_HUNDREDTH_SECOND (0, "0.01 s", .01f),
ONE_TENTH_SECOND (1, "0.1 s", .1f),
@@ -18,7 +18,7 @@ public enum EmgSmoothing implements EmgSettingsEnum
private String name;
private float value;
- EmgSmoothing(int index, String name, float value) {
+ EmgWindow(int index, String name, float value) {
this.index = index;
this.name = name;
this.value = value;
diff --git a/OpenBCI_GUI/EmgSettingsUI.pde b/OpenBCI_GUI/EmgSettingsUI.pde
index 5577adec0..7573afc4d 100644
--- a/OpenBCI_GUI/EmgSettingsUI.pde
+++ b/OpenBCI_GUI/EmgSettingsUI.pde
@@ -1,6 +1,7 @@
////////////////////////////////////////////////////////////
-// EmgSettingsUI.pde //
+// EmgSettingsUI.pde //
// Display the Emg Settings UI as a popup //
+// Note: This window is never resized. //
// //
////////////////////////////////////////////////////////////
@@ -14,7 +15,7 @@ class EmgSettingsUI extends PApplet implements Runnable {
private ControlP5 emgCp5;
private int x, y, w, h;
private final int HEADER_HEIGHT = 55;
- private final int FOOTER_PADDING = 80;
+ private final int FOOTER_PADDING = 90;
private final int PADDING_3 = 3;
private final int PADDING_12 = 12;
private final int NUM_CONTROL_BUTTONS = 3;
@@ -29,7 +30,7 @@ class EmgSettingsUI extends PApplet implements Runnable {
private final int NUM_FOOTER_OBJECTS = 3;
private final int FOOTER_OBJECT_WIDTH = 45;
private final int FOOTER_OBJECT_HEIGHT = 26;
- private int footerObjY = 0;
+ private int footerObjY;
private int[] footerObjX = new int[NUM_FOOTER_OBJECTS];
private final color HEADER_COLOR = OPENBCI_BLUE;
@@ -42,14 +43,14 @@ class EmgSettingsUI extends PApplet implements Runnable {
public EmgSettingsValues emgSettingsValues;
private TextBox channelColumnLabel;
- private TextBox smoothLabel;
+ private TextBox windowLabel;
private TextBox uvLimitLabel;
private TextBox creepIncLabel;
private TextBox creepDecLabel;
private TextBox minDeltaUvLabel;
private TextBox lowLimitLabel;
- private ScrollableList[] smoothLists;
+ private ScrollableList[] windowLists;
private ScrollableList[] uvLimitLists;
private ScrollableList[] creepIncLists;
private ScrollableList[] creepDecLists;
@@ -118,9 +119,11 @@ class EmgSettingsUI extends PApplet implements Runnable {
scene();
// Draw header
+ pushStyle();
noStroke();
fill(HEADER_COLOR);
rect(0, 0, width, HEADER_HEIGHT);
+ popStyle();
emgSettingsValues = dataProcessing.emgSettings.values;
@@ -129,7 +132,7 @@ class EmgSettingsUI extends PApplet implements Runnable {
//Draw column labels
channelColumnLabel.draw();
- smoothLabel.draw();
+ windowLabel.draw();
uvLimitLabel.draw();
creepIncLabel.draw();
creepDecLabel.draw();
@@ -139,29 +142,12 @@ class EmgSettingsUI extends PApplet implements Runnable {
drawChannelLabels();
//Draw cp5 objects on top of everything
- emgCp5.draw();
- }
-
- private void screenResized() {
- x = 0;
- y = 0;
- w = width;
- h = height;
-
- emgCp5.setGraphics(ourApplet, 0, 0);
-
- int colWidth = (width / NUM_COLUMNS);
- int colOffset = colWidth / 2;
- int labelY = y + HEADER_HEIGHT / 2;
- channelColumnLabel.setPosition(x + colOffset, labelY);
- smoothLabel.setPosition(x + colOffset + colWidth, labelY);
- uvLimitLabel.setPosition(x + colOffset + colWidth*2, labelY);
- creepIncLabel.setPosition(x + colOffset + colWidth*3, labelY);
- creepDecLabel.setPosition(x + colOffset + colWidth*4, labelY);
- minDeltaUvLabel.setPosition(x + colOffset + colWidth*5, labelY);
- lowLimitLabel.setPosition(x + colOffset + colWidth*6, labelY);
-
- resizeDropdowns();
+ try {
+ emgCp5.draw();
+ } catch (ConcurrentModificationException e) {
+ e.printStackTrace();
+ outputError("EMG Settings UI: Unable to draw cp5 objects.");
+ }
}
private void scene() {
@@ -169,26 +155,6 @@ class EmgSettingsUI extends PApplet implements Runnable {
background(BACKGROUND_COLOR);
}
- @Override
- public void keyReleased() {
-
- }
-
- @Override
- public void keyPressed() {
-
- }
-
- @Override
- public void mousePressed() {
-
- }
-
- @Override
- public void mouseReleased() {
-
- }
-
@Override
public void exit() {
dispose();
@@ -229,7 +195,7 @@ class EmgSettingsUI extends PApplet implements Runnable {
for (int i = 0; i < channelCount; i++) {
String channelLabel = channelCount > channelLabels.length ? "Channel " + Integer.toString(i + 1) : channelLabels[i];
- text(channelLabel, x + colOffset, dropdownYPositions[i] + (DROPDOWN_HEIGHT / 2));
+ text(channelLabel, x + colOffset, dropdownYPositions[i] + (DROPDOWN_HEIGHT / 2) - 2);
}
popStyle();
@@ -237,15 +203,15 @@ class EmgSettingsUI extends PApplet implements Runnable {
private void resizeDropdowns() {
dropdownWidth = int((w - (DROPDOWN_SPACER * (NUM_COLUMNS + 1))) / NUM_COLUMNS);
- final int MAX_HEIGHT_ITEMS = channelCount == 4 ? 8 : 5;
+ final int MAX_HEIGHT_ITEMS = 6;
for (int i = 0; i < channelCount; i++) {
int dropdownX = x + DROPDOWN_SPACER * 2 + dropdownWidth;
dropdownYPositions[i] = HEADER_HEIGHT + int(y + ((ROW_HEIGHT) * i) + (((ROW_HEIGHT) - DROPDOWN_HEIGHT) / 2));
final int buttonXIncrement = DROPDOWN_SPACER + dropdownWidth;
- smoothLists[i].setPosition(dropdownX, dropdownYPositions[i]);
- smoothLists[i].setSize(dropdownWidth, MAX_HEIGHT_ITEMS * DROPDOWN_HEIGHT);
+ windowLists[i].setPosition(dropdownX, dropdownYPositions[i]);
+ windowLists[i].setSize(dropdownWidth, MAX_HEIGHT_ITEMS * DROPDOWN_HEIGHT);
dropdownX += buttonXIncrement;
uvLimitLists[i].setPosition(dropdownX, dropdownYPositions[i]);
@@ -270,7 +236,8 @@ class EmgSettingsUI extends PApplet implements Runnable {
}
private void createAllUIObjects() {
- footerObjY = y + h - FOOTER_PADDING + PADDING_3;
+ final int HALF_FOOTER_HEIGHT = (FOOTER_PADDING + (DROPDOWN_SPACER * 2)) / 2;
+ footerObjY = y + h - HALF_FOOTER_HEIGHT - (FOOTER_OBJECT_HEIGHT / 2);
int middle = x + w / 2;
int halfObjWidth = FOOTER_OBJECT_WIDTH / 2;
footerObjX[0] = middle - halfObjWidth - PADDING_12 - FOOTER_OBJECT_WIDTH;
@@ -291,13 +258,13 @@ class EmgSettingsUI extends PApplet implements Runnable {
int colWidth = (w / NUM_COLUMNS);
int colOffset = colWidth / 2;
int labelY = y + HEADER_HEIGHT / 2;
- channelColumnLabel = new TextBox("Channel", x + colOffset, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP);
- smoothLabel = new TextBox("Smooth", x + colOffset + colWidth, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP);
- uvLimitLabel = new TextBox("uV Limit", x + colOffset + colWidth*2, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP);
- creepIncLabel = new TextBox("Creep +", x + colOffset + colWidth*3, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP);
- creepDecLabel = new TextBox("Creep -", x + colOffset + colWidth*4, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP);
- minDeltaUvLabel = new TextBox("Min \u0394uV", x + colOffset + colWidth*5, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP);
- lowLimitLabel = new TextBox("Low Limit", x + colOffset + colWidth*6, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP);
+ channelColumnLabel = new TextBox("Channel", x + colOffset, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER);
+ windowLabel = new TextBox("Window", x + colOffset + colWidth, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER);
+ uvLimitLabel = new TextBox("uV Limit", x + colOffset + colWidth*2, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER);
+ creepIncLabel = new TextBox("Creep +", x + colOffset + colWidth*3, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER);
+ creepDecLabel = new TextBox("Creep -", x + colOffset + colWidth*4, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER);
+ minDeltaUvLabel = new TextBox("Min \u0394uV", x + colOffset + colWidth*5, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER);
+ lowLimitLabel = new TextBox("Low Limit", x + colOffset + colWidth*6, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER);
createAllDropdowns();
}
@@ -306,7 +273,7 @@ class EmgSettingsUI extends PApplet implements Runnable {
//the size and space of these buttons are dependendant on the size of the screen and full ChannelController
verbosePrint("EmgChannelSettingsUI: Creating EMG channel setting UI objects...");
- smoothLists = new ScrollableList[channelCount];
+ windowLists = new ScrollableList[channelCount];
uvLimitLists = new ScrollableList[channelCount];
creepIncLists = new ScrollableList[channelCount];
creepDecLists = new ScrollableList[channelCount];
@@ -318,7 +285,7 @@ class EmgSettingsUI extends PApplet implements Runnable {
//Init dropdowns in reverse so that chan 1 draws on top of chan 2, etc.
for (int i = channelCount - 1; i >= 0; i--) {
int exgChannel = i;
- smoothLists[i] = createDropdown(exgChannel, "smooth_ch_"+(i+1), emgSettingsValues.smoothing[exgChannel].values(), emgSettingsValues.smoothing[exgChannel]);
+ windowLists[i] = createDropdown(exgChannel, "smooth_ch_"+(i+1), emgSettingsValues.window[exgChannel].values(), emgSettingsValues.window[exgChannel]);
uvLimitLists[i] = createDropdown(exgChannel, "uvLimit_ch_"+(i+1), emgSettingsValues.uvLimit[exgChannel].values(), emgSettingsValues.uvLimit[exgChannel]);
creepIncLists[i] = createDropdown(exgChannel, "creep_inc_ch_"+(i+1), emgSettingsValues.creepIncreasing[exgChannel].values(), emgSettingsValues.creepIncreasing[exgChannel]);
creepDecLists[i] = createDropdown(exgChannel, "creep_dec_ch_"+(i+1), emgSettingsValues.creepDecreasing[exgChannel].values(), emgSettingsValues.creepDecreasing[exgChannel]);
@@ -386,9 +353,8 @@ class EmgSettingsUI extends PApplet implements Runnable {
EmgSettingsEnum myEnum = (EmgSettingsEnum)bob.get("value");
verbosePrint("EmgSettings: " + (theEvent.getController()).getName() + " == " + myEnum.getString());
- if (myEnum instanceof EmgSmoothing) {
- //verbosePrint("HardwareSettings: previousVal == " + emgSettingsValues.previousValues.gain[channel]);
- emgSettingsValues.smoothing[channel] = (EmgSmoothing)myEnum;
+ if (myEnum instanceof EmgWindow) {
+ emgSettingsValues.window[channel] = (EmgWindow)myEnum;
} else if (myEnum instanceof EmgUVLimit) {
emgSettingsValues.uvLimit[channel] = (EmgUVLimit)myEnum;
} else if (myEnum instanceof EmgCreepIncreasing) {
@@ -437,7 +403,7 @@ class EmgSettingsUI extends PApplet implements Runnable {
private void updateCp5Objects() {
for (int i = 0; i < channelCount; i++) {
//Fetch values from the EmgSettingsValues object
- EmgSmoothing updateSmoothing = emgSettingsValues.smoothing[i];
+ EmgWindow updateSmoothing = emgSettingsValues.window[i];
EmgUVLimit updateUVLimit = emgSettingsValues.uvLimit[i];
EmgCreepIncreasing updateCreepIncreasing = emgSettingsValues.creepIncreasing[i];
EmgCreepDecreasing updateCreepDecreasing = emgSettingsValues.creepDecreasing[i];
@@ -445,7 +411,7 @@ class EmgSettingsUI extends PApplet implements Runnable {
EmgLowerThresholdMinimum updateLowerThresholdMinimum = emgSettingsValues.lowerThresholdMinimum[i];
//Update the ScrollableLists
- smoothLists[i].getCaptionLabel().setText(updateSmoothing.getString());
+ windowLists[i].getCaptionLabel().setText(updateSmoothing.getString());
uvLimitLists[i].getCaptionLabel().setText(updateUVLimit.getString());
creepIncLists[i].getCaptionLabel().setText(updateCreepIncreasing.getString());
creepDecLists[i].getCaptionLabel().setText(updateCreepDecreasing.getString());
diff --git a/OpenBCI_GUI/EmgSettingsValues.pde b/OpenBCI_GUI/EmgSettingsValues.pde
index d892b186a..c87d634f8 100644
--- a/OpenBCI_GUI/EmgSettingsValues.pde
+++ b/OpenBCI_GUI/EmgSettingsValues.pde
@@ -8,7 +8,7 @@
class EmgSettingsValues {
//These values can be changed via dropdowns
- public EmgSmoothing[] smoothing;
+ public EmgWindow[] window;
public EmgUVLimit[] uvLimit;
public EmgCreepIncreasing[] creepIncreasing;
public EmgCreepDecreasing[] creepDecreasing;
@@ -27,7 +27,7 @@ class EmgSettingsValues {
channelCount = currentBoard.getNumEXGChannels();
- smoothing = new EmgSmoothing[channelCount];
+ window = new EmgWindow[channelCount];
uvLimit = new EmgUVLimit[channelCount];
creepIncreasing = new EmgCreepIncreasing[channelCount];
creepDecreasing = new EmgCreepDecreasing[channelCount];
@@ -39,7 +39,7 @@ class EmgSettingsValues {
lowerThreshold = new float[channelCount];
averageuV = new float[channelCount];
- Arrays.fill(smoothing, EmgSmoothing.ONE_SECOND);
+ Arrays.fill(window, EmgWindow.ONE_SECOND);
Arrays.fill(uvLimit, EmgUVLimit.TWO_HUNDRED_UV);
Arrays.fill(creepIncreasing, EmgCreepIncreasing.POINT_9);
Arrays.fill(creepDecreasing, EmgCreepDecreasing.POINT_99999);
@@ -55,7 +55,7 @@ class EmgSettingsValues {
public void process(float[][] data_forDisplay_uV) {
//looping over channels and analyzing input data
for (int i = 0; i < channelCount; i++) {
- float averagePeriod = currentBoard.getSampleRate() * smoothing[i].getValue();
+ float averagePeriod = currentBoard.getSampleRate() * window[i].getValue();
int _uvLimit = uvLimit[i].getValue();
float creepSpeedIncreasing = creepIncreasing[i].getValue();
float creepSpeedDecreasing = creepDecreasing[i].getValue();
diff --git a/OpenBCI_GUI/Info.plist.tmpl b/OpenBCI_GUI/Info.plist.tmpl
index 1b1cd08d8..5bf524091 100644
--- a/OpenBCI_GUI/Info.plist.tmpl
+++ b/OpenBCI_GUI/Info.plist.tmpl
@@ -23,7 +23,7 @@
CFBundleShortVersionString
5
CFBundleVersion
- 5.2.0
+ 5.2.1
CFBundleSignature
????
NSHumanReadableCopyright
@@ -32,7 +32,7 @@
Copyright © 2023 OpenBCI
CFBundleGetInfoString
- June 2023
+ July 2023
@@jvm_runtime@@
diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde
index f526b9c8d..8433ca6e1 100644
--- a/OpenBCI_GUI/OpenBCI_GUI.pde
+++ b/OpenBCI_GUI/OpenBCI_GUI.pde
@@ -62,8 +62,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
// Global Variables & Instances
//------------------------------------------------------------------------
//Used to check GUI version in TopNav.pde and displayed on the splash screen on startup
-String localGUIVersionString = "v5.2.0";
-String localGUIVersionDate = "June 2023";
+String localGUIVersionString = "v5.2.1";
+String localGUIVersionDate = "July 2023";
String guiLatestVersionGithubAPI = "https://api.github.com/repos/OpenBCI/OpenBCI_GUI/releases/latest";
String guiLatestReleaseLocation = "https://github.com/OpenBCI/OpenBCI_GUI/releases/latest";
Boolean guiIsUpToDate;
diff --git a/OpenBCI_GUI/PopupMessage.pde b/OpenBCI_GUI/PopupMessage.pde
index 58dbe7a0a..ad62d8dbf 100644
--- a/OpenBCI_GUI/PopupMessage.pde
+++ b/OpenBCI_GUI/PopupMessage.pde
@@ -106,7 +106,7 @@ class PopupMessage extends PApplet implements Runnable {
textFont(p0, 24);
fill(WHITE);
textAlign(LEFT, CENTER);
- text(headerMessage, (width - w)/2 + padding, (height - h)/2, w, headerHeight);
+ text(headerMessage, (width - w)/2 + padding, headerHeight/2);
//draw message
textFont(p3, 16);
diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde
index 351d92279..cbfe143e7 100644
--- a/OpenBCI_GUI/SessionSettings.pde
+++ b/OpenBCI_GUI/SessionSettings.pde
@@ -68,8 +68,8 @@ class SessionSettings {
int fftSmoothingSave;
int fftFilterSave;
//Analog Read settings
- int arVertScaleSave; //updates in VertScale_AR()
- int arHorizScaleSave; //updates in Duration_AR()
+ int arVertScaleSave;
+ int arHorizScaleSave;
//Headplot settings
int hpIntensitySave;
int hpPolaritySave;
@@ -189,6 +189,13 @@ class SessionSettings {
//Serial load variables
int nwSerialBaudRateLoad;
+ //EMG Widget
+ List loadEmgActiveChannels = new ArrayList();
+
+ //EMG Joystick Widget
+ int loadEmgJoystickSmoothing;
+ List loadEmgJoystickInputs = new ArrayList();
+
//Primary JSON objects for saving and loading data
private JSONObject saveSettingsJSONData;
private JSONObject loadSettingsJSONData;
@@ -204,6 +211,8 @@ class SessionSettings {
private final String kJSONKeyWidget = "widget";
private final String kJSONKeyVersion = "version";
private final String kJSONKeySpectrogram = "spectrogram";
+ private final String kJSONKeyEmg = "emg";
+ private final String kJSONKeyEmgJoystick = "emgJoystick";
//used only in this class to count the number of channels being used while saving/loading, this gets updated in updateToNChan whenever the number of channels being used changes
int slnchan;
@@ -453,6 +462,30 @@ class SessionSettings {
saveSpectrogramSettings.setInt("Spectrogram_LogLin", spectLogLinSave);
saveSettingsJSONData.setJSONObject(kJSONKeySpectrogram, saveSpectrogramSettings);
+ ///////////////////////////////////////////////Setup new JSON object to save EMG Settings
+ JSONObject saveEMGSettings = new JSONObject();
+
+ //Save data from the Active channel checkBoxes
+ JSONArray saveActiveChanEMG = new JSONArray();
+ int numActiveEMGChan = w_emg.emgChannelSelect.activeChan.size();
+ for (int i = 0; i < numActiveEMGChan; i++) {
+ int activeChan = w_emg.emgChannelSelect.activeChan.get(i);
+ saveActiveChanEMG.setInt(i, activeChan);
+ }
+ saveEMGSettings.setJSONArray("activeChannels", saveActiveChanEMG);
+ saveSettingsJSONData.setJSONObject(kJSONKeyEmg, saveEMGSettings);
+
+ ///////////////////////////////////////////////Setup new JSON object to save EMG Joystick Settings
+ JSONObject saveEmgJoystickSettings = new JSONObject();
+ saveEmgJoystickSettings.setInt("smoothing", w_emgJoystick.joystickSmoothing.getIndex());
+ JSONArray saveEmgJoystickInputs = new JSONArray();
+ int numEmgJoystickInputs = w_emgJoystick.emgJoystickInputs.length;
+ for (int i = 0; i < numEmgJoystickInputs; i++) {
+ saveEmgJoystickInputs.setInt(i, w_emgJoystick.emgJoystickInputs[i].getIndex());
+ }
+ saveEmgJoystickSettings.setJSONArray("joystickInputs", saveEmgJoystickInputs);
+ saveSettingsJSONData.setJSONObject(kJSONKeyEmgJoystick, saveEmgJoystickSettings);
+
///////////////////////////////////////////////Setup new JSON object to save Widgets Active in respective Containers
JSONObject saveWidgetSettings = new JSONObject();
@@ -625,6 +658,23 @@ class SessionSettings {
e.printStackTrace();
}
+ //Get EMG widget settings
+ loadEmgActiveChannels.clear();
+ JSONObject loadEmgSettings = loadSettingsJSONData.getJSONObject(kJSONKeyEmg);
+ JSONArray loadEmgChan = loadEmgSettings.getJSONArray("activeChannels");
+ for (int i = 0; i < loadEmgChan.size(); i++) {
+ loadEmgActiveChannels.add(loadEmgChan.getInt(i));
+ }
+
+ //Get EMG Joystick widget settings
+ JSONObject loadEmgJoystickSettings = loadSettingsJSONData.getJSONObject(kJSONKeyEmgJoystick);
+ loadEmgJoystickSmoothing = loadEmgJoystickSettings.getInt("smoothing");
+ loadEmgJoystickInputs.clear();
+ JSONArray loadJoystickInputsJson = loadEmgJoystickSettings.getJSONArray("joystickInputs");
+ for (int i = 0; i < loadJoystickInputsJson.size(); i++) {
+ loadEmgJoystickInputs.add(loadJoystickInputsJson.getInt(i));
+ }
+
//get the Widget/Container settings
JSONObject loadWidgetSettings = loadSettingsJSONData.getJSONObject(kJSONKeyWidget);
//Apply Layout directly before loading and applying widgets to containers
@@ -859,6 +909,30 @@ class SessionSettings {
break;
}//end switch-case for networking settings for all networking protocols
+ ////////////////////////////Apply EMG widget settings
+ try {
+ //apply channel checkbox settings
+ w_emg.emgChannelSelect.deactivateAllButtons();;
+ for (int i = 0; i < loadEmgActiveChannels.size(); i++) {
+ w_emg.emgChannelSelect.setToggleState(loadEmgActiveChannels.get(i), true);
+ }
+ } catch (Exception e) {
+ println("Settings: Exception caught applying EMG widget settings " + e);
+ }
+ verbosePrint("Settings: EMG Widget Active Channels: " + loadEmgActiveChannels);
+
+ ////////////////////////////Apply EMG Joystick settings
+ w_emgJoystick.setJoystickSmoothing(loadEmgJoystickSmoothing);
+ w_emgJoystick.cp5_widget.getController("emgJoystickSmoothingDropdown").getCaptionLabel()
+ .setText(EmgJoystickSmoothing.getEnumStringsAsList().get(loadEmgJoystickSmoothing));
+ try {
+ for (int i = 0; i < loadEmgJoystickInputs.size(); i++) {
+ w_emgJoystick.updateJoystickInput(i, loadEmgJoystickInputs.get(i));
+ }
+ } catch (Exception e) {
+ println("Settings: Exception caught applying EMG Joystick settings " + e);
+ }
+
////////////////////////////////////////////////////////////
// Apply more loaded widget settings above this line //
diff --git a/OpenBCI_GUI/W_EMGJoystick.pde b/OpenBCI_GUI/W_EMGJoystick.pde
index 527b9e53b..e825d1be1 100644
--- a/OpenBCI_GUI/W_EMGJoystick.pde
+++ b/OpenBCI_GUI/W_EMGJoystick.pde
@@ -45,17 +45,31 @@ class W_EMGJoystick extends Widget {
private float leftPolarX, leftPolarY; //9:00
private final int EMG_PLOT_OFFSET = 40; //Used to arrange EMG displays outside of X/Y graph
- private final String CHANNEL_ONE_LABEL_EN = "Channel 1";
- private final String CHANNEL_TWO_LABEL_EN = "Channel 2";
- private final String CHANNEL_THREE_LABEL_EN = "Channel 3";
- private final String CHANNEL_FOUR_LABEL_EN = "Channel 4";
+ private String[] plotChannelLabels = new String[NUM_EMG_CHANNELS];
- private String channelOneLabel;
- private String channelTwoLabel;
- private String channelThreeLabel;
- private String channelFourLabel;
+ public EmgJoystickSmoothing joystickSmoothing = EmgJoystickSmoothing.POINT_9;
- EmgJoystickSmoothing joystickSmoothing = EmgJoystickSmoothing.POINT_9;
+ private int DROPDOWN_HEIGHT = navH - 4;
+ private int DROPDOWN_WIDTH = 80;
+ private int DROPDOWN_SPACER = 10;
+ private int DROPDOWN_LABEL_WIDTH = 24;
+
+ public EmgJoystickInput[] emgJoystickInputs = new EmgJoystickInput[NUM_EMG_CHANNELS];
+
+ private ScrollableList xNegativeInputDropdown;
+ private ScrollableList xPositiveInputDropdown;
+ private ScrollableList yPositiveInputDropdown;
+ private ScrollableList yNegativeInputDropdown;
+
+ private TextBox xNegativeInputDropdownLabel;
+ private TextBox xPositiveInputDropdownLabel;
+ private TextBox yPositiveInputDropdownLabel;
+ private TextBox yNegativeInputDropdownLabel;
+
+ private PImage xNegativeInputLabelImage = loadImage("LEFT_100x100.png");
+ private PImage xPositiveInputLabelImage = loadImage("RIGHT_100x100.png");
+ private PImage yPositiveInputLabelImage = loadImage("UP_100x100.png");
+ private PImage yNegativeInputLabelImage = loadImage("DOWN_100x100.png");
W_EMGJoystick(PApplet _parent){
super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE)
@@ -71,13 +85,18 @@ class W_EMGJoystick extends Widget {
emgSettingsValues = dataProcessing.emgSettings.values;
- channelOneLabel = CHANNEL_ONE_LABEL_EN;
- channelTwoLabel = CHANNEL_TWO_LABEL_EN;
- channelThreeLabel = CHANNEL_THREE_LABEL_EN;
- channelFourLabel = CHANNEL_FOUR_LABEL_EN;
+ emgJoystickInputs[0] = EmgJoystickInput.CHANNEL_1;
+ emgJoystickInputs[1] = EmgJoystickInput.CHANNEL_2;
+ emgJoystickInputs[2] = EmgJoystickInput.CHANNEL_3;
+ emgJoystickInputs[3] = EmgJoystickInput.CHANNEL_4;
+
+ for (int i = 0; i < NUM_EMG_CHANNELS; i++) {
+ plotChannelLabels[i] = Integer.toString(emgJoystickInputs[i].getIndex() + 1);
+ }
addDropdown("emgJoystickSmoothingDropdown", "Smoothing", joystickSmoothing.getEnumStringsAsList(), joystickSmoothing.getIndex());
+ createInputDropdowns();
}
public void update(){
@@ -91,12 +110,12 @@ class W_EMGJoystick extends Widget {
drawJoystickXYGraph();
- drawEmgVisualization(0, leftPolarX, leftPolarY);
- drawEmgVisualization(1, rightPolarX, rightPolarY);
- drawEmgVisualization(2, topPolarX, topPolarY);
- drawEmgVisualization(3, bottomPolarX, bottomPolarY);
-
- drawChannelLabels();
+ drawEmgVisualization(emgJoystickInputs[0].getIndex(), leftPolarX, leftPolarY);
+ drawEmgVisualization(emgJoystickInputs[1].getIndex(), rightPolarX, rightPolarY);
+ drawEmgVisualization(emgJoystickInputs[2].getIndex(), topPolarX, topPolarY);
+ drawEmgVisualization(emgJoystickInputs[3].getIndex(), bottomPolarX, bottomPolarY);
+
+ drawInputDropdownLabels();
emgCp5.draw();
}
@@ -108,6 +127,7 @@ class W_EMGJoystick extends Widget {
emgSettingsButton.setPosition(x0 + 1, y0 + navH + 1);
updateJoystickGraphSizeAndPosition();
+ updateInputDropdownPositions();
}
private void updateJoystickGraphSizeAndPosition() {
@@ -183,7 +203,8 @@ class W_EMGJoystick extends Widget {
popStyle();
}
-
+
+ //This is the core method that updates the joystick input
private void updateJoystickInput() {
previousJoystickRawX = joystickRawX;
previousJoystickRawY = joystickRawY;
@@ -193,11 +214,16 @@ class W_EMGJoystick extends Widget {
joystickRawY = 0;
return;
}
+
+ float xNegativeValue = emgSettingsValues.outputNormalized[emgJoystickInputs[0].getIndex()];
+ float xPositiveValue = emgSettingsValues.outputNormalized[emgJoystickInputs[1].getIndex()];
+ float yPositiveValue = emgSettingsValues.outputNormalized[emgJoystickInputs[2].getIndex()];
+ float yNegativeValue = emgSettingsValues.outputNormalized[emgJoystickInputs[3].getIndex()];
- //Here we subtract the values of the left and right channels to get the X axis
- joystickRawX = emgSettingsValues.outputNormalized[1] - emgSettingsValues.outputNormalized[0];
- //Here we subtract the values of the top and bottom channels to get the Y axis
- joystickRawY = emgSettingsValues.outputNormalized[2] - emgSettingsValues.outputNormalized[3];
+ //Here we subtract the value of the right channel from the left channel to get the X axis
+ joystickRawX = xPositiveValue - xNegativeValue;
+ //Here we subtract the value of the top channel from the bottom channel to get the Y axis
+ joystickRawY = yPositiveValue - yNegativeValue;
//Map the joystick values to a unit circle
float[] unitCircleXY = mapToUnitCircle(joystickRawX, joystickRawY);
@@ -266,17 +292,6 @@ class W_EMGJoystick extends Widget {
noFill();
rect(barX, barY, BAR_WIDTH, BAR_HEIGHT * -1);
- /*
- //draw channel number at upper left corner of row/column cell
- pushStyle();
- stroke(OPENBCI_DARKBLUE);
- fill(OPENBCI_DARKBLUE);
- int _chan = index+1;
- textFont(p5, 12);
- text(_chan + "", 10, 20);
- popStyle();
- */
-
popStyle();
}
@@ -287,11 +302,11 @@ class W_EMGJoystick extends Widget {
textFont(p4, 14);
textLeading(14);
textAlign(CENTER,CENTER);
-
- text(channelOneLabel, leftPolarX, leftPolarY - BAR_CIRCLE_SPACER * 2);
- text(channelTwoLabel, rightPolarX, rightPolarY - BAR_CIRCLE_SPACER *2);
- text(channelThreeLabel, topPolarX + BAR_CIRCLE_SPACER * 4, topPolarY);
- text(channelFourLabel, bottomPolarX + BAR_CIRCLE_SPACER * 4, bottomPolarY);
+
+ text(plotChannelLabels[0], leftPolarX, leftPolarY - BAR_CIRCLE_SPACER * 2);
+ text(plotChannelLabels[1], rightPolarX, rightPolarY - BAR_CIRCLE_SPACER *2);
+ text(plotChannelLabels[2], topPolarX + BAR_CIRCLE_SPACER * 4, topPolarY);
+ text(plotChannelLabels[3], bottomPolarX + BAR_CIRCLE_SPACER * 4, bottomPolarY);
popStyle();
}
@@ -314,6 +329,134 @@ class W_EMGJoystick extends Widget {
emgSettingsButton.setDescription("Click to open the EMG Settings UI to adjust how this metric is calculated.");
}
+ private ScrollableList createEmgJoystickInputDropdown(String name, EmgJoystickInput joystickInput, int inputNumber) {
+ ScrollableList list = emgCp5.addScrollableList(name)
+ .setOpen(false)
+ .setColorBackground(WHITE) // text field bg color
+ .setColorValueLabel(OPENBCI_DARKBLUE) // text color
+ .setColorCaptionLabel(OPENBCI_DARKBLUE)
+ .setColorForeground(color(125)) // border color when not selected
+ .setColorActive(BUTTON_PRESSED) // border color when selected
+ .setOutlineColor(OBJECT_BORDER_GREY)
+ .setSize(DROPDOWN_WIDTH, DROPDOWN_HEIGHT * 6)//temporary size
+ .setBarHeight(DROPDOWN_HEIGHT) //height of top/primary bar
+ .setItemHeight(DROPDOWN_HEIGHT) //height of all item/dropdown bars
+ .setVisible(true)
+ ;
+ // this will store the *actual* enum object inside the dropdown!
+ for (EmgJoystickInput input : EmgJoystickInput.values()) {
+ if (input.getIndex() >= currentBoard.getNumEXGChannels()) {
+ continue;
+ }
+ list.addItem(input.getString(), input);
+ }
+ //Style the text in the ScrollableList
+ list.getCaptionLabel() //the caption label is the text object in the primary bar
+ .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!!
+ .setText(joystickInput.getString())
+ .setFont(h5)
+ .setSize(12)
+ .getStyle() //need to grab style before affecting the paddingTop
+ .setPaddingTop(4)
+ ;
+ list.getValueLabel() //the value label is connected to the text objects in the dropdown item bars
+ .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!!
+ .setText(joystickInput.getString())
+ .setFont(p6)
+ .setSize(10) //set the font size of the item bars to 14pt
+ .getStyle() //need to grab style before affecting the paddingTop
+ .setPaddingTop(3) //4-pixel vertical offset to center text
+ ;
+ list.addCallback(new SLCallbackListener(inputNumber));
+ return list;
+ }
+
+ private class SLCallbackListener implements CallbackListener {
+ private int inputNumber;
+
+ SLCallbackListener(int _i) {
+ inputNumber = _i;
+ }
+ public void controlEvent(CallbackEvent theEvent) {
+ //Selecting an item from ScrollableList triggers Broadcast
+ if (theEvent.getAction() == ControlP5.ACTION_BROADCAST) {
+ int val = (int)(theEvent.getController()).getValue();
+ Map bob = ((ScrollableList)theEvent.getController()).getItem(val);
+ emgJoystickInputs[inputNumber] = (EmgJoystickInput)bob.get("value");
+ verbosePrint("EmgJoystickInput: " + (theEvent.getController()).getName() + " == " + emgJoystickInputs[inputNumber].getString());
+
+ plotChannelLabels[inputNumber] = Integer.toString(emgJoystickInputs[inputNumber].getIndex() + 1);
+ }
+ }
+ }
+
+ private void createInputDropdowns() {
+ //Create the dropdowns in reverse order so that top dropdown draws over bottom dropdown
+ yNegativeInputDropdown = createEmgJoystickInputDropdown("yNegativeDropdown", emgJoystickInputs[3], 3);
+ yPositiveInputDropdown = createEmgJoystickInputDropdown("yPositiveDropdown", emgJoystickInputs[2], 2);
+ xPositiveInputDropdown = createEmgJoystickInputDropdown("xPositiveDropdown", emgJoystickInputs[1], 1);
+ xNegativeInputDropdown = createEmgJoystickInputDropdown("xNegativeDropdown", emgJoystickInputs[0], 0);
+ //Add the dropdowns to the list of cp5 elements to check for mouseover
+ cp5ElementsToCheck.add(xNegativeInputDropdown);
+ cp5ElementsToCheck.add(xPositiveInputDropdown);
+ cp5ElementsToCheck.add(yPositiveInputDropdown);
+ cp5ElementsToCheck.add(yNegativeInputDropdown);
+ //Create labels for the dropdowns
+ color labelBG = color(255,255,255,0);
+ xNegativeInputDropdownLabel = new TextBox("X-", x, y, OPENBCI_DARKBLUE, WHITE, 12, h3, LEFT, TOP);
+ xPositiveInputDropdownLabel = new TextBox("X+", x, y, OPENBCI_DARKBLUE, WHITE, 12, h3, LEFT, TOP);
+ yPositiveInputDropdownLabel = new TextBox("Y+", x, y, OPENBCI_DARKBLUE, WHITE, 12, h3, LEFT, TOP);
+ yNegativeInputDropdownLabel = new TextBox("Y-", x, y, OPENBCI_DARKBLUE, WHITE, 12, h3, LEFT, TOP);
+ }
+
+ private void updateInputDropdownPositions(){
+ final int Y_AXIS_ARROW_LABEL_WIDTH = DROPDOWN_HEIGHT + DROPDOWN_SPACER;
+ xNegativeInputDropdown.setPosition((int) (x + navH + DROPDOWN_LABEL_WIDTH), (int) (y + navH + 1));
+ xPositiveInputDropdown.setPosition((int) (x + navH + DROPDOWN_LABEL_WIDTH), (int) (y + navH + DROPDOWN_SPACER + DROPDOWN_HEIGHT));
+ yPositiveInputDropdown.setPosition((int) (x + w - navH - DROPDOWN_WIDTH - Y_AXIS_ARROW_LABEL_WIDTH), (int) (y + navH + 1));
+ yNegativeInputDropdown.setPosition((int) (x + w - navH - DROPDOWN_WIDTH - Y_AXIS_ARROW_LABEL_WIDTH), (int) (y + navH + DROPDOWN_SPACER + DROPDOWN_HEIGHT));
+ xNegativeInputDropdownLabel.setPosition((int) xNegativeInputDropdown.getPosition()[0] - DROPDOWN_LABEL_WIDTH, (int) xNegativeInputDropdown.getPosition()[1]);
+ xPositiveInputDropdownLabel.setPosition((int) xPositiveInputDropdown.getPosition()[0] - DROPDOWN_LABEL_WIDTH, (int) xPositiveInputDropdown.getPosition()[1]);
+ yPositiveInputDropdownLabel.setPosition((int) yPositiveInputDropdown.getPosition()[0] - DROPDOWN_LABEL_WIDTH, (int) yPositiveInputDropdown.getPosition()[1]);
+ yNegativeInputDropdownLabel.setPosition((int) yNegativeInputDropdown.getPosition()[0] - DROPDOWN_LABEL_WIDTH, (int) yNegativeInputDropdown.getPosition()[1]);
+ }
+
+ private void drawInputDropdownLabels() {
+ xNegativeInputDropdownLabel.draw();
+ xPositiveInputDropdownLabel.draw();
+ yPositiveInputDropdownLabel.draw();
+ yNegativeInputDropdownLabel.draw();
+
+ pushStyle();
+ final int X_OFFSET = DROPDOWN_WIDTH + DROPDOWN_SPACER;
+ image(xNegativeInputLabelImage, xNegativeInputDropdown.getPosition()[0] + X_OFFSET, xNegativeInputDropdown.getPosition()[1] + 2, DROPDOWN_HEIGHT, DROPDOWN_HEIGHT);
+ image(xPositiveInputLabelImage, xPositiveInputDropdown.getPosition()[0] + X_OFFSET, xPositiveInputDropdown.getPosition()[1] + 2, DROPDOWN_HEIGHT, DROPDOWN_HEIGHT);
+ image(yPositiveInputLabelImage, yPositiveInputDropdown.getPosition()[0] + X_OFFSET, yPositiveInputDropdown.getPosition()[1] + 2, DROPDOWN_HEIGHT, DROPDOWN_HEIGHT);
+ image(yNegativeInputLabelImage, yNegativeInputDropdown.getPosition()[0] + X_OFFSET, yNegativeInputDropdown.getPosition()[1] + 2, DROPDOWN_HEIGHT, DROPDOWN_HEIGHT);
+ }
+
+ public void updateJoystickInput(int inputNumber, Integer value) {
+ if (value == null) {
+ return;
+ }
+ emgJoystickInputs[inputNumber] = EmgJoystickInput.values()[value];
+ String inputName = emgJoystickInputs[inputNumber].getString();
+ switch (inputNumber) {
+ case 0:
+ xNegativeInputDropdown.getCaptionLabel().setText(inputName);
+ break;
+ case 1:
+ xPositiveInputDropdown.getCaptionLabel().setText(inputName);
+ break;
+ case 2:
+ yPositiveInputDropdown.getCaptionLabel().setText(inputName);
+ break;
+ case 3:
+ yNegativeInputDropdown.getCaptionLabel().setText(inputName);
+ break;
+ }
+ }
+
};
public void emgJoystickSmoothingDropdown(int n) {
@@ -353,6 +496,57 @@ public enum EmgJoystickSmoothing implements IndexingInterface
return value;
}
+ private static List getEnumStringsAsList() {
+ List enumStrings = new ArrayList();
+ for (IndexingInterface val : vals) {
+ enumStrings.add(val.getString());
+ }
+ return enumStrings;
+ }
+}
+
+public enum EmgJoystickInput implements IndexingInterface
+{
+ CHANNEL_1 (0, "Channel 1", 0),
+ CHANNEL_2 (1, "Channel 2", 1),
+ CHANNEL_3 (2, "Channel 3", 2),
+ CHANNEL_4 (3, "Channel 4", 3),
+ CHANNEL_5 (4, "Channel 5", 4),
+ CHANNEL_6 (5, "Channel 6", 5),
+ CHANNEL_7 (6, "Channel 7", 6),
+ CHANNEL_8 (7, "Channel 8", 7),
+ CHANNEL_9 (8, "Channel 9", 8),
+ CHANNEL_10 (9, "Channel 10", 9),
+ CHANNEL_11 (10, "Channel 11", 10),
+ CHANNEL_12 (11, "Channel 12", 11),
+ CHANNEL_13 (12, "Channel 13", 12),
+ CHANNEL_14 (13, "Channel 14", 13),
+ CHANNEL_15 (14, "Channel 15", 14),
+ CHANNEL_16 (15, "Channel 16", 15);
+
+ private int index;
+ private String name;
+ private int value;
+ private static EmgJoystickInput[] vals = values();
+
+ EmgJoystickInput(int index, String name, int value) {
+ this.index = index;
+ this.name = name;
+ this.value = value;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public String getString() {
+ return name;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
private static List getEnumStringsAsList() {
List enumStrings = new ArrayList();
for (IndexingInterface val : vals) {
diff --git a/OpenBCI_GUI/data/DOWN_100x100.png b/OpenBCI_GUI/data/DOWN_100x100.png
new file mode 100644
index 000000000..72d8b5de8
Binary files /dev/null and b/OpenBCI_GUI/data/DOWN_100x100.png differ
diff --git a/OpenBCI_GUI/data/LEFT_100x100.png b/OpenBCI_GUI/data/LEFT_100x100.png
new file mode 100644
index 000000000..1b6b6131f
Binary files /dev/null and b/OpenBCI_GUI/data/LEFT_100x100.png differ
diff --git a/OpenBCI_GUI/data/RIGHT_100x100.png b/OpenBCI_GUI/data/RIGHT_100x100.png
new file mode 100644
index 000000000..56a127893
Binary files /dev/null and b/OpenBCI_GUI/data/RIGHT_100x100.png differ
diff --git a/OpenBCI_GUI/data/UP_100x100.png b/OpenBCI_GUI/data/UP_100x100.png
new file mode 100644
index 000000000..562d46f0f
Binary files /dev/null and b/OpenBCI_GUI/data/UP_100x100.png differ