1 /*
2 ** Surge Synthesizer is Free and Open Source Software
3 **
4 ** Surge is made available under the Gnu General Public License, v3.0
5 ** https://www.gnu.org/licenses/gpl-3.0.en.html
6 **
7 ** Copyright 2004-2020 by various individuals as described by the Git transaction log
8 **
9 ** All source at: https://github.com/surge-synthesizer/surge.git
10 **
11 ** Surge was a commercial product from 2004-2018, with Copyright and ownership
12 ** in that period held by Claes Johanson at Vember Audio. Claes made Surge
13 ** open source in September 2018.
14 */
15 
16 #include "SkinModel.h"
17 #include "resource.h"
18 
19 using Surge::Skin::Connector;
20 using namespace Surge::Skin::Components;
21 
22 /*
23  * This file, in conjunction with the SVG assets it references by ID, is a complete
24  * description of the surge default UI layout. The action callbacks bind to it and
25  * the engine to lay it out are the collaboration betweeh this, SkinSupport, and SurgeGUIEditor
26  */
27 
28 namespace Surge
29 {
30 namespace Skin
31 {
32 
33 namespace Components
34 {
35 Component None = Component("UNKNOWN");
36 
37 Component HSwitch2 =
38     Component("CHSwitch2")
39         .withProperty(Component::ROWS, {"rows"}, {"Number of rows in the switch"})
40         .withProperty(Component::COLUMNS, {"cols", "columns"}, {"Number of columns in the switch"})
41         .withProperty(
42             Component::FRAMES, {"frames"},
43             {"Number of frames in the graphical asset for the switch (based on rows and columns)"})
44         .withProperty(Component::FRAME_OFFSET, {"frame_offset"},
45                       {"Used in case when a graphical asset for the switch has multiple variations "
46                        "to reach to, valid values are from 0 to 'frames'"})
47         .withProperty(Component::BACKGROUND, {"image", "bg_resource", "bg_id"},
48                       {"Base image of the switch"})
49         .withProperty(Component::DRAGGABLE_HSWITCH, {"draggable"},
50                       {"Is the switch draggable as a slider via mouse/touch or not. Valid values: "
51                        "true, false"})
52         .withProperty(Component::HOVER_IMAGE, {"hover_image"},
53                       {"Hover image of the switch - required if you "
54                        "set the base image and want feedback on mouse hover"})
55         .withProperty(Component::HOVER_ON_IMAGE, {"hover_on_image"},
56                       {"Hover image of the switch used when mouse hovers over the currently "
57                        "selected value on the switch"});
58 
59 Component Slider =
60     Component("CSurgeSlider")
61         .withProperty(Component::SLIDER_TRAY, {"slider_tray", "handle_tray"},
62                       {"Background image of the slider, contains the slider tray/groove only"})
63         .withProperty(Component::HANDLE_IMAGE, {"handle_image"}, {"Slider handle image"})
64         .withProperty(
65             Component::HANDLE_HOVER_IMAGE, {"handle_hover_image"},
66             {"Hovered slider handle image - required if you want feedback on mouse hover"})
67         .withProperty(Component::HANDLE_TEMPOSYNC_IMAGE, {"handle_temposync_image"},
68                       {"Slider handle image when parameter is tempo-synced"})
69         .withProperty(Component::HANDLE_TEMPOSYNC_HOVER_IMAGE, {"handle_temposync_hover_image"},
70                       {"Hovered slider handle image when parameter is tempo-synced - required if "
71                        "you want feedback on mouse hover"})
72         .withProperty(Component::FONT_SIZE, {"font_size"}, {"Font size in points, integer only"})
73         .withProperty(Component::FONT_STYLE, {"font_style"},
74                       {"Valid values: normal, bold, italic, underline, strikethrough"})
75         .withProperty(Component::TEXT_ALIGN, {"text_align"}, {"Valid values: left, center, right"})
76         .withProperty(Component::TEXT_HOFFSET, {"text_hoffset"},
77                       {"Horizontal offset of parameter name label, integer only"})
78         .withProperty(Component::TEXT_VOFFSET, {"text_voffset"},
79                       {"Vertical offset of parameter name label, integer only"})
80         .withProperty(Component::HIDE_SLIDER_LABEL, {"hide_slider_label"},
81                       {"Hides the parameter name label"});
82 
83 Component Switch = Component("CSwitchControl")
84                        .withProperty(Component::BACKGROUND, {"image", "bg_resource", "bg_id"},
85                                      {"Base image of the switch"});
86 
87 Component FilterSelector =
88     Component("FilterSelector")
89         .withProperty(Component::BACKGROUND, {"image"},
90                       {"Base image of the filter type menu itself; it is offset by the glyph if "
91                        "glyph is active"})
92         .withProperty(Component::HOVER_IMAGE, {"hover_image"},
93                       {"Hover image of the filter type menu - required if you "
94                        "set the base image and want feedback on mouse hover"})
95         .withProperty(Component::GLYPH_IMAGE, {"glyph_image"},
96                       {"Resource name of the glyph (filter type icons) image"})
97         .withProperty(
98             Component::GLYPH_HOVER_IMAGE, {"glyph_hover_image"},
99             {"Resource name of the glyph (filter type icons) image that shows on mouse hover"})
100         .withProperty(Component::GLPYH_ACTIVE, {"glyph_active"},
101                       {"Show or hide the filter type icons glyph. Valid values: true, false"})
102         .withProperty(Component::GLYPH_PLACEMENT, {"glyph_placement"},
103                       {"Valid values: above, below, left, right"})
104         .withProperty(Component::GLYPH_W, {"glyph_w"}, {"Width of one glyph frame in pixels"})
105         .withProperty(Component::GLYPH_H, {"glyph_h"}, {"Height of one glyph frame in pixels"});
106 
107 Component LFODisplay = Component("CLFOGui");
108 
109 Component OscMenu =
110     Component("COSCMenu")
111         .withProperty(Component::FONT_SIZE, {"font_size"}, {"Font size in points, integer only"})
112         .withProperty(Component::FONT_STYLE, {"font_style"},
113                       {"Valid values: normal, bold, italic, underline, strikethrough"})
114         .withProperty(Component::TEXT_ALIGN, {"text_align"}, {"Valid values: left, center, right"})
115         .withProperty(Component::TEXT_ALL_CAPS, {"text_allcaps"},
116                       {"Makes the oscillator menu text all caps. Valid values: true, false"})
117         .withProperty(Component::TEXT_HOFFSET, {"text_hoffset"},
118                       {"Horizontal offset of oscillator menu text label, integer only"})
119         .withProperty(Component::TEXT_VOFFSET, {"text_voffset"},
120                       {"Vertical offset of oscillator menu text label, integer only"});
121 
122 Component FxMenu = Component("CFXMenu");
123 
124 Component NumberField =
125     Component("CNumberField")
126         .withProperty(Component::BACKGROUND, {"image", "bg_resource", "bg_id"},
127                       {"Base image of the numberfield"})
128         .withProperty(Component::NUMBERFIELD_CONTROLMODE, {"numberfield_controlmode"},
129                       {"Control mode of the numberfield. Refer to the list of ctrl_mode constants "
130                        "in SurgeParamConfig.h"})
131         .withProperty(Component::TEXT_COLOR, {"text_color"},
132                       {"Valid color formats are #RRGGBB, #RRGGBBAA, and named colors"})
133         .withProperty(Component::TEXT_HOVER_COLOR, {"text_color.hover"},
134                       {"Valid color formats are #RRGGBB, #RRGGBBAA, and named colors"});
135 
136 Component VuMeter = Component("CVuMeter");
137 
138 Component Custom = Component("--CUSTOM--");
139 
140 Component Group = Component("--GROUP--");
141 
142 /*
143  * We could treat label like everything else but we didn't in S1.7/1.8 and Skin V1
144  * so we are stuck with this slight special casing
145  */
146 Component Label =
147     Component("Internal Label")
148         .withProperty(Component::TEXT, {"text"}, {"Arbitrary text to be displayed on the label."})
149         .withProperty(Component::CONTROL_TEXT, {"control_text"},
150                       {"Text tied to a particular Surge parameter. Use skin component connector "
151                        "name as a value. Overrules arbitrary text set with 'text' property"})
152         .withProperty(Component::TEXT_ALIGN, {"text_align"}, {"Valid values: left, center, right"})
153         .withProperty(Component::FONT_SIZE, {"font_size"}, {"Font size in points, integer only"})
154         .withProperty(Component::FONT_STYLE, {"font_style"},
155                       {"Valid values: normal, bold, italic, underline, strikethrough"})
156         .withProperty(Component::TEXT_COLOR, {"color", "text_color"},
157                       {"Valid color formats are #RRGGBB, #RRGGBBAA, and named colors"})
158         .withProperty(Component::BACKGROUND_COLOR, {"bg_color"},
159                       {"Background color of the label. Valid color formats are #RRGGBB, "
160                        "#RRGGBBAA, or named colors"})
161         .withProperty(Component::FRAME_COLOR, {"frame_color"},
162                       {"Color of the label frame/border. Valid color formats are #RRGGBB, "
163                        "#RRGGBBAA, and named colors"})
164         .withProperty(Component::IMAGE, {"image"},
165                       {"Resource name of the image to be displayed by the label. Overrides "
166                        "background and frame/border colors"});
167 
168 } // namespace Components
169 
170 namespace Global
171 {
172 Connector active_scene = Connector("global.active_scene", 7, 12, 40, 42, Components::HSwitch2)
173                              .withHSwitch2Properties(IDB_SCENE_SELECT, 2, 2, 1);
174 Connector scene_mode = Connector("global.scene_mode", 54, 12, 40, 42, Components::HSwitch2)
175                            .withHSwitch2Properties(IDB_SCENE_MODE, 4, 4, 1);
176 Connector fx_disable = Connector("global.fx_disable", 0, 0);
177 Connector fx_bypass = Connector("global.fx_bypass", 607, 12, 135, 27, Components::HSwitch2)
178                           .withHSwitch2Properties(IDB_FX_GLOBAL_BYPASS, 4, 1, 4);
179 Connector character = Connector("global.character", 607, 42, 135, 12, Components::HSwitch2)
180                           .withHSwitch2Properties(IDB_OSC_CHARACTER, 3, 1, 3);
181 Connector master_volume = Connector("global.volume", 756, 29);
182 
183 Connector fx1_return = Connector("global.fx1_return", 759, 141);
184 Connector fx2_return = Connector("global.fx2_return", 759, 162);
185 } // namespace Global
186 
187 namespace Scene
188 {
189 Connector polylimit = Connector("scene.polylimit", 100, 41, 43, 14, Components::NumberField)
190                           .withBackground(IDB_NUMFIELD_POLY_SPLIT)
191                           .withProperty(Component::NUMBERFIELD_CONTROLMODE, cm_polyphony)
192                           .withProperty(Component::TEXT_COLOR, "scene.split_poly.text")
193                           .withProperty(Component::TEXT_HOVER_COLOR, "scene.split_poly.text.hover");
194 Connector splitpoint =
195     Connector("scene.splitpoint", 100, 11, 43, 14, Components::NumberField)
196         .withBackground(IDB_NUMFIELD_POLY_SPLIT)
197         .withProperty(Component::TEXT_COLOR, "scene.split_poly.text")
198         .withProperty(Component::TEXT_HOVER_COLOR, "scene.split_poly.text.hover");
199 // this doesn't have a cm since it is special
200 
201 Connector pbrange_dn = Connector("scene.pbrange_dn", 159, 110, 30, 13, Components::NumberField)
202                            .withProperty(Component::NUMBERFIELD_CONTROLMODE, cm_pbdepth)
203                            .withBackground(IDB_NUMFIELD_PITCHBEND)
204                            .withProperty(Component::TEXT_COLOR, "scene.pbrange.text")
205                            .withProperty(Component::TEXT_HOVER_COLOR, "scene.pbrange.text.hover");
206 Connector pbrange_up = Connector("scene.pbrange_up", 187, 110, 30, 13, Components::NumberField)
207                            .withProperty(Component::NUMBERFIELD_CONTROLMODE, cm_pbdepth)
208                            .withBackground(IDB_NUMFIELD_PITCHBEND)
209                            .withProperty(Component::TEXT_COLOR, "scene.pbrange.text")
210                            .withProperty(Component::TEXT_HOVER_COLOR, "scene.pbrange.text.hover");
211 Connector playmode = Connector("scene.playmode", 239, 87, 50, 47, Components::HSwitch2)
212                          .withHSwitch2Properties(IDB_PLAY_MODE, 6, 6, 1);
213 Connector drift = Connector("scene.drift", 156, 141).asHorizontal().asWhite();
214 Connector noise_color = Connector("scene.noise_color", 156, 162).asHorizontal().asWhite();
215 
216 Connector fmrouting = Connector("scene.fmrouting", 309, 83, 134, 52, Components::HSwitch2)
217                           .withHSwitch2Properties(IDB_OSC_FM_ROUTING, 4, 1, 4);
218 Connector fmdepth = Connector("scene.fmdepth", 306, 152).asHorizontal().asWhite();
219 
220 Connector octave = Connector("scene.octave", 202, 194, 96, 18, Components::HSwitch2)
221                        .withHSwitch2Properties(IDB_SCENE_OCTAVE, 7, 1, 7);
222 Connector pitch = Connector("scene.pitch", 156, 212).asHorizontal();
223 Connector portatime = Connector("scene.portamento", 156, 234).asHorizontal();
224 
225 Connector keytrack_root =
226     Connector("scene.keytrack_root", 311, 266, 43, 14, Components::NumberField)
227         .withBackground(IDB_NUMFIELD_KEYTRACK_ROOT)
228         .withProperty(Component::NUMBERFIELD_CONTROLMODE, cm_notename)
229         .withProperty(Component::TEXT_COLOR, "scene.keytrackroot.text")
230         .withProperty(Component::TEXT_HOVER_COLOR, "scene.keytrackroot.text.hover");
231 
232 Connector scene_output_panel = Connector("scene.output.panel", 606, 78, Components::Group);
233 Connector volume =
234     Connector("scene.volume", 0, 0).asHorizontal().asWhite().inParent("scene.output.panel");
235 Connector pan =
236     Connector("scene.pan", 0, 21).asHorizontal().asWhite().inParent("scene.output.panel");
237 Connector width =
238     Connector("scene.width", 0, 42).asHorizontal().asWhite().inParent("scene.output.panel");
239 Connector send_fx_1 =
240     Connector("scene.send_fx_1", 0, 63).asHorizontal().asWhite().inParent("scene.output.panel");
241 Connector send_fx_2 =
242     Connector("scene.send_fx_2", 0, 84).asHorizontal().asWhite().inParent("scene.output.panel");
243 
244 Connector vel_sensitivity =
245     Connector("scene.velocity_sensitivity", 699, 301).asVertical().asWhite();
246 Connector gain = Connector("scene.gain", 719, 301).asVertical().asWhite();
247 } // namespace Scene
248 
249 namespace Osc
250 {
251 Connector osc_select =
252     Connector("osc.select", 66, 69, 75, 13, Components::HSwitch2, Connector::OSCILLATOR_SELECT)
253         .withHSwitch2Properties(IDB_OSC_SELECT, 3, 1, 3);
254 Connector osc_display =
255     Connector("osc.display", 4, 81, 141, 99, Components::Custom, Connector::OSCILLATOR_DISPLAY);
256 
257 Connector keytrack =
258     Connector("osc.keytrack", 4, 180, 45, 9, Components::Switch).withBackground(IDB_OSC_KEYTRACK);
259 Connector retrigger = Connector("osc.retrigger", 51, 180, 45, 9, Components::Switch)
260                           .withBackground(IDB_OSC_RETRIGGER);
261 
262 Connector octave = Connector("osc.octave", 0, 194, 96, 18, Components::HSwitch2)
263                        .withHSwitch2Properties(IDB_OSC_OCTAVE, 7, 1, 7);
264 Connector osc_type = Connector("osc.type", 96, 194, 49, 18, Components::OscMenu)
265                          .withProperty(Component::FONT_SIZE, 8)
266                          .withProperty(Component::FONT_STYLE, "bold")
267                          .withProperty(Component::TEXT_ALL_CAPS, "true")
268                          .withProperty(Component::TEXT_ALIGN, "center")
269                          .withProperty(Component::TEXT_HOFFSET, -2)
270                          .withProperty(Component::TEXT_VOFFSET, -1);
271 
272 Connector osc_param_panel = Connector("osc.param.panel", 6, 212, Components::Group);
273 Connector pitch = Connector("osc.pitch", 0, 0).asHorizontal().inParent("osc.param.panel");
274 Connector param_1 = Connector("osc.param_1", 0, 22).asHorizontal().inParent("osc.param.panel");
275 Connector param_2 = Connector("osc.param_2", 0, 44).asHorizontal().inParent("osc.param.panel");
276 Connector param_3 = Connector("osc.param_3", 0, 66).asHorizontal().inParent("osc.param.panel");
277 Connector param_4 = Connector("osc.param_4", 0, 88).asHorizontal().inParent("osc.param.panel");
278 Connector param_5 = Connector("osc.param_5", 0, 110).asHorizontal().inParent("osc.param.panel");
279 Connector param_6 = Connector("osc.param_6", 0, 132).asHorizontal().inParent("osc.param.panel");
280 Connector param_7 = Connector("osc.param_7", 0, 154).asHorizontal().inParent("osc.param.panel");
281 } // namespace Osc
282 
283 namespace Mixer
284 {
285 Connector mixer_panel = Connector("mixer.panel", 154, 263, Components::Group);
286 Connector mute_o1 = Connector("mixer.mute_o1", 1, 0).asMixerMute().inParent("mixer.panel");
287 Connector mute_o2 = Connector("mixer.mute_o2", 21, 0).asMixerMute().inParent("mixer.panel");
288 Connector mute_o3 = Connector("mixer.mute_o3", 41, 0).asMixerMute().inParent("mixer.panel");
289 Connector mute_ring12 = Connector("mixer.mute_ring12", 61, 0).asMixerMute().inParent("mixer.panel");
290 Connector mute_ring23 = Connector("mixer.mute_ring23", 81, 0).asMixerMute().inParent("mixer.panel");
291 Connector mute_noise = Connector("mixer.mute_noise", 101, 0).asMixerMute().inParent("mixer.panel");
292 
293 Connector solo_o1 = Connector("mixer.solo_o1", 1, 11).asMixerSolo().inParent("mixer.panel");
294 Connector solo_o2 = Connector("mixer.solo_o2", 21, 11).asMixerSolo().inParent("mixer.panel");
295 Connector solo_o3 = Connector("mixer.solo_o3", 41, 11).asMixerSolo().inParent("mixer.panel");
296 Connector solo_ring12 =
297     Connector("mixer.solo_ring12", 61, 11).asMixerSolo().inParent("mixer.panel");
298 Connector solo_ring23 =
299     Connector("mixer.solo_ring23", 81, 11).asMixerSolo().inParent("mixer.panel");
300 Connector solo_noise = Connector("mixer.solo_noise", 101, 11).asMixerSolo().inParent("mixer.panel");
301 
302 Connector route_o1 = Connector("mixer.route_o1", 1, 22).asMixerRoute().inParent("mixer.panel");
303 Connector route_o2 = Connector("mixer.route_o2", 21, 22).asMixerRoute().inParent("mixer.panel");
304 Connector route_o3 = Connector("mixer.route_o3", 41, 22).asMixerRoute().inParent("mixer.panel");
305 Connector route_ring12 =
306     Connector("mixer.route_ring12", 61, 22).asMixerRoute().inParent("mixer.panel");
307 Connector route_ring23 =
308     Connector("mixer.route_ring23", 81, 22).asMixerRoute().inParent("mixer.panel");
309 Connector route_noise =
310     Connector("mixer.route_noise", 101, 22).asMixerRoute().inParent("mixer.panel");
311 
312 Connector level_o1 =
313     Connector("mixer.level_o1", 0, 38).asVertical().asWhite().inParent("mixer.panel");
314 Connector level_o2 =
315     Connector("mixer.level_o2", 20, 38).asVertical().asWhite().inParent("mixer.panel");
316 Connector level_o3 =
317     Connector("mixer.level_o3", 40, 38).asVertical().asWhite().inParent("mixer.panel");
318 Connector level_ring12 =
319     Connector("mixer.level_ring12", 60, 38).asVertical().asWhite().inParent("mixer.panel");
320 Connector level_ring23 =
321     Connector("mixer.level_ring23", 80, 38).asVertical().asWhite().inParent("mixer.panel");
322 Connector level_noise =
323     Connector("mixer.level_noise", 100, 38).asVertical().asWhite().inParent("mixer.panel");
324 
325 Connector level_prefiltergain =
326     Connector("mixer.level_prefiltergain", 120, 37).asVertical().asWhite().inParent("mixer.panel");
327 } // namespace Mixer
328 
329 namespace Filter
330 {
331 Connector config = Connector("filter.config", 455, 83, 134, 52, Components::HSwitch2)
332                        .withHSwitch2Properties(IDB_FILTER_CONFIG, 8, 1, 8);
333 Connector feedback = Connector("filter.feedback", 457, 152).asHorizontal().asWhite();
334 
335 // FIXME - we should really expose the menu slider fully but for now make a composite FILTERSELECTOR
336 Connector type_1 = Connector("filter.type_1", 305, 191, 124, 21, Components::FilterSelector);
337 Connector subtype_1 = Connector("filter.subtype_1", 432, 194, 12, 18, Components::Switch)
338                           .withBackground(IDB_FILTER_SUBTYPE);
339 Connector cutoff_1 = Connector("filter.cutoff_1", 306, 212).asHorizontal();
340 Connector resonance_1 = Connector("filter.resonance_1", 306, 234).asHorizontal();
341 
342 Connector balance = Connector("filter.balance", 456, 224).asHorizontal();
343 
344 Connector type_2 = Connector("filter.type_2", 605, 191, 124, 21, Components::FilterSelector);
345 Connector subtype_2 = Connector("filter.subtype_2", 732, 194, 12, 18, Components::Switch)
346                           .withBackground(IDB_FILTER_SUBTYPE);
347 Connector cutoff_2 = Connector("filter.cutoff_2", 600, 212).asHorizontal();
348 Connector resonance_2 = Connector("filter.resonance_2", 600, 234).asHorizontal();
349 
350 Connector f2_offset_mode = Connector("filter.f2_offset_mode", 734, 217, 12, 18, Components::Switch)
351                                .withBackground(IDB_FILTER2_OFFSET);
352 Connector f2_link_resonance =
353     Connector("filter.f2_link_resonance", 734, 239, 12, 18, Components::Switch)
354         .withBackground(IDB_FILTER2_RESONANCE_LINK);
355 
356 Connector keytrack_1 = Connector("filter.keytrack_1", 309, 301).asVertical().asWhite();
357 Connector keytrack_2 = Connector("filter.keytrack_2", 329, 301).asVertical().asWhite();
358 
359 Connector envmod_1 = Connector("filter.envmod_1", 549, 301).asVertical().asWhite();
360 Connector envmod_2 = Connector("filter.envmod_2", 569, 301).asVertical().asWhite();
361 
362 Connector waveshaper_drive = Connector("filter.waveshaper_drive", 419, 301).asVertical().asWhite();
363 Connector waveshaper_type =
364     Connector("filter.waveshaper_type", 388, 311, 28, 51, Components::HSwitch2)
365         .withHSwitch2Properties(IDB_WAVESHAPER_MODE, 6, 6, 1);
366 
367 Connector highpass = Connector("filter.highpass", 354, 301).asVertical().asWhite();
368 } // namespace Filter
369 
370 namespace FEG
371 {
372 // If we never refer to it by type we can just construct it and don't need it in extern list
373 Connector feg_panel = Connector("feg.panel", 459, 267, Components::Group);
374 Connector mode = Connector("feg.mode", 93, 0, 34, 18, Components::HSwitch2)
375                      .withHSwitch2Properties(IDB_ENV_MODE, 2, 2, 1)
376                      .inParent("feg.panel");
377 
378 Connector attack_shape = Connector("feg.attack_shape", 4, 1, 20, 16, Components::HSwitch2)
379                              .withHSwitch2Properties(IDB_ENV_SHAPE, 3, 1, 3)
380                              .inParent("feg.panel");
381 Connector decay_shape = Connector("feg.decay_shape", 24, 1, 20, 16, Components::HSwitch2)
382                             .withHSwitch2Properties(IDB_ENV_SHAPE, 3, 1, 3)
383                             .withProperty(Component::FRAME_OFFSET, 3)
384                             .inParent("feg.panel");
385 Connector release_shape = Connector("feg.release_shape", 65, 1, 20, 16, Components::HSwitch2)
386                               .withHSwitch2Properties(IDB_ENV_SHAPE, 3, 1, 3)
387                               .withProperty(Component::FRAME_OFFSET, 6)
388                               .inParent("feg.panel");
389 
390 Connector attack = Connector("feg.attack", 0, 34).asVertical().asWhite().inParent("feg.panel");
391 Connector decay = Connector("feg.decay", 20, 34).asVertical().asWhite().inParent("feg.panel");
392 Connector sustain = Connector("feg.sustain", 40, 34).asVertical().asWhite().inParent("feg.panel");
393 Connector release = Connector("feg.release", 60, 34).asVertical().asWhite().inParent("feg.panel");
394 } // namespace FEG
395 
396 namespace AEG
397 {
398 Connector aeg_panel = Connector("aeg.panel", 609, 267, Components::Group);
399 Connector mode = Connector("aeg.mode", 93, 0, 34, 18, Components::HSwitch2)
400                      .withHSwitch2Properties(IDB_ENV_MODE, 2, 2, 1)
401                      .inParent("aeg.panel");
402 
403 Connector attack_shape = Connector("aeg.attack_shape", 4, 1, 20, 16, Components::HSwitch2)
404                              .withHSwitch2Properties(IDB_ENV_SHAPE, 3, 1, 3)
405                              .inParent("aeg.panel");
406 ;
407 Connector decay_shape = Connector("aeg.decay_shape", 24, 1, 20, 16, Components::HSwitch2)
408                             .withHSwitch2Properties(IDB_ENV_SHAPE, 3, 1, 3)
409                             .withProperty(Component::FRAME_OFFSET, 3)
410                             .inParent("aeg.panel");
411 ;
412 Connector release_shape = Connector("aeg.release_shape", 65, 1, 20, 16, Components::HSwitch2)
413                               .withHSwitch2Properties(IDB_ENV_SHAPE, 3, 1, 3)
414                               .withProperty(Component::FRAME_OFFSET, 6)
415                               .inParent("aeg.panel");
416 ;
417 
418 Connector attack = Connector("aeg.attack", 0, 34).asVertical().asWhite().inParent("aeg.panel");
419 ;
420 Connector decay = Connector("aeg.decay", 20, 34).asVertical().asWhite().inParent("aeg.panel");
421 ;
422 Connector sustain = Connector("aeg.sustain", 40, 34).asVertical().asWhite().inParent("aeg.panel");
423 ;
424 Connector release = Connector("aeg.release", 60, 34).asVertical().asWhite().inParent("aeg.panel");
425 ;
426 } // namespace AEG
427 
428 namespace FX
429 {
430 Connector fx_selector =
431     Connector("fx.selector", 766, 78, 120, 53, Components::Custom, Connector::FX_SELECTOR);
432 Connector fx_type = Connector("fx.type", 761, 194, 133, 18, Components::FxMenu);
433 
434 Connector fx_preset =
435     Connector("fx.preset.name", 762, 212, 95, 12, Components::Custom, Connector::FXPRESET_LABEL);
436 Connector fx_jog = Connector("fx.preset.prevnext", 860, 212, Connector::JOG_FX).asJogPlusMinus();
437 
438 Connector fx_param_panel = Connector("fx.param.panel", 759, 224, Components::Group);
439 Connector param_1 = Connector("fx.param_1", 0, 0).inParent("fx.param.panel");
440 Connector param_2 = Connector("fx.param_2", 0, 20).inParent("fx.param.panel");
441 Connector param_3 = Connector("fx.param_3", 0, 40).inParent("fx.param.panel");
442 Connector param_4 = Connector("fx.param_4", 0, 60).inParent("fx.param.panel");
443 Connector param_5 = Connector("fx.param_5", 0, 80).inParent("fx.param.panel");
444 Connector param_6 = Connector("fx.param_6", 0, 100).inParent("fx.param.panel");
445 Connector param_7 = Connector("fx.param_7", 0, 120).inParent("fx.param.panel");
446 Connector param_8 = Connector("fx.param_8", 0, 140).inParent("fx.param.panel");
447 Connector param_9 = Connector("fx.param_9", 0, 160).inParent("fx.param.panel");
448 Connector param_10 = Connector("fx.param_10", 0, 180).inParent("fx.param.panel");
449 Connector param_11 = Connector("fx.param_11", 0, 200).inParent("fx.param.panel");
450 Connector param_12 = Connector("fx.param_12", 0, 220).inParent("fx.param.panel");
451 } // namespace FX
452 
453 namespace LFO
454 {
455 // For now these two have component 'CUSTOM' and we hadn't pick a component in the code
456 Connector lfo_title_label =
457     Connector("lfo.title", 6, 489, 11, 83, Components::Custom, Connector::LFO_LABEL);
458 Connector lfo_presets =
459     Connector("lfo.presets", 6, 484, 13, 11, Components::Switch, Connector::LFO_MENU)
460         .withBackground(IDB_LFO_PRESET_MENU);
461 
462 Connector lfo_main_panel = Connector("lfo.main.panel", 28, 478, Components::Group);
463 Connector rate = Connector("lfo.rate", 0, 0).asHorizontal().inParent("lfo.main.panel");
464 Connector phase = Connector("lfo.phase", 0, 21).asHorizontal().inParent("lfo.main.panel");
465 Connector deform = Connector("lfo.deform", 0, 42).asHorizontal().inParent("lfo.main.panel");
466 Connector amplitude = Connector("lfo.amplitude", 0, 63).asHorizontal().inParent("lfo.main.panel");
467 
468 Connector trigger_mode = Connector("lfo.trigger_mode", 172, 484, 51, 39, Components::HSwitch2)
469                              .withHSwitch2Properties(IDB_LFO_TRIGGER_MODE, 3, 3, 1);
470 Connector unipolar = Connector("lfo.unipolar", 172, 546, 51, 14, Components::Switch)
471                          .withBackground(IDB_LFO_UNIPOLAR);
472 
473 // combined LFO shape AND LFO display - TODO: split them to individual connectors for 1.9!
474 Connector shape = Connector("lfo.shape", 235, 480, 359, 84, Components::LFODisplay);
475 
476 Connector mseg_editor =
477     Connector("lfo.mseg_editor", 597, 484, 11, 11, Components::Switch, Connector::MSEG_EDITOR_OPEN)
478         .withBackground(IDB_LFO_MSEG_EDIT);
479 
480 Connector lfo_eg_panel = Connector("lfo.envelope.panel", 616, 493, Components::Group);
481 Connector delay = Connector("lfo.delay", 0, 0).inParent("lfo.envelope.panel");
482 Connector attack = Connector("lfo.attack", 20, 0).inParent("lfo.envelope.panel");
483 Connector hold = Connector("lfo.hold", 40, 0).inParent("lfo.envelope.panel");
484 Connector decay = Connector("lfo.decay", 60, 0).inParent("lfo.envelope.panel");
485 Connector sustain = Connector("lfo.sustain", 80, 0).inParent("lfo.envelope.panel");
486 Connector release = Connector("lfo.release", 100, 0).inParent("lfo.envelope.panel");
487 } // namespace LFO
488 
489 /*
490  * We have a collection of controls which don't bind to parameters but which instead
491  * have a non-parameter type. These attach to SurgeGUIEditor by virtue of having the
492  * non-parameter type indicating the action, and the variables are not referenced
493  * except here at construction time and optionally via their names in the skin engine
494  */
495 namespace OtherControls
496 {
497 Connector surge_menu =
498     Connector("controls.surge_menu", 844, 550, 50, 15, Components::HSwitch2, Connector::SURGE_MENU)
499         .withProperty(Component::BACKGROUND, IDB_MAIN_MENU)
500         .withProperty(Component::DRAGGABLE_HSWITCH, false);
501 
502 Connector patch_browser = Connector("controls.patch_browser", 157, 12, 390, 28, Components::Custom,
503                                     Connector::PATCH_BROWSER);
504 Connector patch_category_jog =
505     Connector("controls.category.prevnext", 157, 42, Connector::JOG_PATCHCATEGORY).asJogPlusMinus();
506 Connector patch_jog =
507     Connector("controls.patch.prevnext", 246, 42, Connector::JOG_PATCH).asJogPlusMinus();
508 Connector patch_store =
509     Connector("controls.patch.store", 510, 42, 37, 12, Components::HSwitch2, Connector::STORE_PATCH)
510         .withHSwitch2Properties(IDB_STORE_PATCH, 1, 1, 1)
511         .withProperty(Component::DRAGGABLE_HSWITCH, false);
512 
513 Connector status_panel = Connector("controls.status.panel", 562, 12, Components::Group);
514 Connector status_mpe =
515     Connector("controls.status.mpe", 0, 0, 31, 12, Components::Switch, Connector::STATUS_MPE)
516         .withBackground(IDB_MPE_BUTTON)
517         .inParent("controls.status.panel");
518 Connector status_tune =
519     Connector("controls.status.tune", 0, 15, 31, 12, Components::Switch, Connector::STATUS_TUNE)
520         .withBackground(IDB_TUNE_BUTTON)
521         .inParent("controls.status.panel");
522 Connector status_zoom =
523     Connector("controls.status.zoom", 0, 30, 31, 12, Components::Switch, Connector::STATUS_ZOOM)
524         .withBackground(IDB_ZOOM_BUTTON)
525         .inParent("controls.status.panel");
526 
527 Connector vu_meter =
528     Connector("controls.vu_meter", 763, 15, 123, 13, Components::VuMeter, Connector::MAIN_VU_METER);
529 
530 Connector mseg_editor = Connector("msegeditor.window", 0, 57, 750, 365, Components::Custom,
531                                   Connector::MSEG_EDITOR_WINDOW);
532 
533 Connector store_patch_dialog = Connector("controls.patch.store.window", 157, 57, 390, 143,
534                                          Components::Custom, Connector::STORE_PATCH_DIALOG);
535 
536 // modulation panel is special, so it shows up as 'CUSTOM' with no connector and is special-cased in
537 // SurgeGUIEditor
538 Connector modulation_panel = Connector("controls.modulation.panel", 2, 402, Components::Custom);
539 } // namespace OtherControls
540 } // namespace Skin
541 } // namespace Surge
542