-
Notifications
You must be signed in to change notification settings - Fork 455
Expand file tree
/
Copy pathOscilloscope.h
More file actions
428 lines (354 loc) · 14.2 KB
/
Oscilloscope.h
File metadata and controls
428 lines (354 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
/*
* Surge XT - a free and open source hybrid synthesizer,
* built by Surge Synth Team
*
* Learn more at https://surge-synthesizer.github.io/
*
* Copyright 2018-2024, various authors, as described in the GitHub
* transaction log.
*
* Surge XT is released under the GNU General Public Licence v3
* or later (GPL-3.0-or-later). The license is found in the "LICENSE"
* file in the root of this repository, or at
* https://www.gnu.org/licenses/gpl-3.0.en.html
*
* Surge was a commercial product from 2004-2018, copyright and ownership
* held by Claes Johanson at Vember Audio during that period.
* Claes made Surge open source in September 2018.
*
* All source for Surge XT is available at
* https://github.com/surge-synthesizer/surge
*/
#ifndef SURGE_SRC_SURGE_XT_GUI_OVERLAYS_OSCILLOSCOPE_H
#define SURGE_SRC_SURGE_XT_GUI_OVERLAYS_OSCILLOSCOPE_H
#include <array>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
#include "OverlayComponent.h"
#include "SkinSupport.h"
#include "SurgeGUICallbackInterfaces.h"
#include "SurgeGUIEditor.h"
#include "SurgeStorage.h"
#include "widgets/ModulatableSlider.h"
#include "widgets/MultiSwitch.h"
#include "juce_core/juce_core.h"
#include "juce_dsp/juce_dsp.h"
#include "juce_gui_basics/juce_gui_basics.h"
#include "sst/cpputils.h"
namespace Surge
{
namespace Overlays
{
namespace internal
{
constexpr int fftOrder = 13;
constexpr int fftSize = 8192;
// Really wish span was available.
using FftScopeType = std::array<float, fftSize / 2>;
} // namespace internal
// Waveform-specific display taken from s(m)exoscope GPL code and adapted to use with Surge.
class WaveformDisplay : public juce::Component, public Surge::GUI::SkinConsumingComponent
{
public:
enum TriggerType
{
kTriggerFree = 0,
kTriggerRising,
kTriggerFalling,
kTriggerInternal,
kNumTriggerTypes
};
struct Parameters
{
// These default values are set as a defensive measure, but in general
// these are saved and restored (with defaults) from the DAW state.
float trigger_speed = 0.5f; // internal trigger speed, slider
TriggerType trigger_type = kTriggerFree; // trigger type, selection
float trigger_level = 0.5f; // trigger level, slider
float trigger_limit = 0.5f; // retrigger threshold, slider
float time_window = 0.5f; // X-range, slider
float amp_window = 0.5f; // Y-range, slider
bool freeze = false; // freeze display, on/off
bool dc_kill = false; // kill DC, on/off
bool sync_draw = false; // sync redraw, on/off
// Number of pixels per sample, calculated from the time window 0-1 slider value.
float counterSpeed() const;
// Calculate the amplitude "gain" value from the amp window 0-1 slider value.
float gain() const;
// Calculate the final trigger level value from the 0-1 slider value.
float triggerLevel() const;
};
WaveformDisplay(SurgeGUIEditor *e, SurgeStorage *s);
const Parameters &getParameters() const;
void setParameters(Parameters parameters);
void mouseDown(const juce::MouseEvent &event) override;
void paint(juce::Graphics &g) override;
void resized() override;
void process(std::vector<float> data);
private:
SurgeGUIEditor *editor_;
SurgeStorage *storage_;
Parameters params_;
// Global lock for the class.
std::mutex lock_;
std::vector<juce::Point<float>> peaks;
std::vector<juce::Point<float>> copy; // Copy of peaks, for sync drawing.
// Index into the peak-array.
std::size_t index;
// counter which is used to set the amount of samples/pixel.
float counter;
// max/min peak in this block.
float max, min, maxR, minR;
// the last peak we encountered was a maximum?
bool lastIsMax;
// the previous sample (for edge-triggers)
float previousSample;
// the internal trigger oscillator
float triggerPhase;
// trigger limiter
int triggerLimitPhase;
// DC killer.
float dcKill, dcFilterTemp;
// Point that marks where a click happened.
juce::Point<int> clickPoint;
};
// Spectrum-specific display.
class SpectrumDisplay : public juce::Component, public Surge::GUI::SkinConsumingComponent
{
public:
static constexpr float lowFreq = 10;
static constexpr float highFreq = 25000;
struct Parameters
{
// These default values are set as a defensive measure, but in general
// these are saved and restored (with defaults) from the DAW state.
float noise_floor = 0.f; // Noise floor level, bottom of the scope. Min -100. Slider.
float max_db = 1.f; // Maximum dB displayed. Slider. Maxes out at 0. Slider.
float decay_rate = 1.f; // Rate of decay of existing spectrum data. Slider.
bool freeze = false; // Freeze display, on/off.
// Range of decibels shown in the display, calculated from slider values.
float dbRange() const;
// Calculate the noise floor in decibels from the slider value (noise_floor).
float noiseFloor() const;
// Calculate the maximum decibels shown from the slider value (max_db).
float maxDb() const;
};
SpectrumDisplay(SurgeGUIEditor *e, SurgeStorage *s);
const Parameters &getParameters() const;
void setParameters(Parameters parameters);
void paint(juce::Graphics &g) override;
void resized() override;
void updateScopeData(internal::FftScopeType::iterator begin,
internal::FftScopeType::iterator end);
private:
float interpolate(const float y0, const float y1,
std::chrono::time_point<std::chrono::steady_clock> t) const;
// data_lock_ *must* be held by the caller.
void recalculateScopeData();
SurgeGUIEditor *editor_;
SurgeStorage *storage_;
Parameters params_;
std::chrono::duration<float> mtbs_;
std::chrono::time_point<std::chrono::steady_clock> last_updated_time_;
std::mutex data_lock_;
internal::FftScopeType new_scope_data_;
internal::FftScopeType displayed_data_;
// Why a third array? We calculate into the other two, and if the parameters
// change we have to update our calculations from the beginning.
internal::FftScopeType incoming_scope_data_;
bool display_dirty_;
};
class Oscilloscope : public OverlayComponent,
public Surge::GUI::SkinConsumingComponent,
public Surge::GUI::IComponentTagValue::Listener,
public Surge::GUI::Hoverable
{
public:
Oscilloscope(SurgeGUIEditor *e, SurgeStorage *s);
virtual ~Oscilloscope();
void onSkinChanged() override;
void paint(juce::Graphics &g) override;
void resized() override;
void updateDrawing();
void visibilityChanged() override;
bool wantsInitialKeyboardFocus() const override { return false; }
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,
RIGHT = 2,
STEREO = 3,
OFF = 4,
};
enum ScopeMode
{
WAVEFORM = 0,
SPECTRUM = 1,
};
// Child component for handling the drawing of the background. Done as a separate child instead
// of in the Oscilloscope class so the display, which is repainting at 20-30 hz, doesn't mark
// this as dirty as it repaints. It sucks up a ton of CPU otherwise.
class Background : public juce::Component, public Surge::GUI::SkinConsumingComponent
{
public:
explicit Background(SurgeStorage *s);
void paint(juce::Graphics &g) override;
void updateBackgroundType(ScopeMode mode);
void updateBounds(juce::Rectangle<int> local_bounds, juce::Rectangle<int> scope_bounds);
void updateParameters(SpectrumDisplay::Parameters params);
void updateParameters(WaveformDisplay::Parameters params);
private:
void paintSpectrumBackground(juce::Graphics &g);
void paintWaveformBackground(juce::Graphics &g);
// No lock on these because they can only get updated during the same thread as the one
// doing the painting.
SurgeStorage *storage_;
ScopeMode mode_;
juce::Rectangle<int> scope_bounds_;
SpectrumDisplay::Parameters spectrum_params_;
WaveformDisplay::Parameters waveform_params_;
};
class SpectrumParameters : public juce::Component,
public Surge::GUI::SkinConsumingComponent,
public Surge::GUI::IComponentTagValue::Listener
{
public:
SpectrumParameters(SurgeGUIEditor *e, SurgeStorage *s, Oscilloscope *parent);
std::optional<SpectrumDisplay::Parameters> 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_;
// 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_;
Surge::Widgets::SelfUpdatingModulatableSlider noise_floor_;
Surge::Widgets::SelfUpdatingModulatableSlider max_db_;
Surge::Widgets::SelfUpdatingModulatableSlider decay_rate_;
Surge::Widgets::SelfDrawToggleButton freeze_;
};
class WaveformParameters : public juce::Component,
public Surge::GUI::SkinConsumingComponent,
public Surge::GUI::IComponentTagValue::Listener
{
public:
WaveformParameters(SurgeGUIEditor *e, SurgeStorage *s, Oscilloscope *parent);
std::optional<WaveformDisplay::Parameters> 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_;
// 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_;
Surge::Widgets::SelfUpdatingModulatableSlider trigger_speed_;
Surge::Widgets::SelfUpdatingModulatableSlider trigger_level_;
Surge::Widgets::SelfUpdatingModulatableSlider trigger_limit_;
Surge::Widgets::SelfUpdatingModulatableSlider time_window_;
Surge::Widgets::SelfUpdatingModulatableSlider amp_window_;
Surge::Widgets::ClosedMultiSwitchSelfDraw trigger_type_;
Surge::Widgets::SelfDrawToggleButton freeze_;
Surge::Widgets::SelfDrawToggleButton dc_kill_;
Surge::Widgets::SelfDrawToggleButton sync_draw_;
};
// Child component for handling the switch between waveform/spectrum.
class SwitchButton : public Surge::Widgets::MultiSwitchSelfDraw,
public Surge::GUI::IComponentTagValue::Listener
{
public:
explicit SwitchButton(Oscilloscope &parent);
void valueChanged(Surge::GUI::IComponentTagValue *p) override;
private:
Oscilloscope &parent_;
};
// Height of parameter window, in pixels.
static constexpr const int paramsHeight = 80;
void calculateSpectrumData();
void changeScopeType(ScopeMode type);
juce::Rectangle<int> getScopeRect();
void pullData();
void toggleChannel();
SurgeGUIEditor *editor_{nullptr};
SurgeStorage *storage_{nullptr};
juce::dsp::FFT forward_fft_;
juce::dsp::WindowingFunction<float> window_;
std::array<float, 2 * internal::fftSize> fft_data_;
int pos_;
internal::FftScopeType scope_data_;
ChannelSelect channel_selection_;
ScopeMode scope_mode_;
// Global lock for all data members accessed concurrently.
std::mutex data_lock_;
// Members for the data-pulling thread.
std::thread fft_thread_;
std::atomic_bool complete_;
std::condition_variable channels_off_;
// Visual elements.
Surge::Widgets::SelfDrawToggleButton left_chan_button_;
Surge::Widgets::SelfDrawToggleButton right_chan_button_;
SwitchButton scope_mode_button_;
Background background_;
SpectrumDisplay spectrum_;
WaveformDisplay waveform_;
SpectrumParameters spectrum_parameters_;
WaveformParameters waveform_parameters_;
};
} // namespace Overlays
} // namespace Surge
#endif // SURGE_SRC_SURGE_XT_GUI_OVERLAYS_OSCILLOSCOPE_H