diff --git a/resources/surge-shared/paramdocumentation.xml b/resources/surge-shared/paramdocumentation.xml
index d6e2e3a5a66..71746b7e6db 100644
--- a/resources/surge-shared/paramdocumentation.xml
+++ b/resources/surge-shared/paramdocumentation.xml
@@ -34,6 +34,7 @@
+
diff --git a/src/surge-xt/gui/overlays/AboutScreen.cpp b/src/surge-xt/gui/overlays/AboutScreen.cpp
index 20418764ae6..3b727fbcab6 100644
--- a/src/surge-xt/gui/overlays/AboutScreen.cpp
+++ b/src/surge-xt/gui/overlays/AboutScreen.cpp
@@ -408,6 +408,12 @@ void AboutScreen::resized()
"by Émilie Gillet, licensed under MIT license",
600);
+ yp += lblvs;
+
+ addLabel("Oscilloscope code based on s(m)exoscope by Bram @ Smartelectronix, licensed "
+ "under GNU GPL v3 license",
+ 600);
+
auto img = associatedBitmapStore->getImage(IDB_ABOUT_LOGOS);
auto idxes = {0, 4, 3, 6, 1, 2, 5};
diff --git a/src/surge-xt/gui/overlays/Oscilloscope.cpp b/src/surge-xt/gui/overlays/Oscilloscope.cpp
index f8cefadc478..632181ba613 100644
--- a/src/surge-xt/gui/overlays/Oscilloscope.cpp
+++ b/src/surge-xt/gui/overlays/Oscilloscope.cpp
@@ -27,6 +27,8 @@
#include "RuntimeFont.h"
#include "SkinColors.h"
+#include "widgets/MenuCustomComponents.h"
+
using namespace std::chrono_literals;
using std::placeholders::_1;
@@ -534,7 +536,9 @@ Oscilloscope::Oscilloscope(SurgeGUIEditor *e, SurgeStorage *s)
setOpaque(true);
background_.updateBackgroundType(WAVEFORM);
+
auto onToggle = std::bind(std::mem_fn(&Oscilloscope::toggleChannel), this);
+
left_chan_button_.setStorage(storage_);
left_chan_button_.setToggleState(true);
left_chan_button_.onToggle = onToggle;
@@ -543,6 +547,8 @@ Oscilloscope::Oscilloscope(SurgeGUIEditor *e, SurgeStorage *s)
left_chan_button_.setTitle("Left Channel");
left_chan_button_.setDescription("Enable input from left channel.");
left_chan_button_.setWantsKeyboardFocus(false);
+ left_chan_button_.setTag(tag_input_l);
+ left_chan_button_.addListener(this);
right_chan_button_.setStorage(storage_);
right_chan_button_.setToggleState(true);
right_chan_button_.onToggle = onToggle;
@@ -551,20 +557,26 @@ Oscilloscope::Oscilloscope(SurgeGUIEditor *e, SurgeStorage *s)
right_chan_button_.setTitle("Right Channel");
right_chan_button_.setDescription("Enable input from right channel.");
right_chan_button_.setWantsKeyboardFocus(false);
+ right_chan_button_.setTag(tag_input_r);
+ right_chan_button_.addListener(this);
scope_mode_button_.setStorage(storage_);
scope_mode_button_.setRows(1);
scope_mode_button_.setColumns(2);
scope_mode_button_.setLabels({"Waveform", "Spectrum"});
scope_mode_button_.setWantsKeyboardFocus(false);
scope_mode_button_.setValue(0.f);
+ scope_mode_button_.setTag(tag_scope_mode);
+ scope_mode_button_.setDraggable(true);
+ scope_mode_button_.addListener(this);
spectrum_parameters_.setOpaque(true);
waveform_parameters_.setOpaque(true);
addAndMakeVisible(background_);
addAndMakeVisible(left_chan_button_);
addAndMakeVisible(right_chan_button_);
addAndMakeVisible(scope_mode_button_);
- addAndMakeVisible(spectrum_);
- addAndMakeVisible(spectrum_parameters_);
+
+ addChildComponent(spectrum_);
+ addChildComponent(spectrum_parameters_);
addChildComponent(waveform_);
addChildComponent(waveform_parameters_);
@@ -609,6 +621,120 @@ void Oscilloscope::onSkinChanged()
void Oscilloscope::paint(juce::Graphics &g) {}
+int32_t Oscilloscope::controlModifierClicked(Surge::GUI::IComponentTagValue *pControl,
+ const juce::ModifierKeys &button,
+ bool isDoubleClickEvent)
+{
+ if (isDoubleClickEvent)
+ {
+ return 0;
+ }
+
+ int tag = pControl->getTag();
+
+ // Basically all the menus are a list of options with values
+ std::vector> options;
+ std::string menuName = "";
+
+ switch (tag)
+ {
+ case tag_scope_mode:
+ menuName = "Oscilloscope Mode";
+ options.push_back(std::make_pair("Waveform", 0));
+ options.push_back(std::make_pair("Spectrum", 1));
+ break;
+ case tag_input_l:
+ menuName = "Left Input";
+ break;
+ case tag_input_r:
+ menuName = "Right Input";
+ break;
+ case tag_wf_dc_block:
+ menuName = "DC Block";
+ break;
+ case tag_wf_freeze:
+ case tag_sp_freeze:
+ menuName = "Freeze";
+ break;
+ case tag_wf_sync:
+ menuName = "Sync Redraw";
+ break;
+ case tag_wf_time_scaling:
+ menuName = "Time Scaling";
+ break;
+ case tag_wf_amp_scaling:
+ menuName = "Amplitude Scaling";
+ break;
+ case tag_wf_trigger_mode:
+ menuName = "Trigger Mode";
+ options.push_back(std::make_pair("Freerun", 0.0));
+ options.push_back(std::make_pair("Rising Edge", 0.25));
+ options.push_back(std::make_pair("Falling Edge", 0.5));
+ options.push_back(std::make_pair("Internal Trigger", 1.0));
+ break;
+ case tag_wf_trigger_level:
+ menuName = "Trigger Level";
+ break;
+ case tag_wf_retrigger_threshold:
+ menuName = "Retrigger Threshold";
+ break;
+ case tag_wf_int_trigger_freq:
+ menuName = "Trigger Frequency";
+ break;
+ case tag_sp_min_level:
+ menuName = "Minimum Level";
+ break;
+ case tag_sp_max_level:
+ menuName = "Maximum Level";
+ break;
+ case tag_sp_decay_rate:
+ menuName = "Spectrum Decay Rate";
+ break;
+ default:
+ break;
+ }
+
+ auto contextMenu = juce::PopupMenu();
+
+ auto msurl = SurgeGUIEditor::helpURLForSpecial(storage_, "oscilloscope");
+ auto hurl = SurgeGUIEditor::fullyResolvedHelpURL(msurl);
+ auto tcomp = std::make_unique(menuName, hurl);
+
+ tcomp->setSkin(skin, associatedBitmapStore);
+
+ auto hment = tcomp->getTitle();
+
+ contextMenu.addCustomItem(-1, std::move(tcomp), nullptr, hment);
+
+ if (!options.empty())
+ {
+ contextMenu.addSeparator();
+
+ for (auto op : options)
+ {
+ auto val = op.second;
+
+ contextMenu.addItem(op.first, true, (val == pControl->getValue()),
+ [val, pControl, this]() {
+ pControl->setValue(val);
+ valueChanged(pControl);
+
+ auto iv = pControl->asJuceComponent();
+
+ if (iv)
+ {
+ iv->repaint();
+ }
+ });
+ }
+ }
+
+ contextMenu.showMenuAsync(editor_->popupMenuOptions(),
+ Surge::GUI::makeEndHoverCallback(pControl));
+
+ return 1;
+}
+
void Oscilloscope::resized()
{
// Scope looks like the following picture.
@@ -634,7 +760,7 @@ void Oscilloscope::resized()
left_chan_button_.setBounds(rhs - 21, 4, buttonSize, buttonSize);
right_chan_button_.setBounds(rhs - 5, 4, buttonSize, buttonSize);
- scope_mode_button_.setBounds(8, 4, 105, buttonSize);
+ scope_mode_button_.setBounds(8, 4, 116, buttonSize);
spectrum_.setBounds(scopeRect);
waveform_.setBounds(scopeRect);
@@ -648,9 +774,9 @@ void Oscilloscope::resized()
}
Oscilloscope::WaveformParameters::WaveformParameters(SurgeGUIEditor *e, SurgeStorage *s,
- juce::Component *parent)
+ Oscilloscope *parent)
: editor_(e), storage_(s), parent_(parent), freeze_("Freeze"), dc_kill_("DC Block"),
- sync_draw_("Sync")
+ sync_draw_("Sync Redraw")
{
// Initialize parameters from the DAW state.
auto *state = &s->getPatch().dawExtraState.editor.oscilloscopeOverlayState;
@@ -688,7 +814,7 @@ Oscilloscope::WaveformParameters::WaveformParameters(SurgeGUIEditor *e, SurgeSto
time_window_.setQuantitizedDisplayValue(params_.time_window);
amp_window_.setQuantitizedDisplayValue(params_.amp_window);
- trigger_speed_.setLabel("Internal Trigger Freq");
+ trigger_speed_.setLabel("Trigger Frequency");
trigger_level_.setLabel("Trigger Level");
trigger_limit_.setLabel("Retrigger Threshold");
time_window_.setLabel("Time Scaling");
@@ -718,6 +844,22 @@ Oscilloscope::WaveformParameters::WaveformParameters(SurgeGUIEditor *e, SurgeSto
time_window_.setPrecision(2);
amp_window_.setPrecision(2);
+ trigger_type_.setTag(tag_wf_trigger_mode);
+ trigger_speed_.setTag(tag_wf_int_trigger_freq);
+ trigger_level_.setTag(tag_wf_trigger_level);
+ trigger_limit_.setTag(tag_wf_retrigger_threshold);
+ time_window_.setTag(tag_wf_time_scaling);
+ amp_window_.setTag(tag_wf_amp_scaling);
+
+ trigger_type_.addListener(this);
+ trigger_speed_.addListener(this);
+ trigger_level_.addListener(this);
+ trigger_limit_.addListener(this);
+ time_window_.addListener(this);
+ amp_window_.addListener(this);
+
+ trigger_type_.setDraggable(true);
+
auto updateParameter = [this](float ¶m, float &backer, float value) {
std::lock_guard l(params_lock_);
params_changed_ = true;
@@ -750,7 +892,7 @@ Oscilloscope::WaveformParameters::WaveformParameters(SurgeGUIEditor *e, SurgeSto
time_window_.setRootWindow(parent_);
amp_window_.setRootWindow(parent_);
- // These are not visible by default, since the default trigger type is "free".
+ // These are not visible by default, since the default trigger type is Freerun
trigger_speed_.setVisible(false);
trigger_level_.setVisible(false);
trigger_limit_.setVisible(false);
@@ -761,10 +903,9 @@ Oscilloscope::WaveformParameters::WaveformParameters(SurgeGUIEditor *e, SurgeSto
addAndMakeVisible(time_window_);
addAndMakeVisible(amp_window_);
- // The multiswitch.
trigger_type_.setRows(4);
trigger_type_.setColumns(1);
- trigger_type_.setLabels({"Free", "Rising", "Falling", "Internal"});
+ trigger_type_.setLabels({"Freerun", "Rising Edge", "Falling Edge", "Internal Trigger"});
trigger_type_.setIntegerValue(static_cast(params_.trigger_type));
trigger_type_.setWantsKeyboardFocus(false);
trigger_type_.setOnUpdate([this, state](int value) {
@@ -820,6 +961,7 @@ Oscilloscope::WaveformParameters::WaveformParameters(SurgeGUIEditor *e, SurgeSto
dc_kill_.setValue(static_cast(params_.dc_kill));
sync_draw_.setValue(static_cast(params_.sync_draw));
+
dc_kill_.isToggled = params_.dc_kill;
sync_draw_.isToggled = params_.sync_draw;
@@ -827,6 +969,14 @@ Oscilloscope::WaveformParameters::WaveformParameters(SurgeGUIEditor *e, SurgeSto
dc_kill_.setWantsKeyboardFocus(false);
sync_draw_.setWantsKeyboardFocus(false);
+ freeze_.setTag(tag_wf_freeze);
+ dc_kill_.setTag(tag_wf_dc_block);
+ sync_draw_.setTag(tag_wf_sync);
+
+ freeze_.addListener(this);
+ dc_kill_.addListener(this);
+ sync_draw_.addListener(this);
+
freeze_.onToggle = std::bind(toggleParam, std::ref(params_.freeze), nullptr);
dc_kill_.onToggle = std::bind(toggleParam, std::ref(params_.dc_kill), &state->dc_kill);
sync_draw_.onToggle = std::bind(toggleParam, std::ref(params_.sync_draw), &state->sync_draw);
@@ -839,11 +989,14 @@ Oscilloscope::WaveformParameters::WaveformParameters(SurgeGUIEditor *e, SurgeSto
std::optional Oscilloscope::WaveformParameters::getParamsIfDirty()
{
std::lock_guard l(params_lock_);
+
if (params_changed_)
{
params_changed_ = false;
+
return params_;
}
+
return std::nullopt;
}
@@ -878,25 +1031,25 @@ void Oscilloscope::WaveformParameters::resized()
auto t = getTransform().inverted();
auto h = getHeight();
auto w = getWidth();
- auto buttonWidth = 49;
+ auto buttonWidth = 58;
int labelHeight = 12;
- trigger_type_.setBounds(222, 14, buttonWidth, 52);
+ trigger_type_.setBounds(227, 14, 68, 52);
- trigger_speed_.setBounds(283, 28, 140, 26);
- trigger_level_.setBounds(283, 14, 140, 26);
- trigger_limit_.setBounds(283, 42, 140, 26);
+ trigger_speed_.setBounds(307, 28, 140, 26);
+ trigger_level_.setBounds(307, 14, 140, 26);
+ trigger_limit_.setBounds(307, 42, 140, 26);
- time_window_.setBounds(73, 14, 140, 26);
- amp_window_.setBounds(73, 42, 140, 26);
+ time_window_.setBounds(78, 14, 140, 26);
+ amp_window_.setBounds(78, 42, 140, 26);
- dc_kill_.setBounds(12, 14, buttonWidth, 14);
- freeze_.setBounds(12, 33, buttonWidth, 14);
- sync_draw_.setBounds(12, 52, buttonWidth, 14);
+ dc_kill_.setBounds(8, 14, buttonWidth, 14);
+ freeze_.setBounds(8, 33, buttonWidth, 14);
+ sync_draw_.setBounds(8, 52, buttonWidth, 14);
}
Oscilloscope::SpectrumParameters::SpectrumParameters(SurgeGUIEditor *e, SurgeStorage *s,
- juce::Component *parent)
+ Oscilloscope *parent)
: editor_(e), storage_(s), parent_(parent), freeze_("Freeze")
{
// Initialize parameters from the DAW state.
@@ -941,6 +1094,14 @@ Oscilloscope::SpectrumParameters::SpectrumParameters(SurgeGUIEditor *e, SurgeSto
max_db_.setUnit(" dB");
decay_rate_.setUnit(" %");
+ noise_floor_.setTag(tag_sp_min_level);
+ max_db_.setTag(tag_sp_max_level);
+ decay_rate_.setTag(tag_sp_decay_rate);
+
+ noise_floor_.addListener(this);
+ max_db_.addListener(this);
+ decay_rate_.addListener(this);
+
auto updateParameter = [this](float ¶m, float &backer, float value) {
std::lock_guard l(params_lock_);
params_changed_ = true;
@@ -971,6 +1132,8 @@ Oscilloscope::SpectrumParameters::SpectrumParameters(SurgeGUIEditor *e, SurgeSto
};
freeze_.setWantsKeyboardFocus(false);
+ freeze_.setTag(tag_sp_freeze);
+ freeze_.addListener(this);
freeze_.onToggle = std::bind(toggleParam, std::ref(params_.freeze));
addAndMakeVisible(freeze_);
@@ -1013,13 +1176,13 @@ void Oscilloscope::SpectrumParameters::resized()
auto t = getTransform().inverted();
auto h = getHeight();
auto w = getWidth();
- auto buttonWidth = 49;
+ auto buttonWidth = 58;
- noise_floor_.setBounds(73, 14, 140, 26);
- max_db_.setBounds(73, 42, 140, 26);
- decay_rate_.setBounds(214, 28, 140, 26);
+ noise_floor_.setBounds(78, 14, 140, 26);
+ max_db_.setBounds(78, 42, 140, 26);
+ decay_rate_.setBounds(219, 28, 140, 26);
- freeze_.setBounds(12, 33, buttonWidth, 14);
+ freeze_.setBounds(8, 33, buttonWidth, 14);
}
void Oscilloscope::updateDrawing()
diff --git a/src/surge-xt/gui/overlays/Oscilloscope.h b/src/surge-xt/gui/overlays/Oscilloscope.h
index d153b8f420b..6e91d7ad12e 100644
--- a/src/surge-xt/gui/overlays/Oscilloscope.h
+++ b/src/surge-xt/gui/overlays/Oscilloscope.h
@@ -194,6 +194,7 @@ class SpectrumDisplay : public juce::Component, public Surge::GUI::SkinConsuming
class Oscilloscope : public OverlayComponent,
public Surge::GUI::SkinConsumingComponent,
+ public Surge::GUI::IComponentTagValue::Listener,
public Surge::GUI::Hoverable
{
public:
@@ -206,7 +207,38 @@ class Oscilloscope : public OverlayComponent,
void updateDrawing();
void visibilityChanged() override;
+ void valueChanged(GUI::IComponentTagValue *p) override{};
+ int32_t controlModifierClicked(Surge::GUI::IComponentTagValue *pControl,
+ const juce::ModifierKeys &button,
+ bool isDoubleClickEvent) override;
+
private:
+ enum ControlTags
+ {
+ tag_scope_mode = 567898765, // Just to push outside any ID range
+
+ tag_input_l,
+ tag_input_r,
+
+ tag_wf_dc_block,
+ tag_wf_freeze,
+ tag_wf_sync,
+
+ tag_wf_time_scaling,
+ tag_wf_amp_scaling,
+
+ tag_wf_trigger_mode,
+ tag_wf_trigger_level,
+ tag_wf_retrigger_threshold,
+ tag_wf_int_trigger_freq,
+
+ tag_sp_freeze,
+
+ tag_sp_min_level,
+ tag_sp_max_level,
+ tag_sp_decay_rate,
+ };
+
enum ChannelSelect
{
LEFT = 1,
@@ -247,22 +279,36 @@ class Oscilloscope : public OverlayComponent,
WaveformDisplay::Parameters waveform_params_;
};
- class SpectrumParameters : public juce::Component, public Surge::GUI::SkinConsumingComponent
+ class SpectrumParameters : public juce::Component,
+ public Surge::GUI::SkinConsumingComponent,
+ public Surge::GUI::IComponentTagValue::Listener
{
public:
- SpectrumParameters(SurgeGUIEditor *e, SurgeStorage *s, juce::Component *parent);
+ SpectrumParameters(SurgeGUIEditor *e, SurgeStorage *s, Oscilloscope *parent);
std::optional getParamsIfDirty();
void onSkinChanged() override;
void paint(juce::Graphics &g) override;
void resized() override;
+ void valueChanged(GUI::IComponentTagValue *p) override{};
+ int32_t controlModifierClicked(Surge::GUI::IComponentTagValue *pControl,
+ const juce::ModifierKeys &button,
+ bool isDoubleClickEvent) override
+ {
+ if (parent_)
+ {
+ return parent_->controlModifierClicked(pControl, button, isDoubleClickEvent);
+ }
+
+ return 0;
+ }
private:
SurgeGUIEditor *editor_;
SurgeStorage *storage_;
- juce::Component
- *parent_; // Saved here so we can provide it to the children at construction time.
+ // Saved here so we can provide it to the children at construction time.
+ Oscilloscope *parent_;
SpectrumDisplay::Parameters params_;
bool params_changed_;
std::mutex params_lock_;
@@ -273,22 +319,36 @@ class Oscilloscope : public OverlayComponent,
Surge::Widgets::SelfDrawToggleButton freeze_;
};
- class WaveformParameters : public juce::Component, public Surge::GUI::SkinConsumingComponent
+ class WaveformParameters : public juce::Component,
+ public Surge::GUI::SkinConsumingComponent,
+ public Surge::GUI::IComponentTagValue::Listener
{
public:
- WaveformParameters(SurgeGUIEditor *e, SurgeStorage *s, juce::Component *parent);
+ WaveformParameters(SurgeGUIEditor *e, SurgeStorage *s, Oscilloscope *parent);
std::optional getParamsIfDirty();
void onSkinChanged() override;
void paint(juce::Graphics &g) override;
void resized() override;
+ void valueChanged(GUI::IComponentTagValue *p) override{};
+ int32_t controlModifierClicked(Surge::GUI::IComponentTagValue *pControl,
+ const juce::ModifierKeys &button,
+ bool isDoubleClickEvent) override
+ {
+ if (parent_)
+ {
+ return parent_->controlModifierClicked(pControl, button, isDoubleClickEvent);
+ }
+
+ return 0;
+ }
private:
SurgeGUIEditor *editor_;
SurgeStorage *storage_;
- juce::Component
- *parent_; // Saved here so we can provide it to the children at construction time.
+ // Saved here so we can provide it to the children at construction time.
+ Oscilloscope *parent_;
WaveformDisplay::Parameters params_;
bool params_changed_;
std::mutex params_lock_;