Skip to content

Commit

Permalink
Merge pull request #985 from OpenBCI/development
Browse files Browse the repository at this point in the history
GUI 5.0.6 - July 2021
  • Loading branch information
retiutut authored Jul 31, 2021
2 parents d3e635d + 9b24951 commit 5313607
Show file tree
Hide file tree
Showing 15 changed files with 361 additions and 35 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# v5.0.6

### Improvements
* Add Auditory Feedback to the Focus Widget Fixes #709

### Bug Fixes
* Fix drawing error in Control Panel WiFi Shield static IP Textfield
* Accomodate high-dpi screens Fixes #968
* Add Arduino Focus Fan example to networking test kit on GitHub repo
* Allow synthetic square wave expert mode keyboard shortcut for Cyton and Ganglion Fixes #976

# v5.0.5

### Improvements
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/////////////////////////////////////////////////////////////////////////////////////////
// OpenBCI_GUI to Arduino via Serial: Focus Fan! //
// //
// - The Arduino Built-In LED blinks when the user is Focused //
// - A button on pin 7 toggles motor speed: Full, Medium, Low, and Off //
// //
// Tested 7/13/2021 using iMac, Arduino Pro Mini, OpenBCI_GUI 5.0.5 //
// Uses https://learn.adafruit.com/adafruit-arduino-lesson-15-dc-motor-reversing/ //
// and https://docs.openbci.com/Tutorials/17-Arduino_Focus_Example //
/////////////////////////////////////////////////////////////////////////////////////////

const byte numChars = 32;
char receivedChars[numChars]; // an array to store the received data
String previousData = "";
boolean newData = false;
boolean isFocused = false;
boolean lastFocusState = false;

int enablePin = 11;
int in1Pin = 10;
int in2Pin = 9;
int buttonPin = 7;
int ledPin = 13;

int buttonPushCounter = 0; // counter for the number of button presses
int buttonState = 0; // current state of the button
int lastButtonState = 0; // previous state of the button

void setup() {
pinMode(in1Pin, OUTPUT);
pinMode(in2Pin, OUTPUT);
pinMode(enablePin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);

Serial.begin(57600);
pinMode(LED_BUILTIN, OUTPUT);
Serial.println("<Arduino is ready>");
}

void loop() {
recvWithEndMarker();
showNewData();

handleButtonState();
//check for state change
if (isFocused) {
setMotor(getMotorPower(), true);
} else {
setMotor(0, true);
}
lastFocusState = isFocused;
}

//Recieve data and look for the endMarker '\n' (new line)
void recvWithEndMarker() {
static byte ndx = 0;
char endMarker = '\n';
char rc;

while (Serial.available() > 0 && newData == false) {
rc = Serial.read();

if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
ndx = 0;
newData = true;
}
}
}

void showNewData() {
if (newData == true) {
//Convert char array into string
String s = receivedChars;
//Only perform an action when the incoming data changes
if (!s.equals(previousData)) {
//Check if the string is "true" or "false"
if (s.equals("0")) {
Serial.println("Input: FALSE");
isFocused = false;
digitalWrite(LED_BUILTIN, LOW);
} else if (s.equals("1")) {
Serial.println("Input: TRUE");
digitalWrite(LED_BUILTIN, HIGH);
isFocused = true;
} else {
//Otherwise print the incoming with no action
Serial.println("This just in ... " + s);
}
}
newData = false;
previousData = s;
}
}

void setMotor(int speed, boolean reverse) {
analogWrite(enablePin, speed);
digitalWrite(in1Pin, !reverse);
digitalWrite(in2Pin, reverse);
}

void handleButtonState () {
buttonState = digitalRead(buttonPin);

// compare the buttonState to its previous state
if (buttonState != lastButtonState) {
// if the state has changed, increment the counter
if (buttonState == HIGH) {
// if the current state is HIGH then the button went from off to on:
buttonPushCounter++;
//Serial.println("on");
} else {
// if the current state is LOW then the button went from on to off:
//Serial.println("off");
}
// Delay a little bit to avoid bouncing
delay(50);
}
// save the current state as the last state, for next time through the loop
lastButtonState = buttonState;
}

int getMotorPower() {
//Toggle the fan power between four settings, shown below
//Default: Full Power
int power;
switch (buttonPushCounter % 4) {
case 0: //Every 4 clicks reverts to full power
power = 255; //Full power
break;
case 1:
power = 180; //Medium power
break;
case 2:
power = 90; //Low power
break;
case 3:
power = 0; //Motor off
break;
}
return power;
}
130 changes: 130 additions & 0 deletions OpenBCI_GUI/AuditoryNeurofeedback.pde
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//Used in the Focus Widget to provide auditory neurofeedback
//Adjust amplitude of calming audio samples using normalized band power data or predicted metric

Minim minim;
FilePlayer[] auditoryNfbFilePlayers;
ddf.minim.ugens.Gain[] auditoryNfbGains;
AudioOutput audioOutput;

//Pre-load audio files into memory in delayedSetup for best app performance and no waiting
void asyncLoadAudioFiles() {
final int _numSoundFiles = 5;
minim = new Minim(this);
auditoryNfbFilePlayers = new FilePlayer[_numSoundFiles];
auditoryNfbGains = new ddf.minim.ugens.Gain[_numSoundFiles];
audioOutput = minim.getLineOut();
println("OpenBCI_GUI: AuditoryFeedback: Loading Audio...");
for (int i = 0; i < _numSoundFiles; i++) {
//Use large buffer size and cache files in memory
auditoryNfbFilePlayers[i] = new FilePlayer( minim.loadFileStream("bp" + (i+1) + ".mp3", 2048, true) );
auditoryNfbGains[i] = new ddf.minim.ugens.Gain(-15.0f);
auditoryNfbFilePlayers[i].patch(auditoryNfbGains[i]).patch(audioOutput);
}
println("OpenBCI_GUI: AuditoryFeedback: Done Loading Audio!");
}

class AuditoryNeurofeedback {

private int x, y, w, h;
private ControlP5 localCP5;
public Button startStopButton;
public Button modeButton;
private boolean usingBandPowers = false;
//There will always be 5 band powers, and 5 possible concurrent audio files for playback
private final int NUM_SOUND_FILES = auditoryNfbFilePlayers.length;
private final float MIN_GAIN = -42.0;
private final float MAX_GAIN = -7.0;
private final int MAX_BUTTON_W = 120;
private int buttonW = 120;
private int buttonH;

AuditoryNeurofeedback(int _x, int _y, int _w, int _h) {
localCP5 = new ControlP5(ourApplet);
localCP5.setGraphics(ourApplet, 0,0);
localCP5.setAutoDraw(false);
buttonH = _h;
createStartStopButton(_x, _y, buttonW, buttonH);
createModeButton(_x, _y, buttonW, buttonH);
}

//Use band powers or prediction value to control volume of each sound file
public void update(double[] bandPowers, float predictionVal) {
if (usingBandPowers) {
for (int i = 0; i < NUM_SOUND_FILES; i++) {
float gain = map((float)bandPowers[i], 0.1, .7, MIN_GAIN + 20f, MAX_GAIN);
auditoryNfbGains[i].setValue(gain);
}
} else {
float gain = map(predictionVal, 0.0, 1.0, MIN_GAIN, MAX_GAIN);
for (int i = 0; i < NUM_SOUND_FILES; i++) {
auditoryNfbGains[i].setValue(gain);
}
}
}

public void draw() {
localCP5.draw();
}

public void screenResized(int _x, int _y, int _w, int _h) {
localCP5.setGraphics(ourApplet, 0, 0);
buttonW = (_w - 6) / 2;
buttonW = buttonW > MAX_BUTTON_W ? MAX_BUTTON_W : buttonW;
startStopButton.setPosition(_x - buttonW - 3, _y);
startStopButton.setSize(buttonW, _h);
modeButton.setPosition(_x + 3, _y);
modeButton.setSize(buttonW, _h);
}

public void killAudio() {
for (int i = 0; i < NUM_SOUND_FILES; i++) {
auditoryNfbFilePlayers[i].pause();
auditoryNfbFilePlayers[i].rewind();
}
}

private void createStartStopButton(int _x, int _y, int _w, int _h) {
//This is a generalized createButton method that allows us to save code by using a few patterns and method overloading
startStopButton = createButton(localCP5, "startStopButton", "Turn Audio On", _x, _y, _w, _h, p5, 12, colorNotPressed, OPENBCI_DARKBLUE);
//Set the border color explicitely
startStopButton.setBorderColor(OBJECT_BORDER_GREY);
//For this button, only call the callback listener on mouse release
startStopButton.onRelease(new CallbackListener() {
public void controlEvent(CallbackEvent theEvent) {
//If using a TopNav object, ignore interaction with widget object (ex. widgetTemplateButton)
if (!topNav.configSelector.isVisible && !topNav.layoutSelector.isVisible) {
if (auditoryNfbFilePlayers[0].isPlaying()) {
killAudio();
startStopButton.getCaptionLabel().setText("Turn Audio On");
} else {
for (int i = 0; i < NUM_SOUND_FILES; i++) {
auditoryNfbFilePlayers[i].loop();
}
startStopButton.getCaptionLabel().setText("Turn Audio Off");
}
}
}
});
startStopButton.setDescription("Start and Stop Auditory Feedback.");
}

private void createModeButton(int _x, int _y, int _w, int _h) {
//This is a generalized createButton method that allows us to save code by using a few patterns and method overloading
modeButton = createButton(localCP5, "modeButton", "Use Band Powers", _x, _y, _w, _h, p5, 12, colorNotPressed, OPENBCI_DARKBLUE);
//Set the border color explicitely
modeButton.setBorderColor(OBJECT_BORDER_GREY);
//For this button, only call the callback listener on mouse release
modeButton.onRelease(new CallbackListener() {
public void controlEvent(CallbackEvent theEvent) {
//If using a TopNav object, ignore interaction with widget object (ex. widgetTemplateButton)
if (!topNav.configSelector.isVisible && !topNav.layoutSelector.isVisible) {
String s = !usingBandPowers ? "Use Metric" : "Use Band Powers";
modeButton.getCaptionLabel().setText(s);
usingBandPowers = !usingBandPowers;
}
}
});
modeButton.setDescription("Change Auditory Feedback mode. Use the Metric to control all notes at once, or use Band Powers to control certain notes of the chord.");
}

}
18 changes: 9 additions & 9 deletions OpenBCI_GUI/ControlPanel.pde
Original file line number Diff line number Diff line change
Expand Up @@ -941,9 +941,15 @@ class WifiBox {
.setColorCursor(color(26, 26, 26))
.setText(wifi_ipAddress)
.align(5, 10, 20, 40)
.onDoublePress(cb)
.setAutoClear(true)
.setVisible(false);
//Clear textfield on double click
staticIPAddressTF.onDoublePress(new CallbackListener() {
public void controlEvent(CallbackEvent theEvent) {
output("WiFi Static IP: Enter your custom IP address for WiFi shield.");
staticIPAddressTF.clear();
}
});
}

public void setDefaultToDynamicIP() {
Expand All @@ -956,15 +962,8 @@ class WifiBox {
}

private void setStaticIPTextfield(String text) {
staticIPAddressTF.getCaptionLabel().setText(text);
staticIPAddressTF.setText(text);
}

//Clear text field on double-click
CallbackListener cb = new CallbackListener() {
public void controlEvent(CallbackEvent theEvent) {
staticIPAddressTF.clear();
}
};
};

class InterfaceBoxCyton {
Expand Down Expand Up @@ -2787,6 +2786,7 @@ class InitBox {
controlPanel.dataLogBoxGanglion.setSessionTextfieldText(directoryManager.getFileNameDateTime());
controlPanel.dataLogBoxGalea.setSessionTextfieldText(directoryManager.getFileNameDateTime());
controlPanel.wifiBox.setStaticIPTextfield(wifi_ipAddress);
w_focus.killAuditoryFeedback();
haltSystem();
}
}
Expand Down
6 changes: 3 additions & 3 deletions OpenBCI_GUI/DataProcessing.pde
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ void processNewData() {
}
}

void initializeFFTObjects(FFT[] fftBuff, float[][] dataProcessingRawBuffer, int Nfft, float fs_Hz) {
void initializeFFTObjects(ddf.minim.analysis.FFT[] fftBuff, float[][] dataProcessingRawBuffer, int Nfft, float fs_Hz) {

float[] fooData;
for (int Ichan=0; Ichan < nchan; Ichan++) {
//make the FFT objects...Following "SoundSpectrum" example that came with the Minim library
fftBuff[Ichan].window(FFT.HAMMING);
fftBuff[Ichan].window(ddf.minim.analysis.FFT.HAMMING);

//do the FFT on the initial data
if (isFFTFiltered == true) {
Expand Down Expand Up @@ -237,7 +237,7 @@ class DataProcessing {
}
}

public void process(float[][] data_forDisplay_uV, FFT[] fftData) { //holds the FFT (frequency spectrum) of the latest data
public void process(float[][] data_forDisplay_uV, ddf.minim.analysis.FFT[] fftData) { //holds the FFT (frequency spectrum) of the latest data

float prevFFTdata[] = new float[fftBuff[0].specSize()];

Expand Down
4 changes: 2 additions & 2 deletions OpenBCI_GUI/Info.plist.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<key>CFBundleShortVersionString</key>
<string>5</string>
<key>CFBundleVersion</key>
<string>5.0.5</string>
<string>5.0.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>NSHumanReadableCopyright</key>
Expand All @@ -32,7 +32,7 @@
Copyright © 2021 OpenBCI
</string>
<key>CFBundleGetInfoString</key>
<string>May 2021</string>
<string>July 2021</string>
<!-- End of the set that can be customized -->

@@jvm_runtime@@
Expand Down
5 changes: 3 additions & 2 deletions OpenBCI_GUI/Interactivity.pde
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,9 @@ void parseKey(char val) {
break;
}
}

if (currentBoard instanceof BoardGanglion) {

// Fixes #976. These keyboard shortcuts enable synthetic square waves on Ganglion and Cyton
if (currentBoard instanceof BoardGanglion || currentBoard instanceof BoardCyton) {
if (val == '[' || val == ']') {
println("Expert Mode: '" + val + "' pressed. Sending to Ganglion...");
Boolean success = ((Board)currentBoard).sendCommand(str(val)).getKey();
Expand Down
Loading

0 comments on commit 5313607

Please sign in to comment.