Skip to content

Commit 2a465c1

Browse files
committed
Add WidgetWithSettings to Spectrogram Widget and account for dual channel selects
1 parent a29d9bc commit 2a465c1

4 files changed

Lines changed: 154 additions & 96 deletions

File tree

OpenBCI_GUI/SpectrogramEnums.pde

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public enum SpectrogramMaxFrequency implements IndexingInterface {
3838
}
3939

4040
public enum SpectrogramWindowSize implements IndexingInterface {
41-
ONE_MINUTE (0, 1f, "1 Min.", new float[]{1, .5, 0}, 1000),
41+
ONE_MINUTE (0, 1f, "1 Min.", new float[]{1, .5, 0}, 25),
4242
ONE_MINUTE_THIRTY (1, 1.5f, "1.5 Min.", new float[]{1.5, 1, .5, 0}, 50),
4343
THREE_MINUTES (2, 3f, "3 Min.", new float[]{3, 2, 1, 0}, 100),
4444
SIX_MINUTES (3, 6f, "6 Min.", new float[]{6, 5, 4, 3, 2, 1, 0}, 200),

OpenBCI_GUI/W_Spectrogram.pde

Lines changed: 75 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
// W_Spectrogram.pde //
44
// //
55
// //
6-
// Created by: Richard Waltman, September 2019 //
6+
// Created by: Richard Waltman, September 2019 //
7+
// Refactored by: Richard Waltman, April 2025 //
78
// //
89
//////////////////////////////////////////////////////
910

10-
class W_Spectrogram extends Widget {
11-
//to see all core variables/methods of the Widget class, refer to Widget.pde
11+
class W_Spectrogram extends WidgetWithSettings {
1212
public ExGChannelSelect spectChanSelectTop;
1313
public ExGChannelSelect spectChanSelectBot;
1414
private boolean chanSelectWasOpen = false;
15-
List<controlP5.Controller> cp5ElementsToCheck = new ArrayList<controlP5.Controller>();
15+
List<controlP5.Controller> cp5ElementsToCheck;
1616

1717
int xPos = 0;
1818
int hueLimit = 160;
@@ -43,22 +43,10 @@ class W_Spectrogram extends Widget {
4343
float[] topFFTAvg;
4444
float[] botFFTAvg;
4545

46-
private SpectrogramMaxFrequency maxFrequency = SpectrogramMaxFrequency.MAX_60;
47-
private SpectrogramWindowSize windowSize = SpectrogramWindowSize.ONE_MINUTE;
48-
private FFTLogLin logLin = FFTLogLin.LIN;
49-
5046
W_Spectrogram() {
5147
super();
5248
widgetTitle = "Spectrogram";
5349

54-
//Add channel select dropdown to this widget
55-
spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true);
56-
spectChanSelectBot = new DualExGChannelSelect(ourApplet, x, y + navH, w, navH, false);
57-
activateDefaultChannels();
58-
59-
cp5ElementsToCheck.addAll(spectChanSelectTop.getCp5ElementsForOverlapCheck());
60-
cp5ElementsToCheck.addAll(spectChanSelectBot.getCp5ElementsForOverlapCheck());
61-
6250
xPos = w - 1; //draw on the right, and shift pixels to the left
6351
prevW = w;
6452
prevH = h;
@@ -70,20 +58,62 @@ class W_Spectrogram extends Widget {
7058
//Fetch/calculate the time strings for the horizontal axis ticks
7159
horizontalAxisLabelStrings = fetchTimeStrings();
7260

73-
List<String> maxFrequencyList = EnumHelper.getEnumStrings(SpectrogramMaxFrequency.class);
74-
List<String> windowSizeList = EnumHelper.getEnumStrings(SpectrogramWindowSize.class);
75-
List<String> logLinList = EnumHelper.getEnumStrings(FFTLogLin.class);
76-
77-
addDropdown("spectrogramMaxFrequencyDropdown", "Max Hz", maxFrequencyList, maxFrequency.getIndex());
78-
addDropdown("spectrogramWindowDropdown", "Window", windowSizeList, windowSize.getIndex());
79-
addDropdown("spectrogramLogLinDropdown", "Log/Lin", logLinList, logLin.getIndex());
80-
8161
//Resize the height of the data image using default
62+
SpectrogramMaxFrequency maxFrequency = widgetSettings.get(SpectrogramMaxFrequency.class);
8263
dataImageH = maxFrequency.getAxisLabels()[0] * 2;
8364
//Create image using correct dimensions! Fixes bug where image size and labels do not align on session start.
8465
dataImg = createImage(dataImageW, dataImageH, RGB);
8566
}
8667

68+
@Override
69+
protected void initWidgetSettings() {
70+
super.initWidgetSettings();
71+
widgetSettings.set(SpectrogramMaxFrequency.class, SpectrogramMaxFrequency.MAX_60)
72+
.set(SpectrogramWindowSize.class, SpectrogramWindowSize.ONE_MINUTE)
73+
.set(FFTLogLin.class, FFTLogLin.LIN);
74+
75+
initDropdown(SpectrogramMaxFrequency.class, "spectrogramMaxFrequencyDropdown", "Max Hz");
76+
initDropdown(SpectrogramWindowSize.class, "spectrogramWindowDropdown", "Window");
77+
initDropdown(FFTLogLin.class, "spectrogramLogLinDropdown", "Log/Lin");
78+
79+
spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true);
80+
spectChanSelectBot = new DualExGChannelSelect(ourApplet, x, y + navH, w, navH, false);
81+
activateDefaultChannels();
82+
83+
// Save both channel selections with unique identifiers
84+
saveNamedChannels("top", spectChanSelectTop.getActiveChannels());
85+
saveNamedChannels("bottom", spectChanSelectBot.getActiveChannels());
86+
87+
cp5ElementsToCheck = new ArrayList<controlP5.Controller>();
88+
cp5ElementsToCheck.addAll(spectChanSelectTop.getCp5ElementsForOverlapCheck());
89+
cp5ElementsToCheck.addAll(spectChanSelectBot.getCp5ElementsForOverlapCheck());
90+
}
91+
92+
@Override
93+
protected void applySettings() {
94+
updateDropdownLabel(SpectrogramMaxFrequency.class, "spectrogramMaxFrequencyDropdown");
95+
updateDropdownLabel(SpectrogramWindowSize.class, "spectrogramWindowDropdown");
96+
updateDropdownLabel(FFTLogLin.class, "spectrogramLogLinDropdown");
97+
applyMaxFrequency();
98+
applyWindowSize();
99+
100+
// Apply saved channel selections if available
101+
if (hasNamedChannels("top")) {
102+
applyNamedChannels("top", spectChanSelectTop);
103+
}
104+
105+
if (hasNamedChannels("bottom")) {
106+
applyNamedChannels("bottom", spectChanSelectBot);
107+
}
108+
}
109+
110+
@Override
111+
protected void updateChannelSettings() {
112+
// Save current channel selections before saving settings
113+
saveNamedChannels("top", spectChanSelectTop.getActiveChannels());
114+
saveNamedChannels("bottom", spectChanSelectBot.getActiveChannels());
115+
}
116+
87117
void update(){
88118
super.update();
89119

@@ -148,8 +178,11 @@ class W_Spectrogram extends Widget {
148178
//draw the spectrogram if the widget is open, and update pixels if board is streaming data
149179
if (currentBoard.isStreaming()) {
150180
pushStyle();
181+
151182
dataImg.loadPixels();
152183

184+
FFTLogLin logLin = widgetSettings.get(FFTLogLin.class);
185+
153186
//Shift all pixels to the left! (every scrollspeed ms)
154187
if(millis() - lastShift > scrollSpeed) {
155188
for (int r = 0; r < dataImg.height; r++) {
@@ -265,6 +298,7 @@ class W_Spectrogram extends Widget {
265298
fill(255);
266299
strokeWeight(2);
267300
textSize(11);
301+
SpectrogramWindowSize windowSize = widgetSettings.get(SpectrogramWindowSize.class);
268302
int horizontalAxisDivCount = windowSize.getAxisLabels().length;
269303
for (int i = 0; i < horizontalAxisDivCount; i++) {
270304
float offset = scaledW * dataImageW * (float(i) / horizontalAxisDivCount);
@@ -297,6 +331,7 @@ class W_Spectrogram extends Widget {
297331
fill(255);
298332
textSize(12);
299333
strokeWeight(2);
334+
SpectrogramMaxFrequency maxFrequency = widgetSettings.get(SpectrogramMaxFrequency.class);
300335
int verticalAxisDivCount = maxFrequency.getAxisLabels().length - 1;
301336
for (int i = 0; i < verticalAxisDivCount; i++) {
302337
float offset = scaledH * dataImageH * (float(i) / verticalAxisDivCount);
@@ -329,6 +364,7 @@ class W_Spectrogram extends Widget {
329364
return;
330365
}
331366
}
367+
FFTLogLin logLin = widgetSettings.get(FFTLogLin.class);
332368
pushStyle();
333369
//draw color scale reference to the right of the spectrogram
334370
for (int i = 0; i < colorScaleHeight; i++) {
@@ -405,6 +441,7 @@ class W_Spectrogram extends Widget {
405441
time = LocalDateTime.ofInstant(Instant.ofEpochMilli(getCurrentTimeStamp()),
406442
TimeZone.getDefault().toZoneId());
407443
}
444+
SpectrogramWindowSize windowSize = widgetSettings.get(SpectrogramWindowSize.class);
408445
for (int i = 0; i < windowSize.getAxisLabels().length; i++) {
409446
long l = (long)(windowSize.getAxisLabels()[i] * 60f);
410447
LocalDateTime t = time.minus(l, ChronoUnit.SECONDS);
@@ -434,24 +471,33 @@ class W_Spectrogram extends Widget {
434471
}
435472

436473
public void setLogLin(int n) {
437-
logLin = logLin.values()[n];
474+
widgetSettings.setByIndex(FFTLogLin.class, n);
438475
}
439476

440477
public void setMaxFrequency(int n) {
441-
maxFrequency = maxFrequency.values()[n];
478+
widgetSettings.setByIndex(SpectrogramMaxFrequency.class, n);
479+
applyMaxFrequency();
480+
}
481+
482+
public void setWindowSize(int n) {
483+
widgetSettings.setByIndex(SpectrogramWindowSize.class, n);
484+
applyWindowSize();
485+
}
486+
487+
private void applyMaxFrequency() {
488+
SpectrogramMaxFrequency maxFrequency = widgetSettings.get(SpectrogramMaxFrequency.class);
442489
// Resize the height of the data image
443490
dataImageH = maxFrequency.getAxisLabels()[0] * 2;
444491
// Overwrite the existing image
445492
dataImg = createImage(dataImageW, dataImageH, RGB);
446493
}
447494

448-
public void setWindowSize(int n) {
449-
windowSize = windowSize.values()[n];
495+
private void applyWindowSize() {
496+
SpectrogramWindowSize windowSize = widgetSettings.get(SpectrogramWindowSize.class);
450497
setScrollSpeed(windowSize.getScrollSpeed());
451498
horizontalAxisLabelStrings.clear();
452499
horizontalAxisLabelStrings = fetchTimeStrings();
453500
dataImg = createImage(dataImageW, dataImageH, RGB);
454-
455501
}
456502
};
457503

@@ -466,69 +512,3 @@ public void spectrogramWindowDropdown(int n) {
466512
public void spectrogramLogLinDropdown(int n) {
467513
((W_Spectrogram) widgetManager.getWidget("W_Spectrogram")).setLogLin(n);
468514
}
469-
470-
471-
//Save data from the Active channel checkBoxes - Top
472-
//JSONArray saveActiveChanSpectTop = new JSONArray();
473-
/*
474-
//FIX ME
475-
int numActiveSpectChanTop = w_spectrogram.spectChanSelectTop.getActiveChannels().size();
476-
for (int i = 0; i < numActiveSpectChanTop; i++) {
477-
int activeChannel = w_spectrogram.spectChanSelectTop.getActiveChannels().get(i);
478-
saveActiveChanSpectTop.setInt(i, activeChannel);
479-
}
480-
saveSpectrogramSettings.setJSONArray("activeChannelsTop", saveActiveChanSpectTop);
481-
//Save data from the Active channel checkBoxes - Bottom
482-
JSONArray saveActiveChanSpectBot = new JSONArray();
483-
int numActiveSpectChanBot = w_spectrogram.spectChanSelectBot.getActiveChannels().size();
484-
for (int i = 0; i < numActiveSpectChanBot; i++) {
485-
int activeChannel = w_spectrogram.spectChanSelectBot.getActiveChannels().get(i);
486-
saveActiveChanSpectBot.setInt(i, activeChannel);
487-
}
488-
*/
489-
//saveSpectrogramSettings.setJSONArray("activeChannelsBot", saveActiveChanSpectBot);
490-
//Save Spectrogram_Max Freq Setting. The max frq variable is updated every time the user selects a dropdown in the spectrogram widget
491-
//FIX ME
492-
/*
493-
saveSpectrogramSettings.setInt("Spectrogram_Max Freq", spectMaxFrqSave);
494-
saveSpectrogramSettings.setInt("Spectrogram_Sample Rate", spectSampleRateSave);
495-
saveSpectrogramSettings.setInt("Spectrogram_LogLin", spectLogLinSave);
496-
*/
497-
498-
//Get Band Power widget settings
499-
//FIX ME
500-
/*
501-
loadBPActiveChans.clear();
502-
JSONObject loadBPSettings = loadSettingsJSONData.getJSONObject(kJSONKeyBandPower);
503-
JSONArray loadBPChan = loadBPSettings.getJSONArray("activeChannels");
504-
for (int i = 0; i < loadBPChan.size(); i++) {
505-
loadBPActiveChans.add(loadBPChan.getInt(i));
506-
}
507-
loadBPAutoClean = loadBPSettings.getInt("bpAutoClean");
508-
loadBPAutoCleanThreshold = loadBPSettings.getInt("bpAutoCleanThreshold");
509-
loadBPAutoCleanTimer = loadBPSettings.getInt("bpAutoCleanTimer");
510-
//println("Settings: band power active chans loaded = " + loadBPActiveChans );
511-
*/
512-
513-
/*
514-
try {
515-
//Get Spectrogram widget settings
516-
loadSpectActiveChanTop.clear();
517-
loadSpectActiveChanBot.clear();
518-
JSONObject loadSpectSettings = loadSettingsJSONData.getJSONObject(kJSONKeySpectrogram);
519-
JSONArray loadSpectChanTop = loadSpectSettings.getJSONArray("activeChannelsTop");
520-
for (int i = 0; i < loadSpectChanTop.size(); i++) {
521-
loadSpectActiveChanTop.add(loadSpectChanTop.getInt(i));
522-
}
523-
JSONArray loadSpectChanBot = loadSpectSettings.getJSONArray("activeChannelsBot");
524-
for (int i = 0; i < loadSpectChanBot.size(); i++) {
525-
loadSpectActiveChanBot.add(loadSpectChanBot.getInt(i));
526-
}
527-
spectMaxFrqLoad = loadSpectSettings.getInt("Spectrogram_Max Freq");
528-
spectSampleRateLoad = loadSpectSettings.getInt("Spectrogram_Sample Rate");
529-
spectLogLinLoad = loadSpectSettings.getInt("Spectrogram_LogLin");
530-
//println(loadSpectActiveChanTop, loadSpectActiveChanBot);
531-
} catch (Exception e) {
532-
e.printStackTrace();
533-
}
534-
*/

OpenBCI_GUI/Widget.pde

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,49 @@ abstract class WidgetWithSettings extends Widget {
437437
// Default implementation does nothing
438438
// Override in widgets that have channel selectors
439439
}
440+
441+
/**
442+
* Save active channel selection to widget settings with a specific name
443+
* @param name Identifier for this channel selection (e.g., "top", "bottom")
444+
* @param channels List of selected channel indices
445+
*/
446+
protected void saveNamedChannels(String name, List<Integer> channels) {
447+
widgetSettings.setNamedChannels(name, channels);
448+
println(widgetTitle + ": Saved " + channels.size() + " channels for " + name);
449+
}
450+
451+
/**
452+
* Apply saved named channel selection to a channel select component
453+
* @param name Identifier for the channel selection
454+
* @param channelSelect The channel select component to update
455+
* @return true if channels were loaded and applied, false otherwise
456+
*/
457+
protected boolean applyNamedChannels(String name, ExGChannelSelect channelSelect) {
458+
List<Integer> savedChannels = widgetSettings.getNamedChannels(name);
459+
if (!savedChannels.isEmpty()) {
460+
channelSelect.updateChannelSelection(savedChannels);
461+
return true;
462+
}
463+
return false;
464+
}
465+
466+
/**
467+
* Get the list of active channels for a named selection from settings
468+
* @param name Identifier for the channel selection
469+
* @return List of active channel indices, or empty list if none are saved
470+
*/
471+
protected List<Integer> getNamedChannels(String name) {
472+
return widgetSettings.getNamedChannels(name);
473+
}
474+
475+
/**
476+
* Check if a named channel selection is defined in settings
477+
* @param name Identifier for the channel selection
478+
* @return true if the named channel selection is defined, false otherwise
479+
*/
480+
protected boolean hasNamedChannels(String name) {
481+
return widgetSettings.hasNamedChannels(name);
482+
}
440483
}
441484

442485
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

OpenBCI_GUI/WidgetSettings.pde

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,41 @@ class WidgetSettings {
124124
!channelSettings.get(KEY_ACTIVE_CHANNELS).isEmpty();
125125
}
126126

127+
/**
128+
* Store active channels with a specific name identifier
129+
* @param name Identifier for this channel selection (e.g., "top", "bottom")
130+
* @param channels List of selected channel indices
131+
* @return this WidgetSettings instance for method chaining
132+
*/
133+
public WidgetSettings setNamedChannels(String name, List<Integer> channels) {
134+
// Create a copy to prevent external modification
135+
channelSettings.put(name, new ArrayList<Integer>(channels));
136+
return this;
137+
}
138+
139+
/**
140+
* Get active channels for a specific named selection
141+
* @param name Identifier for the channel selection
142+
* @return List of selected channel indices or empty list if not found
143+
*/
144+
public List<Integer> getNamedChannels(String name) {
145+
if (channelSettings.containsKey(name)) {
146+
// Return a copy to prevent external modification
147+
return new ArrayList<Integer>(channelSettings.get(name));
148+
}
149+
return new ArrayList<Integer>(); // Empty list if not found
150+
}
151+
152+
/**
153+
* Check if a named channel selection exists
154+
* @param name Identifier for the channel selection
155+
* @return true if the named selection exists, false otherwise
156+
*/
157+
public boolean hasNamedChannels(String name) {
158+
return channelSettings.containsKey(name) &&
159+
!channelSettings.get(name).isEmpty();
160+
}
161+
127162
//
128163
// OTHER SETTINGS
129164
//

0 commit comments

Comments
 (0)