1 /* B.Jumblr
2  * Pattern-controlled audio stream / sample re-sequencer LV2 plugin
3  *
4  * Copyright (C) 2018, 2019 by Sven Jähnichen
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 
21 #include "BJumblrGUI.hpp"
22 #include "BUtilities/to_string.hpp"
23 #include "BUtilities/Path.hpp"
24 #include "BUtilities/vsystem.hpp"
25 #include "MessageDefinitions.hpp"
26 #include "MidiDefs.hpp"
27 
floorfrac(const double value)28 inline double floorfrac (const double value) {return value - floor (value);}
floormod(const double numer,const double denom)29 inline double floormod (const double numer, const double denom) {return numer - floor(numer / denom) * denom;}
30 
BJumblrGUI(const char * bundle_path,const LV2_Feature * const * features,PuglNativeView parentWindow)31 BJumblrGUI::BJumblrGUI (const char *bundle_path, const LV2_Feature *const *features, PuglNativeView parentWindow) :
32 	Window (1020, 620, "B.Jumblr", parentWindow, true, PUGL_MODULE, 0),
33 	controller (NULL), write_function (NULL),
34 	pluginPath (bundle_path ? std::string (bundle_path) : std::string ("")),
35 	sz (1.0), bgImageSurface (nullptr),
36 	uris (), forge (), editMode (0),
37 	patternFlipped (false),
38 	clipBoard (),
39 	cursor (0), wheelScrolled (false), padPressed (false), deleteMode (false),
40 	samplePath ("."), sampleStart (0), sampleEnd (0), sampleLoop (false),
41 	actPage (0), nrPages (1), pageOffset (0),
42 	mContainer (0, 0, 1020, 620, "main"),
43 	messageLabel (400, 45, 600, 20, "ctlabel", ""),
44 	pageWidget (18, 86, 504, 30, "widget", 0.0),
45 	pageBackSymbol (0, 0, 10, 30, "tab", LEFTSYMBOL),
46 	pageForwardSymbol (482, 0, 10, 30, "tab", RIGHTSYMBOL),
47 
48 	sourceLabel (510, 95, 60, 10, "ylabel", BJUMBLR_LABEL_SOURCE),
49 	calibrationLabel (405, 577, 80, 10, "ylabel", BJUMBLR_LABEL_CALIBRATION),
50 	stepEditModeLabel (535, 577, 80, 10, "ylabel", BJUMBLR_LABEL_STEP_EDIT_MODE),
51 	stepSizeLabel (690, 577, 80, 10, "ylabel", BJUMBLR_LABEL_STEP_SIZE),
52 	patternSizeLabel (850, 577, 80, 10, "ylabel", BJUMBLR_LABEL_PATTERN_SIZE),
53 	padLevelLabel (950, 120, 60, 10, "ylabel", BJUMBLR_LABEL_PAD_LEVEL),
54 	playbackLabel (950, 350, 60, 10, "ylabel", BJUMBLR_LABEL_PLAYBACK),
55 	speedLabel (950, 515, 60, 10, "ylabel", BJUMBLR_LABEL_SPEED),
56 
57 	midiBox (18, 118, 510, 120, "screen", 0),
58 	midiText (20, 10, 450, 20, "tlabel", BJUMBLR_LABEL_MIDI_PAGE " #1"),
59 	midiStatusLabel (10, 30, 130, 20, "ylabel", BJUMBLR_LABEL_MIDI_STATUS),
60 	midiStatusListBox
61 	(
62 		10, 50, 130, 20, 0, 20, 130, 100, "menu",
63 		BItems::ItemList ({{0, BJUMBLR_LABEL_NONE}, {9, BJUMBLR_LABEL_NOTE_ON}, {8, BJUMBLR_LABEL_NOTE_OFF}, {11, BJUMBLR_LABEL_CC}}),
64 		0
65 	),
66 	midiChannelLabel (150, 30, 50, 20, "ylabel", BJUMBLR_LABEL_CHANNEL),
67 	midiChannelListBox
68 	(
69 		150, 50, 50, 20, 0, 20, 50, 360, "menu",
70 		BItems::ItemList
71 		({
72 			{0, BJUMBLR_LABEL_ALL}, {1, "1"}, {2, "2"}, {3, "3"},
73 			{4, "4"}, {5, "5"}, {6, "6"}, {7, "7"},
74 			{8, "8"}, {9, "9"}, {10, "10"}, {11, "11"},
75 			{12, "12"}, {13, "13"}, {14, "14"}, {15, "15"}, {16, "16"}
76 		}),
77 		0
78 	),
79 	midiNoteLabel (210, 30, 160, 20, "ylabel", BJUMBLR_LABEL_NOTE),
80 	midiNoteListBox (210, 50, 160, 20, 0, 20, 160, 360, "menu", BItems::ItemList ({NOTELIST}), 128),
81 	midiValueLabel (380, 30, 50, 20, "ylabel", BJUMBLR_LABEL_VALUE),
82 	midiValueListBox (380, 50, 50, 20, 0, 20, 50, 360, "menu", BItems::ItemList ({VALLIST}), 128),
83 	midiLearnButton (440, 50, 60, 20, "menu/button", BJUMBLR_LABEL_LEARN),
84 	midiCancelButton (170, 90, 60, 20, "menu/button", BJUMBLR_LABEL_CANCEL),
85 	midiOkButton (280, 90, 60, 20, "menu/button", BJUMBLR_LABEL_OK),
86 
87 	flipButton (940, 120, 10, 10, "widget"),
88 	padSurface (18, 128, 924, 434, "box"),
89 	markerFwd (0, 130 + 15.5 * (430.0 / 16.0) - 10, 20, 20, "widget", MARKER_RIGHT),
90 	markerRev (940, 130 + 15.5 * (430.0 / 16.0) - 10, 20, 20, "widget", MARKER_LEFT),
91 	monitorWidget (20, 130, 920, 430, "monitor"),
92 	sourceListBox (570, 90, 120, 20, 120, 60, "menu", BItems::ItemList ({{0, BJUMBLR_LABEL_AUDIO_STREAM}, {1, BJUMBLR_LABEL_SAMPLE}}), 0),
93 	loadButton (710, 90, 20, 20, "menu/button"),
94 	sampleNameLabel (740, 90, 160, 20, "boxlabel", ""),
95 	sampleAmpDial (918, 88, 24, 24, "dial", 1.0, 0.0, 1.0, 0.0),
96 	playButton (18, 588, 24, 24, "widget", BJUMBLR_LABEL_PLAY),
97 	bypassButton (48, 588, 24, 24, "widget", BJUMBLR_LABEL_BYPASS),
98 	stopButton (78, 588, 24, 24, "widget", BJUMBLR_LABEL_STOP),
99 	syncWidget (400, 590, 95, 20, "widget", 0),
100 	zeroStepOffsetButton (0, 0, 20, 20, "menu/button"),
101 	decStepOffsetButton (25, 0, 20, 20, "menu/button"),
102 	hostSyncButton (50, 0, 20, 20, "menu/button"),
103 	incStepOffsetButton (75, 0, 20, 20, "menu/button"),
104 	editModeListBox (530, 590, 90, 20, 0, -60, 90, 60, "menu",
105 			 BItems::ItemList ({{0, BJUMBLR_LABEL_ADD}, {1, BJUMBLR_LABEL_REPLACE}}), 0),
106 	stepSizeListBox (650, 590, 70, 20, 0, -280, 70, 280, "menu",
107 			 BItems::ItemList ({{0.0625, "1/16"}, {0.83333333, "1/12"}, {0.125, "1/8"}, {0.1666667, "1/6"},
108 			 		    {0.25, "1/4"}, {0.3333333, "1/3"}, {0.5, "1/2"}, {0.666666667, "2/3"}, {0.75, "3/4"},
109 					    {1, "1"}, {2, "2"}, {2, "3"}, {4, "4"}}), 1),
110 	stepBaseListBox (730, 590, 90, 20, 0, -80, 90, 80, "menu", BItems::ItemList ({{0, BJUMBLR_LABEL_SECONDS}, {1, BJUMBLR_LABEL_BEATS}, {2, BJUMBLR_LABEL_BARS}}), 1),
111 	padSizeListBox (850, 590, 90, 20, 0, -240, 100, 240, "menu",
112 		     BItems::ItemList ({{2, "2 " BJUMBLR_LABEL_STEPS}, {3, "3 " BJUMBLR_LABEL_STEPS}, {4, "4 " BJUMBLR_LABEL_STEPS},
113 		     			{6, "6 " BJUMBLR_LABEL_STEPS}, {8, "8 " BJUMBLR_LABEL_STEPS}, {9, "9 " BJUMBLR_LABEL_STEPS},
114 		     			{12, "12 " BJUMBLR_LABEL_STEPS}, {16, "16 " BJUMBLR_LABEL_STEPS}, {18, "18 " BJUMBLR_LABEL_STEPS},
115 					{24, "24 " BJUMBLR_LABEL_STEPS}, {32, "32 " BJUMBLR_LABEL_STEPS}}), 16),
116 	levelDial (960, 290, 40, 48, "dial", 1.0, 0.0, 1.0, 0.01, "%1.2f"),
117 	delayDisplayLabel (958, 370, 44, 20, "smboxlabel", ""),
118 	manualProgressionDelayWidget (0, 0, 0, 0, "widget", 0.0, -32.0, 32.0, 0.0),
119 	resetDelayButton (958, 398, 44, 22, "widget", BJUMBLR_LABEL_RESET_DELAY),
120 	increaseDelayButton (958, 428, 44, 22, "widget", BJUMBLR_LABEL_INCREASE_DELAY),
121 	decreaseDelayButton (958, 488, 44, 22, "widget", BJUMBLR_LABEL_DECREASE_DELAY),
122 	setStartDelayButton (958, 458, 44, 22, "widget", BJUMBLR_LABEL_DELAY_TO_START),
123 	speedDial (960, 530, 40, 48, "dial", 1.0, 0.0, 4.0, 0.25, "%1.2f"),
124 	helpButton (958, 588, 24, 24, "widget", BJUMBLR_LABEL_HELP),
125 	ytButton (988, 588, 24, 24, "widget", BJUMBLR_LABEL_TUTORIAL),
126 	fileChooser (nullptr)
127 {
128 	// Init tabs
129 	for (int i = 0; i < MAXPAGES; ++i)
130 	{
131 		tabs[i].container = BWidgets::Widget (i * 80, 0, 78, 30, "tab");
132 		tabs[i].icon = BWidgets::ImageIcon (0, 8, 40, 20, "widget", pluginPath + "inc/page" + std::to_string (i + 1) + ".png");
133 		tabs[i].playSymbol = SymbolWidget (40, 12, 20, 12, "symbol", PLAYSYMBOL);
134 		tabs[i].midiSymbol = SymbolWidget (60, 12, 20, 12, "symbol", MIDISYMBOL);
135 		for (int j = 0; j < 4; ++j) tabs[i].symbols[j] = SymbolWidget (68 - j * 10, 2, 8, 8, "symbol", SWSymbol(j));
136 		for (int j = 0; j < 4; ++j) tabs[i].midiWidgets[j] = BWidgets::ValueWidget (0, 0, 0, 0, "widget", 0);
137 	}
138 
139 	// Init editButtons
140 	for (int i = 0; i < EDIT_RESET; ++i) edit1Buttons[i] = HaloToggleButton (128 + i * 30, 588, 24, 24, "widget", editLabels[i]);
141 	for (int i = 0; i < MAXEDIT - EDIT_RESET; ++i) edit2Buttons[i] = HaloButton (298 + i * 30, 588, 24, 24, "widget", editLabels[i + EDIT_RESET]);
142 
143 	for (int i = 0; i < 5; ++i) levelButtons[i] = HaloToggleButton (958, 138 + i * 30, 44, 22, "widget", BUtilities::to_string (1.0 - 0.25 * double(i), "%1.2f"));
144 	levelButtons[0].setValue (1.0);
145 
146 	// Link controllerWidgets
147 	controllerWidgets[SOURCE] = (BWidgets::ValueWidget*) &sourceListBox;
148 	controllerWidgets[PLAY] = (BWidgets::ValueWidget*) &playButton;
149 	controllerWidgets[NR_OF_STEPS] = (BWidgets::ValueWidget*) &padSizeListBox;
150 	controllerWidgets[STEP_SIZE] = (BWidgets::ValueWidget*) &stepSizeListBox;
151 	controllerWidgets[STEP_BASE] = (BWidgets::ValueWidget*) &stepBaseListBox;
152 	controllerWidgets[STEP_OFFSET] = (BWidgets::ValueWidget*) &syncWidget;
153 	controllerWidgets[MANUAL_PROGRSSION_DELAY] = (BWidgets::ValueWidget*) &manualProgressionDelayWidget;
154 	controllerWidgets[SPEED] = (BWidgets::ValueWidget*) &speedDial;
155 	controllerWidgets[PAGE] = (BWidgets::ValueWidget*) &pageWidget;
156 	for (int i = 0; i < MAXPAGES; ++i)
157 	{
158 		for (int j = 0; j < 4; ++j) controllerWidgets[MIDI + i * 4 + j] = (BWidgets::ValueWidget*) &tabs[i].midiWidgets[j];
159 	}
160 
161 	// Init controller values
162 	for (int i = 0; i < MAXCONTROLLERS; ++i) controllers[i] = controllerWidgets[i]->getValue ();
163 
164 	// Set callback functions
165 	for (int i = 0; i < MAXCONTROLLERS; ++i) controllerWidgets[i]->setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, valueChangedCallback);
166 	bypassButton.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, valueChangedCallback);
167 	stopButton.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, valueChangedCallback);
168 	syncWidget.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, valueChangedCallback);
169 	zeroStepOffsetButton.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, syncButtonClickedCallback);
170 	decStepOffsetButton.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, syncButtonClickedCallback);
171 	hostSyncButton.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, syncButtonClickedCallback);
172 	incStepOffsetButton.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, syncButtonClickedCallback);
173 	editModeListBox.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, valueChangedCallback);
174 	for (int i = 0; i < EDIT_RESET; ++i) edit1Buttons[i].setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, edit1ChangedCallback);
175 	for (int i = 0; i < MAXEDIT - EDIT_RESET; ++i) edit2Buttons[i].setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, edit2ChangedCallback);
176 	for (int i = 0; i < 5; ++i) levelButtons[i].setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, levelChangedCallback);
177 	levelDial.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, levelChangedCallback);
178 	resetDelayButton.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, delayButtonsClickedCallback);
179 	increaseDelayButton.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, delayButtonsClickedCallback);
180 	decreaseDelayButton.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, delayButtonsClickedCallback);
181 	setStartDelayButton.setCallbackFunction (BEvents::VALUE_CHANGED_EVENT, delayButtonsClickedCallback);
182 	helpButton.setCallbackFunction(BEvents::BUTTON_PRESS_EVENT, helpButtonClickedCallback);
183 	ytButton.setCallbackFunction(BEvents::BUTTON_PRESS_EVENT, ytButtonClickedCallback);
184 	loadButton.setCallbackFunction(BEvents::BUTTON_PRESS_EVENT, loadButtonClickedCallback);
185 	sampleNameLabel.setCallbackFunction(BEvents::BUTTON_PRESS_EVENT, loadButtonClickedCallback);
186 	sampleAmpDial.setCallbackFunction(BEvents::VALUE_CHANGED_EVENT, valueChangedCallback);
187 
188 	pageBackSymbol.setCallbackFunction(BEvents::BUTTON_PRESS_EVENT, pageScrollClickedCallback);
189 	pageForwardSymbol.setCallbackFunction(BEvents::BUTTON_PRESS_EVENT, pageScrollClickedCallback);
190 
191 	for (Tab& t : tabs)
192 	{
193 		t.container.setCallbackFunction(BEvents::BUTTON_PRESS_EVENT, pageClickedCallback);
194 		t.playSymbol.setCallbackFunction(BEvents::BUTTON_PRESS_EVENT, pagePlayClickedCallback);
195 		t.midiSymbol.setCallbackFunction(BEvents::BUTTON_PRESS_EVENT, midiSymbolClickedCallback);
196 		for (SymbolWidget& s : t.symbols) s.setCallbackFunction(BEvents::BUTTON_PRESS_EVENT, pageSymbolClickedCallback);
197 	}
198 
199 	midiLearnButton.setCallbackFunction(BEvents::VALUE_CHANGED_EVENT, midiButtonClickedCallback);
200 	midiCancelButton.setCallbackFunction(BEvents::VALUE_CHANGED_EVENT, midiButtonClickedCallback);
201 	midiOkButton.setCallbackFunction(BEvents::VALUE_CHANGED_EVENT, midiButtonClickedCallback);
202 	midiStatusListBox.setCallbackFunction(BEvents::VALUE_CHANGED_EVENT, midiStatusChangedCallback);
203 
204 	padSurface.setDraggable (true);
205 	padSurface.setCallbackFunction (BEvents::BUTTON_PRESS_EVENT, padsPressedCallback);
206 	padSurface.setCallbackFunction (BEvents::BUTTON_RELEASE_EVENT, padsPressedCallback);
207 	padSurface.setCallbackFunction (BEvents::POINTER_DRAG_EVENT, padsPressedCallback);
208 
209 	padSurface.setScrollable (true);
210 	padSurface.setCallbackFunction (BEvents::WHEEL_SCROLL_EVENT, padsScrolledCallback);
211 
212 	padSurface.setFocusable (true);
213 	padSurface.setCallbackFunction (BEvents::FOCUS_IN_EVENT, padsFocusedCallback);
214 	padSurface.setCallbackFunction (BEvents::FOCUS_OUT_EVENT, padsFocusedCallback);
215 
216 	flipButton.setClickable (true);
217 	flipButton.setCallbackFunction (BEvents::BUTTON_CLICK_EVENT, patternFlippedClickedCallback);
218 
219 	// Configure widgets
220 	loadButton.hide();
221 	sampleNameLabel.hide();
222 	sampleAmpDial.hide();
223 	midiBox.hide();
224 	pageBackSymbol.setFocusable (false);
225 	pageForwardSymbol.setFocusable (false);
226 	pageBackSymbol.hide();
227 	pageForwardSymbol.hide();
228 	for (int i = 0; i < MAXPAGES; ++i)
229 	{
230 		tabs[i].playSymbol.setState (BColors::INACTIVE);
231 		tabs[i].midiSymbol.setState (BColors::INACTIVE);
232 		for (int j = 0; j < 4; ++j) tabs[i].symbols[j].setState (BColors::ACTIVE);
233 		if (i > 0) tabs[i].container.hide();
234 	}
235 	tabs[0].container.rename ("activetab");
236 	tabs[0].playSymbol.setState (BColors::ACTIVE);
237 	tabs[0].symbols[CLOSESYMBOL].hide(); // -
238 	tabs[0].symbols[LEFTSYMBOL].hide(); // <
239 	tabs[0].symbols[RIGHTSYMBOL].hide(); // >
240 	for (Tab& t : tabs) t.icon.setClickable (false);
241 
242 	for (Pattern& p : pattern) p.clear();
243 
244 	// Load background & apply theme
245 	bgImageSurface = cairo_image_surface_create_from_png ((pluginPath + BG_FILE).c_str());
246 	for (int i = 0; i < 5; ++i)
247 	{
248 		BColors::Color col = BColors::yellow;
249 		col.applyBrightness (- 0.25 * double(i));
250 		drawButton (bgImageSurface, 960, 140 + i * 30, 40, 18, col);
251 	}
252 
253 	BColors::Color col = BColors::white;
254 	col.applyBrightness (- 0.25);
255 	drawButton (bgImageSurface, 960, 400, 40, 18, col, BUTTON_HOME_SYMBOL);
256 	drawButton (bgImageSurface, 960, 430, 40, 18, col, BUTTON_UP_SYMBOL);
257 	drawButton (bgImageSurface, 960, 490, 40, 18, col, BUTTON_DOWN_SYMBOL);
258 	drawButton (bgImageSurface, 960, 460, 40, 18, col, BUTTON_BOTTOM_SYMBOL);
259 
260 	widgetBg.loadFillFromCairoSurface (bgImageSurface);
261 	applyTheme (theme);
262 
263 	// Pack widgets
264 	mContainer.add (sourceLabel);
265 	mContainer.add (calibrationLabel);
266 	mContainer.add (stepEditModeLabel);
267 	mContainer.add (stepSizeLabel);
268 	mContainer.add (patternSizeLabel);
269 	mContainer.add (padLevelLabel);
270 	mContainer.add (playbackLabel);
271 	mContainer.add (speedLabel);
272 
273 	mContainer.add (messageLabel);
274 	mContainer.add (pageWidget);
275 	pageWidget.add (pageBackSymbol);
276 	pageWidget.add (pageForwardSymbol);
277 	for (int i = MAXPAGES - 1; i >= 0; --i)
278 	{
279 		Tab& t = tabs[i];
280 		t.container.add (t.icon);
281 		for (SymbolWidget& s : t.symbols) t.container.add (s);
282 		for (BWidgets::ValueWidget& m : t.midiWidgets) t.container.add (m);
283 		t.container.add (t.playSymbol);
284 		t.container.add (t.midiSymbol);
285 		pageWidget.add (t.container);
286 	}
287 	mContainer.add (playButton);
288 	mContainer.add (bypassButton);
289 	mContainer.add (stopButton);
290 	for (int i = 0; i < EDIT_RESET; ++i) mContainer.add (edit1Buttons[i]);
291 	for (int i = 0; i < MAXEDIT - EDIT_RESET; ++i) mContainer.add (edit2Buttons[i]);
292 	syncWidget.add (zeroStepOffsetButton);
293 	syncWidget.add (decStepOffsetButton);
294 	syncWidget.add (hostSyncButton);
295 	syncWidget.add (incStepOffsetButton);
296 	mContainer.add (syncWidget);
297 	mContainer.add (editModeListBox);
298 	mContainer.add (stepSizeListBox);
299 	mContainer.add (stepBaseListBox);
300 	mContainer.add (padSizeListBox);
301 	mContainer.add (padSurface);
302 	mContainer.add (flipButton);
303 	mContainer.add (markerFwd);
304 	mContainer.add (markerRev);
305 	mContainer.add (monitorWidget);
306 	for (int i = 0; i < 5; ++i) mContainer.add (levelButtons[i]);
307 	mContainer.add (sourceListBox);
308 	mContainer.add (loadButton);
309 	mContainer.add (sampleNameLabel);
310 	mContainer.add (sampleAmpDial);
311 	mContainer.add (levelDial);
312 	mContainer.add (delayDisplayLabel);
313 	mContainer.add (manualProgressionDelayWidget);
314 	mContainer.add (resetDelayButton);
315 	mContainer.add (increaseDelayButton);
316 	mContainer.add (decreaseDelayButton);
317 	mContainer.add (setStartDelayButton);
318 	mContainer.add (speedDial);
319 	mContainer.add (helpButton);
320 	mContainer.add (ytButton);
321 
322 	mContainer.add (midiBox);
323 	midiBox.add (midiText);
324 	midiBox.add (midiStatusLabel);
325 	midiBox.add (midiStatusListBox);
326 	midiBox.add (midiChannelLabel);
327 	midiBox.add (midiChannelListBox);
328 	midiBox.add (midiNoteLabel);
329 	midiBox.add (midiNoteListBox);
330 	midiBox.add (midiValueLabel);
331 	midiBox.add (midiValueListBox);
332 	midiBox.add (midiLearnButton);
333 	midiBox.add (midiCancelButton);
334 	midiBox.add (midiOkButton);
335 
336 	drawPad();
337 	add (mContainer);
338 	getKeyGrabStack()->add (this);
339 
340 	for (Pattern& p : pattern) p.clear ();
341 
342 	//Scan host features for URID map
343 	LV2_URID_Map* map = NULL;
344 	for (int i = 0; features[i]; ++i)
345 	{
346 		if (strcmp(features[i]->URI, LV2_URID__map) == 0)
347 		{
348 			map = (LV2_URID_Map*) features[i]->data;
349 		}
350 	}
351 	if (!map) throw std::invalid_argument ("Host does not support urid:map");
352 
353 	//Map URIS
354 	getURIs (map, &uris);
355 
356 	// Initialize forge
357 	lv2_atom_forge_init (&forge, map);
358 }
359 
~BJumblrGUI()360 BJumblrGUI::~BJumblrGUI ()
361 {
362 	if (fileChooser) delete fileChooser;
363 	send_ui_off ();
364 }
365 
clear()366 void BJumblrGUI::Pattern::clear ()
367 {
368 	Pad pad0 = Pad ();
369 
370 	changes.oldMessage.clear ();
371 	changes.newMessage.clear ();
372 	journal.clear ();
373 
374 	for (int r = 0; r < MAXSTEPS; ++r)
375 	{
376 		for (int s = 0; s < MAXSTEPS; ++s)
377 		{
378 			setPad (r, s, pad0);
379 		}
380 	}
381 
382 	store ();
383 }
384 
getPad(const size_t row,const size_t step) const385 Pad BJumblrGUI::Pattern::getPad (const size_t row, const size_t step) const
386 {
387 	return pads[LIMIT (row, 0, MAXSTEPS)][LIMIT (step, 0, MAXSTEPS)];
388 }
setPad(const size_t row,const size_t step,const Pad & pad)389 void BJumblrGUI::Pattern::setPad (const size_t row, const size_t step, const Pad& pad)
390 {
391 	size_t r = LIMIT (row, 0, MAXSTEPS);
392 	size_t s = LIMIT (step, 0, MAXSTEPS);
393 	changes.oldMessage.push_back (PadMessage (s, r, pads[r][s]));
394 	changes.newMessage.push_back (PadMessage (s, r, pad));
395 	pads[r][s] = pad;
396 }
397 
undo()398 std::vector<PadMessage> BJumblrGUI::Pattern::undo ()
399 {
400 	if (!changes.newMessage.empty ()) store ();
401 
402 	std::vector<PadMessage> padMessages = journal.undo ();
403 	std::reverse (padMessages.begin (), padMessages.end ());
404 	for (PadMessage const& p : padMessages)
405 	{
406 		size_t r = LIMIT (p.row, 0, MAXSTEPS);
407 		size_t s = LIMIT (p.step, 0, MAXSTEPS);
408 		pads[r][s] = Pad (p);
409 	}
410 
411 	return padMessages;
412 }
413 
redo()414 std::vector<PadMessage> BJumblrGUI::Pattern::redo ()
415 {
416 	if (!changes.newMessage.empty ()) store ();
417 
418 	std::vector<PadMessage> padMessages = journal.redo ();
419 	for (PadMessage const& p : padMessages)
420 	{
421 		size_t r = LIMIT (p.row, 0, MAXSTEPS);
422 		size_t s = LIMIT (p.step, 0, MAXSTEPS);
423 		pads[r][s] = Pad (p);
424 	}
425 
426 	return padMessages;
427 }
428 
store()429 void BJumblrGUI::Pattern::store ()
430 {
431 	if (changes.newMessage.empty ()) return;
432 
433 	journal.push (changes.oldMessage, changes.newMessage);
434 	changes.oldMessage.clear ();
435 	changes.newMessage.clear ();
436 }
437 
port_event(uint32_t port,uint32_t buffer_size,uint32_t format,const void * buffer)438 void BJumblrGUI::port_event(uint32_t port, uint32_t buffer_size,
439 	uint32_t format, const void* buffer)
440 {
441 	// Notify port
442 	if ((format == uris.atom_eventTransfer) && (port == NOTIFY))
443 	{
444 		const LV2_Atom* atom = (const LV2_Atom*) buffer;
445 		if ((atom->type == uris.atom_Blank) || (atom->type == uris.atom_Object))
446 		{
447 			const LV2_Atom_Object* obj = (const LV2_Atom_Object*) atom;
448 
449 			// Pad notification
450 			if (obj->body.otype == uris.notify_padEvent)
451 			{
452 				LV2_Atom *oEdit = NULL, *oPage = NULL, *oPad = NULL, *oFull = NULL;
453 				int page = -1;
454 				lv2_atom_object_get(obj,
455 						    uris.notify_editMode, &oEdit,
456 						    uris.notify_padPage, &oPage,
457 						    uris.notify_pad, &oPad,
458 						    uris.notify_padFullPattern, &oFull,
459 						    NULL);
460 
461 				if (oEdit && (oEdit->type == uris.atom_Int))
462 				{
463 					editModeListBox.setValue (((LV2_Atom_Int*)oEdit)->body);
464 				}
465 
466 				if (oPage && (oPage->type == uris.atom_Int))
467 				{
468 					page = (((LV2_Atom_Int*)oPage)->body);
469 
470 					while (page >= nrPages) pushPage();
471 				}
472 
473 				if (oPad && (oPad->type == uris.atom_Vector) && (page >= 0) && (page < MAXPAGES))
474 				{
475 					const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*) oPad;
476 					if (vec->body.child_type == uris.atom_Float)
477 					{
478 						if (wheelScrolled)
479 						{
480 							pattern[page].store ();
481 							wheelScrolled = false;
482 						}
483 
484 						uint32_t size = (uint32_t) ((oPad->size - sizeof(LV2_Atom_Vector_Body)) / sizeof (PadMessage));
485 						PadMessage* pMes = (PadMessage*)(&vec->body + 1);
486 						for (unsigned int i = 0; i < size; ++i)
487 
488 						{
489 							int step = (int) pMes[i].step;
490 							int row = (int) pMes[i].row;
491 							if ((step >= 0) && (step < MAXSTEPS) && (row >= 0) && (row < MAXSTEPS))
492 							{
493 								pattern[page].setPad (row, step, Pad (pMes[i]));
494 							}
495 						}
496 						pattern[page].store ();
497 						if (page == actPage) drawPad ();
498 					}
499 				}
500 
501 				if (oFull && (oFull->type == uris.atom_Vector) && (page >= 0) && (page < MAXPAGES))
502 				{
503 					const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*) oFull;
504 					if (vec->body.child_type == uris.atom_Float)
505 					{
506 						if (wheelScrolled)
507 						{
508 							pattern[page].store ();
509 							wheelScrolled = false;
510 						}
511 
512 						uint32_t size = (uint32_t) ((oFull->size - sizeof(LV2_Atom_Vector_Body)) / sizeof (Pad));
513 						Pad* data = (Pad*)(&vec->body + 1);
514 
515 						if (size == MAXSTEPS * MAXSTEPS)
516 						{
517 							// Copy pattern data
518 							for (int r = 0; r < MAXSTEPS; ++r)
519 							{
520 								for (int s = 0; s < MAXSTEPS; ++s)
521 								{
522 									pattern[page].setPad (r, s, data[r * MAXSTEPS + s]);
523 								}
524 							}
525 						}
526 
527 						pattern[page].store ();
528 						if (page == actPage) drawPad ();
529 					}
530 				}
531 			}
532 
533 			// Message notification
534 			else if (obj->body.otype == uris.notify_messageEvent)
535 			{
536 				const LV2_Atom* data = NULL;
537 				lv2_atom_object_get(obj, uris.notify_message, &data, 0);
538 				if (data && (data->type == uris.atom_Int))
539 				{
540 					const int messageNr = ((LV2_Atom_Int*)data)->body;
541 					std::string msg = ((messageNr >= NO_MSG) && (messageNr < MAXMESSAGES) ? messageStrings[messageNr] : "");
542 					messageLabel.setText (msg);
543 				}
544 			}
545 
546 			// Path notification
547 			else if (obj->body.otype == uris.notify_pathEvent)
548 			{
549 				const LV2_Atom* oPath = NULL, *oStart = NULL, *oEnd = NULL, *oAmp = NULL, *oLoop = NULL;
550 				lv2_atom_object_get
551 				(
552 					obj,
553 					uris.notify_samplePath, &oPath,
554 					uris.notify_sampleStart, &oStart,
555 					uris.notify_sampleEnd, &oEnd,
556 					uris.notify_sampleAmp, &oAmp,
557 					uris.notify_sampleLoop, &oLoop,
558 					0
559 				);
560 				if (oPath && (oPath->type == uris.atom_Path))
561 				{
562 					const BUtilities::Path p = BUtilities::Path ((const char*)LV2_ATOM_BODY_CONST(oPath));
563 					samplePath = p.dir();
564 					sampleNameLabel.setText (p.filename());
565 				}
566 
567 				if (oStart && (oStart->type == uris.atom_Long)) sampleStart = ((LV2_Atom_Long*)oStart)->body;
568 				if (oEnd && (oEnd->type == uris.atom_Long)) sampleEnd = ((LV2_Atom_Long*)oEnd)->body;
569 				if (oAmp && (oAmp->type == uris.atom_Float)) sampleAmpDial.setValue (((LV2_Atom_Float*)oAmp)->body);
570 				if (oLoop && (oLoop->type == uris.atom_Bool)) sampleLoop = bool (((LV2_Atom_Bool*)oLoop)->body);
571 			}
572 
573 			// Status notifications
574 			else if (obj->body.otype == uris.notify_statusEvent)
575 			{
576 				LV2_Atom *oMax = NULL, *oSched = NULL, *oPlay = NULL, *oMid = NULL, *oCursor = NULL, *oDelay = NULL, *oFlip = NULL;
577 				lv2_atom_object_get
578 				(
579 					obj,
580 					uris.notify_maxPage, &oMax,
581 					uris.notify_schedulePage, &oSched,
582 					uris.notify_playbackPage, &oPlay,
583 					uris.notify_midiLearned, &oMid,
584 					uris.notify_cursor, &oCursor,
585 					uris.notify_progressionDelay, &oDelay,
586 					uris.notify_padFlipped, &oFlip,
587 					NULL
588 				);
589 
590 				if (oMax && (oMax->type == uris.atom_Int))
591 				{
592 					int newPages = (((LV2_Atom_Int*)oMax)->body);
593 
594 					while (newPages > nrPages) pushPage();
595 					while (newPages < nrPages) popPage();
596 				}
597 
598 				if (oSched && (oSched->type == uris.atom_Int))
599 				{
600 					int newSched = (((LV2_Atom_Int*)oSched)->body);
601 					pageWidget.setValue (LIMIT (newSched, 0, MAXPAGES - 1));
602 				}
603 
604 				if (oPlay && (oPlay->type == uris.atom_Int))
605 				{
606 					int newPlay = (((LV2_Atom_Int*)oPlay)->body);
607 					newPlay = LIMIT (newPlay, 0, MAXPAGES - 1);
608 					int val = pageWidget.getValue();
609 					for (int i = 0; i < MAXPAGES; ++i)
610 					{
611 						if (i == newPlay) tabs[i].playSymbol.setState (BColors::ACTIVE);
612 						else if (i != val) tabs[i].playSymbol.setState (BColors::INACTIVE);
613 					}
614 					drawPad();
615 				}
616 
617 				if (oMid && (oMid->type == uris.atom_Int))
618 				{
619 					uint32_t newMid = (((LV2_Atom_Int*)oMid)->body);
620 					uint8_t st = newMid >> 24;
621 					uint8_t ch = (newMid >> 16) & 0xFF;
622 					uint8_t nt = (newMid >> 8) & 0xFF;
623 					uint8_t vl = newMid & 0xFF;
624 					midiStatusListBox.setValue ((st == 8) || (st == 9) || (st == 11) ? st : 0);
625 					midiChannelListBox.setValue (LIMIT (ch, 0, 15) + 1);
626 					midiNoteListBox.setValue (LIMIT (nt, 0, 127));
627 					midiValueListBox.setValue (LIMIT (vl, 0, 127));
628 					midiLearnButton.setValue (0.0);
629 				}
630 
631 				// Cursor notifications
632 				if (oCursor && (oCursor->type == uris.atom_Float) && (cursor != ((LV2_Atom_Float*)oCursor)->body))
633 				{
634 					const int iOldCursor = cursor;
635 					cursor = ((LV2_Atom_Float*)oCursor)->body;
636 					if (int (cursor) != iOldCursor)
637 					{
638 						setMarkers();
639 						drawPad ();
640 					}
641 				}
642 
643 				// Delay notifications
644 				if (oDelay && (oDelay->type == uris.atom_Float))
645 				{
646 					float delay = ((LV2_Atom_Float*)oDelay)->body;
647 					std::string delaytxt = BUtilities::to_string (delay, "%5.2f");
648 					if (delaytxt != delayDisplayLabel.getText()) delayDisplayLabel.setText (delaytxt);
649 				}
650 
651 				// Flip notification
652 				if (oFlip && (oFlip->type == uris.atom_Bool))
653 				{
654 					const bool newFlip = ((LV2_Atom_Bool*)oFlip)->body;
655 					if (newFlip != patternFlipped)
656 					{
657 						patternFlipped = newFlip;
658 						monitorWidget.flip (patternFlipped);
659 						setMarkers();
660 						drawPad();
661 					}
662 				}
663 			}
664 
665 			// Monitor notification
666 			if (obj->body.otype == uris.notify_waveformEvent)
667 			{
668 				int start = -1;
669 
670 				const LV2_Atom *oStart = NULL, *oData = NULL;
671 				lv2_atom_object_get (obj,
672 						     uris.notify_waveformStart, &oStart,
673 						     uris.notify_waveformData, &oData,
674 						     NULL);
675 				if (oStart && (oStart->type == uris.atom_Int)) start = ((LV2_Atom_Int*)oStart)->body;
676 
677 				if (oData && (oData->type == uris.atom_Vector))
678 				{
679 					const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*) oData;
680 					if (vec->body.child_type == uris.atom_Float)
681 					{
682 						uint32_t size = (uint32_t) ((oData->size - sizeof(LV2_Atom_Vector_Body)) / sizeof (float));
683 						float* data = (float*) (&vec->body + 1);
684 						if ((start >= 0) && (size > 0))
685 						{
686 							monitorWidget.addData (start, size, data);
687 							monitorWidget.redrawRange (start, size);
688 						}
689 					}
690 				}
691 			}
692 		}
693 	}
694 
695 	// Scan remaining ports
696 	else if ((format == 0) && (port >= CONTROLLERS))
697 	{
698 		float* pval = (float*) buffer;
699 		controllerWidgets[port - CONTROLLERS]->setValue (*pval);
700 	}
701 
702 }
703 
resize()704 void BJumblrGUI::resize ()
705 {
706 	hide ();
707 	//Scale fonts
708 	ctLabelFont.setFontSize (12 * sz);
709 	tLabelFont.setFontSize (12 * sz);
710 	tgLabelFont.setFontSize (12 * sz);
711 	lfLabelFont.setFontSize (12 * sz);
712 	boldLfLabelFont.setFontSize (12 * sz);
713 	smLabelFont.setFontSize (8 * sz);
714 
715 	//Background
716 	cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1020 * sz, 620 * sz);
717 	cairo_t* cr = cairo_create (surface);
718 	cairo_scale (cr, sz, sz);
719 	cairo_set_source_surface(cr, bgImageSurface, 0, 0);
720 	cairo_paint(cr);
721 	widgetBg.loadFillFromCairoSurface(surface);
722 	cairo_destroy (cr);
723 	cairo_surface_destroy (surface);
724 
725 	//Scale widgets
726 	RESIZE (mContainer, 0, 0, 1020, 620, sz);
727 	RESIZE (messageLabel, 400, 45, 600, 20, sz);
728 	RESIZE (pageWidget, 18, 86, 504, 30, sz);
729 	updatePageContainer();
730 	for (int i = 0; i < MAXPAGES; ++i)
731 	{
732 		RESIZE (tabs[i].icon, 0, 8, 40, 20, sz);
733 		RESIZE (tabs[i].playSymbol, 40, 12, 20, 12, sz);
734 		RESIZE (tabs[i].midiSymbol, 60, 12, 20, 12, sz);
735 		for (int j = 0; j < 4; ++j) RESIZE (tabs[i].symbols[j], 68 - j * 10, 2, 8, 8, sz);
736 	}
737 
738 	RESIZE (sourceLabel, 510, 95, 60, 10, sz);
739 	RESIZE (calibrationLabel, 405, 577, 80, 10, sz);
740 	RESIZE (stepEditModeLabel ,535, 577, 80, 10, sz);
741 	RESIZE (stepSizeLabel, 690, 577, 80, 10, sz);
742 	RESIZE (patternSizeLabel, 850, 577, 80, 10, sz);
743 	RESIZE (padLevelLabel, 950, 120, 60, 10, sz);
744 	RESIZE (playbackLabel, 950, 350, 60, 10, sz);
745 	RESIZE (speedLabel, 950, 515, 60, 10, sz);
746 
747 	RESIZE (midiBox, 290, 168, 510, 120, sz);
748 	RESIZE (midiText, 20, 10, 450, 20, sz);
749 	RESIZE (midiStatusLabel, 10, 30, 130, 20, sz);
750 	RESIZE (midiStatusListBox, 10, 50, 130, 20, sz);
751 	midiStatusListBox.resizeListBox(BUtilities::Point (130 * sz, 100 * sz));
752 	midiStatusListBox.moveListBox(BUtilities::Point (0, 20 * sz));
753 	midiStatusListBox.resizeListBoxItems(BUtilities::Point (130 * sz, 20 * sz));
754 	RESIZE (midiChannelLabel, 150, 30, 50, 20, sz);
755 	RESIZE (midiChannelListBox, 150, 50, 50, 20, sz);
756 	midiChannelListBox.resizeListBox(BUtilities::Point (50 * sz, 360 * sz));
757 	midiChannelListBox.moveListBox(BUtilities::Point (0, 20 * sz));
758 	midiChannelListBox.resizeListBoxItems(BUtilities::Point (50 * sz, 20 * sz));
759 	RESIZE (midiNoteLabel, 210, 30, 160, 20, sz);
760 	RESIZE (midiNoteListBox, 210, 50, 160, 20, sz);
761 	midiNoteListBox.resizeListBox(BUtilities::Point (160 * sz, 360 * sz));
762 	midiNoteListBox.moveListBox(BUtilities::Point (0, 20 * sz));
763 	midiNoteListBox.resizeListBoxItems(BUtilities::Point (160 * sz, 20 * sz));
764 	RESIZE (midiValueLabel, 380, 30, 50, 20, sz);
765 	RESIZE (midiValueListBox, 380, 50, 50, 20, sz);
766 	midiValueListBox.resizeListBox(BUtilities::Point (50 * sz, 360 * sz));
767 	midiValueListBox.moveListBox(BUtilities::Point (0, 20 * sz));
768 	midiValueListBox.resizeListBoxItems(BUtilities::Point (50 * sz, 20 * sz));
769 	RESIZE (midiLearnButton, 440, 50, 60, 20, sz);
770 	RESIZE (midiCancelButton, 170, 90, 60, 20, sz);
771 	RESIZE (midiOkButton, 280, 90, 60, 20, sz);
772 
773 	RESIZE (flipButton, 940, 120, 10, 10, sz);
774 	RESIZE (padSurface, 18, 128, 924, 434, sz);
775 	setMarkers();
776 	RESIZE (monitorWidget, 20, 130, 920, 430, sz);
777 	RESIZE (sourceListBox, 570, 90, 120, 20, sz);
778 	sourceListBox.resizeListBox (BUtilities::Point (120 * sz, 60 * sz));
779 	sourceListBox.resizeListBoxItems (BUtilities::Point (120 * sz, 20 * sz));
780 	RESIZE (loadButton, 710, 90, 20, 20, sz);
781 	RESIZE (sampleNameLabel, 740, 90, 160, 20, sz);
782 	RESIZE (sampleAmpDial, 918, 88, 24, 24, sz);
783 	RESIZE (playButton, 18, 588, 24, 24, sz);
784 	RESIZE (bypassButton, 48, 588, 24, 24, sz);
785 	RESIZE (stopButton, 78, 588, 24, 24, sz);
786 	for (int i = 0; i < EDIT_RESET; ++i) RESIZE (edit1Buttons[i], 128 + i * 30, 588, 24, 24, sz);
787 	for (int i = 0; i < MAXEDIT - EDIT_RESET; ++i) RESIZE (edit2Buttons[i], 298 + i * 30, 588, 24, 24, sz);
788 	RESIZE (syncWidget, 400, 590, 95, 20, sz);
789 	RESIZE (zeroStepOffsetButton, 0, 0, 20, 20, sz);
790 	RESIZE (decStepOffsetButton, 25, 0, 20, 20, sz);
791 	RESIZE (hostSyncButton, 50, 0, 20, 20, sz);
792 	RESIZE (incStepOffsetButton, 75, 0, 20, 20, sz);
793 	RESIZE (editModeListBox, 530, 590, 90, 20, sz);
794 	editModeListBox.resizeListBox(BUtilities::Point (90 * sz, 60 * sz));
795 	editModeListBox.moveListBox(BUtilities::Point (0, -60 * sz));
796 	editModeListBox.resizeListBoxItems(BUtilities::Point (90 * sz, 20 * sz));
797 	RESIZE (stepSizeListBox, 650, 590, 70, 20, sz);
798 	stepSizeListBox.resizeListBox(BUtilities::Point (70 * sz, 280 * sz));
799 	stepSizeListBox.moveListBox(BUtilities::Point (0, -280 * sz));
800 	stepSizeListBox.resizeListBoxItems(BUtilities::Point (70 * sz, 20 * sz));
801 	RESIZE (stepBaseListBox, 730, 590, 90, 20, sz);
802 	stepBaseListBox.resizeListBox(BUtilities::Point (90 * sz, 80 * sz));
803 	stepBaseListBox.moveListBox(BUtilities::Point (0, -80 * sz));
804 	stepBaseListBox.resizeListBoxItems(BUtilities::Point (90 * sz, 20 * sz));
805 	RESIZE (padSizeListBox, 850, 590, 90, 20, sz);
806 	padSizeListBox.resizeListBox(BUtilities::Point (90 * sz, 240 * sz));
807 	padSizeListBox.moveListBox(BUtilities::Point (0, -240 * sz));
808 	padSizeListBox.resizeListBoxItems(BUtilities::Point (90 * sz, 20 * sz));
809 	for (int i = 0; i < 5; ++i) RESIZE (levelButtons[i], 958, 138 + 30 * i, 44, 22, sz);
810 	RESIZE (levelDial, 960, 290, 40, 48, sz);
811 	RESIZE (delayDisplayLabel, 958, 370, 44, 20, sz);
812 	RESIZE (increaseDelayButton, 958, 398, 44, 22, sz);
813 	RESIZE (increaseDelayButton, 958, 428, 44, 22, sz);
814 	RESIZE (decreaseDelayButton, 958, 488, 44, 22, sz);
815 	RESIZE (setStartDelayButton, 958, 458, 44, 22, sz);
816 	RESIZE (speedDial, 960, 530, 40, 48, sz);
817 	RESIZE (helpButton, 958, 588, 24, 24, sz);
818 	RESIZE (ytButton, 988, 588, 24, 24, sz);
819 	if (fileChooser) RESIZE ((*fileChooser), 200, 140, 640, 400, sz);
820 
821 	applyTheme (theme);
822 	drawPad ();
823 	show ();
824 }
825 
applyTheme(BStyles::Theme & theme)826 void BJumblrGUI::applyTheme (BStyles::Theme& theme)
827 {
828 	mContainer.applyTheme (theme);
829 	messageLabel.applyTheme (theme);
830 	pageWidget.applyTheme (theme);
831 	pageBackSymbol.applyTheme (theme);
832 	pageForwardSymbol.applyTheme (theme);
833 	for (Tab& t : tabs)
834 	{
835 		t.container.applyTheme (theme);
836 		t.icon.applyTheme (theme);
837 		t.playSymbol.applyTheme (theme);
838 		t.midiSymbol.applyTheme (theme);
839 		for (SymbolWidget& s : t.symbols) s.applyTheme (theme);
840 	}
841 
842 	sourceLabel.applyTheme (theme);
843 	calibrationLabel.applyTheme (theme);
844 	stepEditModeLabel.applyTheme (theme);
845 	stepSizeLabel.applyTheme (theme);
846 	patternSizeLabel.applyTheme (theme);
847 	padLevelLabel.applyTheme (theme);
848 	playbackLabel.applyTheme (theme);
849 	speedLabel.applyTheme (theme);
850 
851 	midiBox.applyTheme (theme);
852 	midiText.applyTheme (theme);
853 	midiStatusLabel.applyTheme (theme);
854 	midiStatusListBox.applyTheme (theme);
855 	midiChannelLabel.applyTheme (theme);
856 	midiChannelListBox.applyTheme (theme);
857 	midiNoteLabel.applyTheme (theme);
858 	midiNoteListBox.applyTheme (theme);
859 	midiValueLabel.applyTheme (theme);
860 	midiValueListBox.applyTheme (theme);
861 	midiLearnButton.applyTheme (theme);
862 	midiCancelButton.applyTheme (theme);
863 	midiOkButton.applyTheme (theme);
864 
865 	flipButton.applyTheme (theme);
866 	padSurface.applyTheme (theme);
867 	markerFwd.applyTheme (theme);
868 	markerRev.applyTheme (theme);
869 	monitorWidget.applyTheme (theme);
870 	sourceListBox.applyTheme (theme);
871 	loadButton.applyTheme (theme);
872 	sampleNameLabel.applyTheme (theme);
873 	sampleAmpDial.applyTheme (theme);
874 	playButton.applyTheme (theme);
875 	bypassButton.applyTheme (theme);
876 	stopButton.applyTheme (theme);
877 	for (int i = 0; i < EDIT_RESET; ++i) edit1Buttons[i].applyTheme (theme);
878 	for (int i = 0; i < MAXEDIT - EDIT_RESET; ++i) edit2Buttons[i].applyTheme (theme);
879 	syncWidget.applyTheme (theme);
880 	zeroStepOffsetButton.applyTheme (theme);
881 	decStepOffsetButton.applyTheme (theme);
882 	hostSyncButton.applyTheme (theme);
883 	incStepOffsetButton.applyTheme (theme);
884 	editModeListBox.applyTheme (theme);
885 	stepSizeListBox.applyTheme (theme);
886 	stepBaseListBox.applyTheme (theme);
887 	padSizeListBox.applyTheme (theme);
888 	for (int i = 0; i < 5; ++i) levelButtons[i].applyTheme (theme);
889 	levelDial.applyTheme (theme);
890 	delayDisplayLabel.applyTheme (theme);
891 	manualProgressionDelayWidget.applyTheme (theme);
892 	resetDelayButton.applyTheme (theme);
893 	increaseDelayButton.applyTheme (theme);
894 	decreaseDelayButton.applyTheme (theme);
895 	setStartDelayButton.applyTheme (theme);
896 	speedDial.applyTheme (theme);
897 	helpButton.applyTheme (theme);
898 	ytButton.applyTheme (theme);
899 	if (fileChooser) fileChooser->applyTheme (theme);
900 }
901 
onConfigureRequest(BEvents::ExposeEvent * event)902 void BJumblrGUI::onConfigureRequest (BEvents::ExposeEvent* event)
903 {
904 	Window::onConfigureRequest (event);
905 
906 	sz = (getWidth() / 1020 > getHeight() / 620 ? getHeight() / 620 : getWidth() / 1020);
907 	resize ();
908 }
909 
onCloseRequest(BEvents::WidgetEvent * event)910 void BJumblrGUI::onCloseRequest (BEvents::WidgetEvent* event)
911 {
912 	if (!event) return;
913 	Widget* requestWidget = event->getRequestWidget ();
914 	if (!requestWidget) return;
915 
916 	if (requestWidget == fileChooser)
917 	{
918 		if (fileChooser->getValue() == 1.0)
919 		{
920 			sampleNameLabel.setText (fileChooser->getFileName());
921 			samplePath = fileChooser->getPath();
922 			sampleStart = fileChooser->getStart();
923 			sampleEnd = fileChooser->getEnd();
924 			sampleLoop = fileChooser->getLoop();
925 			send_samplePath ();
926 		}
927 
928 		// Close fileChooser
929 		mContainer.release (fileChooser);	// TODO Check why this is required
930 		delete fileChooser;
931 		fileChooser = nullptr;
932 		return;
933 	}
934 
935 	Window::onCloseRequest (event);
936 }
937 
onKeyPressed(BEvents::KeyEvent * event)938 void BJumblrGUI::onKeyPressed (BEvents::KeyEvent* event)
939 {
940 	if ((event) && (event->getKey() == BDevices::KEY_SHIFT))
941 	{
942 		monitorWidget.setScrollable (true);
943 	}
944 }
945 
onKeyReleased(BEvents::KeyEvent * event)946 void BJumblrGUI::onKeyReleased (BEvents::KeyEvent* event)
947 {
948 	if ((event) && (event->getKey() == BDevices::KEY_SHIFT))
949 	{
950 		monitorWidget.setScrollable (false);
951 	}
952 }
953 
send_ui_on()954 void BJumblrGUI::send_ui_on ()
955 {
956 	uint8_t obj_buf[64];
957 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
958 
959 	LV2_Atom_Forge_Frame frame;
960 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.ui_on);
961 	lv2_atom_forge_pop(&forge, &frame);
962 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
963 }
964 
send_ui_off()965 void BJumblrGUI::send_ui_off ()
966 {
967 	uint8_t obj_buf[64];
968 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
969 
970 	LV2_Atom_Forge_Frame frame;
971 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.ui_off);
972 	lv2_atom_forge_pop(&forge, &frame);
973 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
974 }
975 
send_samplePath()976 void BJumblrGUI::send_samplePath ()
977 {
978 	std::string path = samplePath + BUTILITIES_PATH_SLASH + sampleNameLabel.getText();
979 	uint8_t obj_buf[1024];
980 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
981 
982 	LV2_Atom_Forge_Frame frame;
983 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.notify_pathEvent);
984 	lv2_atom_forge_key(&forge, uris.notify_samplePath);
985 	lv2_atom_forge_path (&forge, path.c_str(), path.size() + 1);
986 	lv2_atom_forge_key(&forge, uris.notify_sampleStart);
987 	lv2_atom_forge_long(&forge, sampleStart);
988 	lv2_atom_forge_key(&forge, uris.notify_sampleEnd);
989 	lv2_atom_forge_long(&forge, sampleEnd);
990 	lv2_atom_forge_key(&forge, uris.notify_sampleAmp);
991 	lv2_atom_forge_float(&forge, sampleAmpDial.getValue());
992 	lv2_atom_forge_key(&forge, uris.notify_sampleLoop);
993 	lv2_atom_forge_bool(&forge, int32_t (sampleLoop));
994 	lv2_atom_forge_pop(&forge, &frame);
995 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
996 }
997 
send_sampleAmp()998 void BJumblrGUI::send_sampleAmp ()
999 {
1000 	uint8_t obj_buf[1024];
1001 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
1002 
1003 	LV2_Atom_Forge_Frame frame;
1004 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.notify_pathEvent);
1005 	lv2_atom_forge_key(&forge, uris.notify_sampleAmp);
1006 	lv2_atom_forge_float(&forge, sampleAmpDial.getValue());
1007 	lv2_atom_forge_pop(&forge, &frame);
1008 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
1009 }
1010 
send_editMode()1011 void BJumblrGUI::send_editMode ()
1012 {
1013 	uint8_t obj_buf[128];
1014 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
1015 
1016 	LV2_Atom_Forge_Frame frame;
1017 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.notify_padEvent);
1018 	lv2_atom_forge_key(&forge, uris.notify_editMode);
1019 	lv2_atom_forge_int(&forge, editMode);
1020 	lv2_atom_forge_pop(&forge, &frame);
1021 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
1022 }
1023 
send_maxPage()1024 void BJumblrGUI::send_maxPage ()
1025 {
1026 	uint8_t obj_buf[128];
1027 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
1028 
1029 	LV2_Atom_Forge_Frame frame;
1030 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.notify_statusEvent);
1031 	lv2_atom_forge_key(&forge, uris.notify_maxPage);
1032 	lv2_atom_forge_int(&forge, nrPages);
1033 	lv2_atom_forge_pop(&forge, &frame);
1034 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
1035 }
1036 
send_playbackPage()1037 void BJumblrGUI::send_playbackPage ()
1038 {
1039 	uint8_t obj_buf[128];
1040 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
1041 
1042 	LV2_Atom_Forge_Frame frame;
1043 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.notify_statusEvent);
1044 	lv2_atom_forge_key(&forge, uris.notify_playbackPage);
1045 	lv2_atom_forge_int(&forge, int (pageWidget.getValue()));
1046 	lv2_atom_forge_pop(&forge, &frame);
1047 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
1048 }
1049 
send_requestMidiLearn()1050 void BJumblrGUI::send_requestMidiLearn ()
1051 {
1052 	uint8_t obj_buf[128];
1053 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
1054 
1055 	LV2_Atom_Forge_Frame frame;
1056 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.notify_statusEvent);
1057 	lv2_atom_forge_key(&forge, uris.notify_requestMidiLearn);
1058 	lv2_atom_forge_bool(&forge, midiLearnButton.getValue() != 0.0);
1059 	lv2_atom_forge_pop(&forge, &frame);
1060 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
1061 }
1062 
send_pad(int page)1063 void BJumblrGUI::send_pad (int page)
1064 {
1065 	Pad padmsg [MAXSTEPS * MAXSTEPS];
1066 	for (int r = 0; r < MAXSTEPS; ++r)
1067 	{
1068 		for (int s = 0; s < MAXSTEPS; ++s)
1069 		{
1070 			padmsg[r * MAXSTEPS + s] = pattern[page].getPad (r, s);
1071 		}
1072 	}
1073 
1074 	uint8_t obj_buf[8192];
1075 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
1076 
1077 	LV2_Atom_Forge_Frame frame;
1078 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.notify_padEvent);
1079 	lv2_atom_forge_key(&forge, uris.notify_padPage);
1080 	lv2_atom_forge_int(&forge, page);
1081 	lv2_atom_forge_key(&forge, uris.notify_padFullPattern);
1082 	lv2_atom_forge_vector(&forge, sizeof(float), uris.atom_Float, MAXSTEPS * MAXSTEPS * sizeof(Pad) / sizeof(float), (void*) padmsg);
1083 	lv2_atom_forge_pop(&forge, &frame);
1084 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
1085 }
1086 
send_pad(int page,int row,int step)1087 void BJumblrGUI::send_pad (int page, int row, int step)
1088 {
1089 	PadMessage padmsg (step, row, pattern[page].getPad (row, step));
1090 
1091 	uint8_t obj_buf[128];
1092 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
1093 
1094 	LV2_Atom_Forge_Frame frame;
1095 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.notify_padEvent);
1096 	lv2_atom_forge_key(&forge, uris.notify_padPage);
1097 	lv2_atom_forge_int(&forge, page);
1098 	lv2_atom_forge_key(&forge, uris.notify_pad);
1099 	lv2_atom_forge_vector(&forge, sizeof(float), uris.atom_Float, sizeof(PadMessage) / sizeof(float), (void*) &padmsg);
1100 	lv2_atom_forge_pop(&forge, &frame);
1101 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
1102 }
1103 
send_flip()1104 void BJumblrGUI::send_flip ()
1105 {
1106 	uint8_t obj_buf[128];
1107 	lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf));
1108 
1109 	LV2_Atom_Forge_Frame frame;
1110 	LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, uris.notify_statusEvent);
1111 	lv2_atom_forge_key(&forge, uris.notify_padFlipped);
1112 	lv2_atom_forge_bool(&forge, patternFlipped);
1113 	lv2_atom_forge_pop(&forge, &frame);
1114 	write_function(controller, CONTROL, lv2_atom_total_size(msg), uris.atom_eventTransfer, msg);
1115 }
1116 
pushPage()1117 void BJumblrGUI::pushPage ()
1118 {
1119 	if (nrPages >= MAXPAGES) return;
1120 
1121 	tabs[nrPages - 1].symbols[CLOSESYMBOL].show();
1122 	tabs[nrPages - 1].symbols[RIGHTSYMBOL].show();
1123 
1124 	tabs[nrPages].container.show();
1125 	tabs[nrPages].symbols[CLOSESYMBOL].show();
1126 	tabs[nrPages].symbols[LEFTSYMBOL].show();
1127 	tabs[nrPages].symbols[RIGHTSYMBOL].hide();
1128 
1129 	if (nrPages == MAXPAGES - 1)
1130 	{
1131 		for (Tab& t : tabs) t.symbols[ADDSYMBOL].hide();
1132 	}
1133 
1134 	++nrPages;
1135 	updatePageContainer();
1136 }
1137 
popPage()1138 void BJumblrGUI::popPage ()
1139 {
1140 	if (nrPages <= 1) return;
1141 
1142 	tabs[nrPages - 2].symbols[RIGHTSYMBOL].hide();
1143 	if (nrPages == 2) tabs[0].symbols[CLOSESYMBOL].hide();
1144 	tabs[nrPages - 1].container.hide();
1145 	for (Tab& t : tabs) t.symbols[ADDSYMBOL].show();
1146 
1147 	if (actPage >= nrPages - 1) gotoPage (nrPages - 2);
1148 	if (pageWidget.getValue() >= nrPages - 1) pageWidget.setValue (0);
1149 
1150 	--nrPages;
1151 	updatePageContainer();
1152 }
1153 
gotoPage(const int page)1154 void BJumblrGUI::gotoPage (const int page)
1155 {
1156 	if ((page < 0) || (page >= nrPages)) return;
1157 
1158 	actPage = page;
1159 	for (int i = 0; i < MAXPAGES; ++i)
1160 	{
1161 		if (i == page) tabs[i].container.rename ("activetab");
1162 		else tabs[i].container.rename ("tab");
1163 		tabs[i].container.applyTheme (theme);
1164 	}
1165 	drawPad();
1166 	updatePageContainer();
1167 }
1168 
insertPage(const int page)1169 void BJumblrGUI::insertPage (const int page)
1170 {
1171 	if ((page < 0) || (nrPages >= MAXPAGES)) return;
1172 
1173 	pushPage();
1174 	if (actPage >= page) gotoPage (actPage + 1);
1175 	if (pageWidget.getValue() >= page) pageWidget.setValue (pageWidget.getValue() + 1);
1176 
1177 	// Move pages
1178 	for (int i = nrPages - 1; i > page; --i)
1179 	{
1180 		pattern[i] = pattern[i - 1];
1181 		send_pad (i);
1182 		if (i == actPage) drawPad();
1183 		for (int j = 0; j < NR_MIDI_CTRLS; ++j)
1184 		{
1185 			tabs[i].midiWidgets[j].setValue (tabs[i - 1].midiWidgets[j].getValue());
1186 		}
1187 	}
1188 
1189 	// Init new page
1190 	pattern[page].clear();
1191 	for (int i = 0; i < MAXSTEPS; ++i) pattern[page].setPad (i, i, Pad (1.0));
1192 	send_pad (page);
1193 	if (page == actPage) drawPad();
1194 	tabs[page].midiWidgets[STATUS].setValue (0);
1195 	tabs[page].midiWidgets[CHANNEL].setValue (0);
1196 	tabs[page].midiWidgets[NOTE].setValue (128);
1197 	tabs[page].midiWidgets[VALUE].setValue (128);
1198 }
1199 
deletePage(const int page)1200 void BJumblrGUI::deletePage (const int page)
1201 {
1202 	if ((page < 0) || (page >= nrPages)) return;
1203 
1204 	if (actPage > page) gotoPage (actPage - 1);
1205 	if (pageWidget.getValue() > page) pageWidget.setValue (pageWidget.getValue() - 1);
1206 
1207 	for (int i = page; i < nrPages -1; ++i)
1208 	{
1209 		pattern[i] = pattern[i + 1];
1210 		send_pad (i);
1211 		if (i == actPage) drawPad ();
1212 		for (int j = 0; j < NR_MIDI_CTRLS; ++j)
1213 		{
1214 			tabs[i].midiWidgets[j].setValue (tabs[i + 1].midiWidgets[j].getValue());
1215 		}
1216 	}
1217 
1218 	tabs[nrPages - 1].midiWidgets[STATUS].setValue (0);
1219 	tabs[nrPages - 1].midiWidgets[CHANNEL].setValue (0);
1220 	tabs[nrPages - 1].midiWidgets[NOTE].setValue (128);
1221 	tabs[nrPages - 1].midiWidgets[VALUE].setValue (128);
1222 
1223 	popPage();
1224 	send_maxPage();
1225 }
1226 
swapPage(const int page1,const int page2)1227 void BJumblrGUI::swapPage (const int page1, const int page2)
1228 {
1229 	if ((page1 < 0) || (page1 >= nrPages) || (page2 < 0) || (page2 >= nrPages)) return;
1230 
1231 	Pattern p;
1232 	p.clear();
1233 	p = pattern[page1];
1234 	pattern[page1] = pattern[page2];
1235 	pattern[page2] = p;
1236 	send_pad (page1);
1237 	send_pad (page2);
1238 
1239 	if (actPage == page1) gotoPage (page2);
1240 	else if (actPage == page2) gotoPage (page1);
1241 
1242 	if (pageWidget.getValue() == page1) pageWidget.setValue (page2);
1243 	else if (pageWidget.getValue() == page2) pageWidget.setValue (page1);
1244 
1245 	for (int j = 0; j < NR_MIDI_CTRLS; ++j)
1246 	{
1247 		double v = tabs[page1].midiWidgets[j].getValue();
1248 		tabs[page1].midiWidgets[j].setValue (tabs[page2].midiWidgets[j].getValue());
1249 		tabs[page2].midiWidgets[j].setValue (v);
1250 	}
1251 }
1252 
updatePageContainer()1253 void BJumblrGUI::updatePageContainer()
1254 {
1255 	if (nrPages > 6) pageOffset = LIMIT (pageOffset, 0, nrPages - 6);
1256 	else pageOffset = 0;
1257 
1258 	int x0 = (pageOffset == 0 ? 0 : 12 * sz);
1259 
1260 	if (pageOffset != 0) pageBackSymbol.show();
1261 	else pageBackSymbol.hide();
1262 
1263 	if (pageOffset + 6 < nrPages) pageForwardSymbol.show();
1264 	else pageForwardSymbol.hide();
1265 
1266 	for (int p = 0; p < nrPages; ++p)
1267 	{
1268 		if ((p < pageOffset) || (p >= pageOffset + 6)) tabs[p].container.hide();
1269 		else
1270 		{
1271 			tabs[p].container.moveTo (x0 + (p - pageOffset) * 80 * sz, 0);
1272 			tabs[p].container.resize (78 * sz, 30 * sz);
1273 			tabs[p].container.show();
1274 		}
1275 	}
1276 
1277 	for (int p = nrPages; p < MAXPAGES; ++p ) tabs[p].container.hide();
1278 
1279 	pageBackSymbol.moveTo (0, 0);
1280 	pageBackSymbol.resize (10 * sz, 30 * sz);
1281 	pageForwardSymbol.moveTo (x0 + 480 * sz, 0);
1282 	pageForwardSymbol.resize (10 * sz, 30 * sz);
1283 }
1284 
validatePad(int page)1285 bool BJumblrGUI::validatePad (int page)
1286 {
1287 	bool changed = false;
1288 
1289 	// REPLACE mode
1290 	if (editMode == 1)
1291 	{
1292 		for (int s = 0; s < MAXSTEPS; ++s)
1293 		{
1294 			bool padOn = false;
1295 			for (int r = 0; r < MAXSTEPS; ++r)
1296 			{
1297 				// Clear if there is already an active pad
1298 				if (padOn)
1299 				{
1300 					if (pattern[page].getPad (r, s).level != 0.0)
1301 					{
1302 						pattern[page].setPad (r, s, Pad(0));
1303 						send_pad (page, r, s);
1304 						changed = true;
1305 					}
1306 				}
1307 
1308 				// First active pad
1309 				else if (pattern[page].getPad (r, s).level != 0.0)
1310 				{
1311 					padOn = true;
1312 				}
1313 			}
1314 
1315 			// Empty step: Set default
1316 			if (!padOn)
1317 			{
1318 				pattern[page].setPad (s, s, Pad (1.0));
1319 				send_pad (page, s, s);
1320 				changed = true;
1321 			}
1322 		}
1323 	}
1324 
1325 	return (!changed);
1326 }
1327 
validatePad(int page,int row,int step,Pad & pad)1328 bool BJumblrGUI::validatePad (int page, int row, int step, Pad& pad)
1329 {
1330 	bool changed = false;
1331 
1332 	// REPLACE mode
1333 	if (editMode == 1)
1334 	{
1335 		if (pad.level != 0.0)
1336 		{
1337 			pattern[page].setPad (row, step, pad);
1338 			send_pad (page, row, step);
1339 
1340 			for (int r = 0; r < MAXSTEPS; ++r)
1341 			{
1342 				// Clear all other active pads
1343 				if (r != row)
1344 				{
1345 					if (pattern[page].getPad (r, step).level != 0.0)
1346 					{
1347 						pattern[page].setPad (r, step, Pad(0));
1348 						send_pad (page, r, step);
1349 						changed = true;
1350 					}
1351 				}
1352 			}
1353 		}
1354 	}
1355 
1356 	else
1357 	{
1358 		pattern[page].setPad (row, step, pad);
1359 		send_pad (page, row, step);
1360 	}
1361 
1362 	return (!changed);
1363 }
1364 
valueChangedCallback(BEvents::Event * event)1365 void BJumblrGUI::valueChangedCallback(BEvents::Event* event)
1366 {
1367 	if (!event) return;
1368 	BWidgets::ValueWidget* widget = (BWidgets::ValueWidget*) event->getWidget ();
1369 	if (!widget) return;
1370 	float value = widget->getValue();
1371 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1372 	if (!ui) return;
1373 
1374 	int controllerNr = -1;
1375 
1376 	// Identify controller
1377 	for (int i = 0; i < MAXCONTROLLERS; ++i)
1378 	{
1379 		if (widget == ui->controllerWidgets[i])
1380 		{
1381 			controllerNr = i;
1382 			break;
1383 		}
1384 	}
1385 
1386 	// Controllers
1387 	if (controllerNr >= 0)
1388 	{
1389 		ui->controllers[controllerNr] = value;
1390 		ui->write_function(ui->controller, CONTROLLERS + controllerNr, sizeof(float), 0, &ui->controllers[controllerNr]);
1391 
1392 		switch (controllerNr)
1393 		{
1394 			case SOURCE:		if (value == 0.0)
1395 						{
1396 							ui->loadButton.hide();
1397 							ui->sampleNameLabel.hide();
1398 							ui->sampleAmpDial.hide();
1399 						}
1400 						else
1401 						{
1402 							ui->loadButton.show();
1403 							ui->sampleNameLabel.show();
1404 							ui->sampleAmpDial.show();
1405 						}
1406 						break;
1407 
1408 			case NR_OF_STEPS:	ui->drawPad ();
1409 						break;
1410 
1411 			case PLAY:		ui->bypassButton.setValue (value == 2.0 ? 1 : 0);
1412 						break;
1413 
1414 			case PAGE:		for (int i = 0; i < MAXPAGES; ++i)
1415 						{
1416 							if (ui->tabs[i].playSymbol.getState() != BColors::ACTIVE)
1417 							{
1418 								if (i == value) ui->tabs[i].playSymbol.setState (BColors::NORMAL);
1419 								else ui->tabs[i].playSymbol.setState (BColors::INACTIVE);
1420 							}
1421 						}
1422 						ui->drawPad();
1423 						break;
1424 
1425 			default:		if (controllerNr >= MIDI)
1426 						{
1427 							int page = (controllerNr - MIDI) / NR_MIDI_CTRLS;
1428 							int midiCtrl = (controllerNr - MIDI) % NR_MIDI_CTRLS;
1429 
1430 							if (midiCtrl == STATUS)
1431 							{
1432 								if (value < 8) ui->tabs[page].midiSymbol.setState (BColors::INACTIVE);
1433 								else ui->tabs[page].midiSymbol.setState (BColors::ACTIVE);
1434 							}
1435 						}
1436 						break;
1437 		}
1438 	}
1439 
1440 	// Other widgets
1441 	else if (widget == &ui->editModeListBox)
1442 	{
1443 		ui->editMode = ui->editModeListBox.getValue();
1444 		ui->send_editMode();
1445 		for (int i = 0; i < ui->nrPages; ++i)
1446 		{
1447 			if (!ui->validatePad(i))
1448 			{
1449 				if (i == ui->actPage) ui->drawPad();
1450 				ui->pattern[i].store ();
1451 			}
1452 		}
1453 	}
1454 
1455 	// Buttons
1456 	else if (widget == &ui->bypassButton)
1457 	{
1458 		if ((value == 0.0) && (ui->playButton.getValue() == 2.0)) ui->playButton.setValue (0.0);
1459 		else if (value == 1.0) ui->playButton.setValue (2.0);
1460 	}
1461 
1462 	else if (widget == &ui->stopButton)
1463 	{
1464 		if (value == 1.0)
1465 		{
1466 			ui->playButton.setValue (0.0);
1467 			ui->bypassButton.setValue (0.0);
1468 		}
1469 	}
1470 
1471 	else if (widget == &ui->sampleAmpDial) ui->send_sampleAmp();
1472 }
1473 
1474 
pageClickedCallback(BEvents::Event * event)1475 void BJumblrGUI::pageClickedCallback(BEvents::Event* event)
1476 {
1477 	if (!event) return;
1478 	BWidgets::Widget* widget = event->getWidget ();
1479 	if (!widget) return;
1480 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1481 	if (!ui) return;
1482 
1483 	for (int i = 0; i < ui->nrPages; ++i)
1484 	{
1485 		if (&ui->tabs[i].container == widget)
1486 		{
1487 			ui->gotoPage (i);
1488 			break;
1489 		}
1490 	}
1491 }
1492 
pageSymbolClickedCallback(BEvents::Event * event)1493 void BJumblrGUI::pageSymbolClickedCallback(BEvents::Event* event)
1494 {
1495 	if (!event) return;
1496 	SymbolWidget* widget = (SymbolWidget*)event->getWidget ();
1497 	if (!widget) return;
1498 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1499 	if (!ui) return;
1500 
1501 	for (int i = 0; i < ui->nrPages; ++i)
1502 	{
1503 		for (int j = 0; j < 4; ++j)
1504 		{
1505 			if (&ui->tabs[i].symbols[j] == widget)
1506 			{
1507 				switch (j)
1508 				{
1509 					// Symbol +
1510 					case ADDSYMBOL:		ui->insertPage (i + 1);
1511 								break;
1512 
1513 					// Symbol -
1514 					case CLOSESYMBOL: 	ui->deletePage (i);
1515 								break;
1516 
1517 					// Symbol <
1518 					case LEFTSYMBOL:	ui->swapPage (i, i - 1);
1519 								break;
1520 
1521 					// Symbol >
1522 					case RIGHTSYMBOL:	ui->swapPage (i, i + 1);
1523 								break;
1524 				}
1525 				return;
1526 			}
1527 		}
1528 	}
1529 }
1530 
pagePlayClickedCallback(BEvents::Event * event)1531 void BJumblrGUI::pagePlayClickedCallback(BEvents::Event* event)
1532 {
1533 	if (!event) return;
1534 	SymbolWidget* widget = (SymbolWidget*)event->getWidget ();
1535 	if (!widget) return;
1536 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1537 	if (!ui) return;
1538 
1539 	for (int i = 0; i < ui->nrPages; ++i)
1540 	{
1541 		if (&ui->tabs[i].playSymbol == widget)
1542 		{
1543 			ui->pageWidget.setValue (i);
1544 			break;
1545 		}
1546 	}
1547 }
1548 
pageScrollClickedCallback(BEvents::Event * event)1549 void BJumblrGUI::pageScrollClickedCallback(BEvents::Event* event)
1550 {
1551 	if (!event) return;
1552 	SymbolWidget* widget = (SymbolWidget*)event->getWidget ();
1553 	if (!widget) return;
1554 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1555 	if (!ui) return;
1556 
1557 	if (widget == &ui->pageBackSymbol) --ui->pageOffset;
1558 	else if (widget == &ui->pageForwardSymbol) ++ui->pageOffset;
1559 
1560 	ui->updatePageContainer();
1561 }
1562 
midiSymbolClickedCallback(BEvents::Event * event)1563 void BJumblrGUI::midiSymbolClickedCallback(BEvents::Event* event)
1564 {
1565 	if (!event) return;
1566 	SymbolWidget* widget = (SymbolWidget*)event->getWidget ();
1567 	if (!widget) return;
1568 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1569 	if (!ui) return;
1570 
1571 	for (int i = 0; i < ui->nrPages; ++i)
1572 	{
1573 		if (widget == &ui->tabs[i].midiSymbol)
1574 		{
1575 			ui->midiText.setText (BJUMBLR_LABEL_MIDI_PAGE " #" + std::to_string (i + 1));
1576 			ui->midiStatusListBox.setValue (ui->controllers[MIDI + i * NR_MIDI_CTRLS + STATUS]);
1577 			ui->midiChannelListBox.setValue (ui->controllers[MIDI + i * NR_MIDI_CTRLS + CHANNEL]);
1578 			ui->midiNoteListBox.setValue (ui->controllers[MIDI + i * NR_MIDI_CTRLS + NOTE]);
1579 			ui->midiValueListBox.setValue (ui->controllers[MIDI + i * NR_MIDI_CTRLS + VALUE]);
1580 			ui->midiBox.setValue (i);
1581 			ui->midiBox.show();
1582 			return;
1583 		}
1584 	}
1585 }
1586 
midiButtonClickedCallback(BEvents::Event * event)1587 void BJumblrGUI::midiButtonClickedCallback(BEvents::Event* event)
1588 {
1589 	if (!event) return;
1590 	BWidgets::ValueWidget* widget = (BWidgets::ValueWidget*) event->getWidget ();
1591 	if (!widget) return;
1592 	float value = widget->getValue();
1593 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1594 	if (!ui) return;
1595 
1596 	if (widget == &ui->midiLearnButton)
1597 	{
1598 		if (value == 1) ui->send_requestMidiLearn();
1599 	}
1600 
1601 	else if (widget == &ui->midiCancelButton)
1602 	{
1603 		if (value == 1)
1604 		{
1605 			ui->midiLearnButton.setValue (0);
1606 			ui->midiBox.hide();
1607 		}
1608 	}
1609 
1610 	else if (widget == &ui->midiOkButton)
1611 	{
1612 		if (value == 1)
1613 		{
1614 			int page = ui->midiBox.getValue();
1615 			ui->midiLearnButton.setValue (0);
1616 			ui->tabs[page].midiWidgets[STATUS].setValue (ui->midiStatusListBox.getValue());
1617 			ui->tabs[page].midiWidgets[CHANNEL].setValue (ui->midiChannelListBox.getValue());
1618 			ui->tabs[page].midiWidgets[NOTE].setValue (ui->midiNoteListBox.getValue());
1619 			ui->tabs[page].midiWidgets[VALUE].setValue (ui->midiValueListBox.getValue());
1620 			ui->midiBox.hide();
1621 		}
1622 	}
1623 }
1624 
midiStatusChangedCallback(BEvents::Event * event)1625 void BJumblrGUI::midiStatusChangedCallback(BEvents::Event* event)
1626 {
1627 	if (!event) return;
1628 	BWidgets::PopupListBox* widget = (BWidgets::PopupListBox*) event->getWidget ();
1629 	if (!widget) return;
1630 	float value = widget->getValue();
1631 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1632 	if (!ui) return;
1633 
1634 	BWidgets::PopupListBox& nlb = ui->midiNoteListBox;
1635 	BWidgets::Label& nl = ui->midiNoteLabel;
1636 	int nr = nlb.getValue();
1637 
1638 	if (value == 11)
1639 	{
1640 		nlb = BWidgets::PopupListBox
1641 		(
1642 			210 * ui->sz, 50 * ui->sz, 160 * ui->sz, 20 * ui->sz, 0, 20 * ui->sz, 160 * ui->sz, 360 *ui->sz,
1643 			"menu",
1644 			BItems::ItemList ({CCLIST}),
1645 			0
1646 		);
1647 		nl.setText (BJUMBLR_LABEL_CC);
1648 	}
1649 
1650 	else
1651 	{
1652 		nlb = BWidgets::PopupListBox
1653 		(
1654 			210 * ui->sz, 50 * ui->sz, 160 * ui->sz, 20 * ui->sz, 0, 20 * ui->sz, 160 * ui->sz, 360 * ui->sz,
1655 			"menu",
1656 			BItems::ItemList ({NOTELIST}),
1657 			0
1658 		);
1659 		nl.setText (BJUMBLR_LABEL_NOTE);
1660 	}
1661 
1662 	nlb.resizeListBoxItems(BUtilities::Point (160 * ui->sz, 20 * ui->sz));
1663 	nlb.applyTheme (ui->theme);
1664 	nlb.setValue (nr);
1665 }
1666 
levelChangedCallback(BEvents::Event * event)1667 void BJumblrGUI::levelChangedCallback(BEvents::Event* event)
1668 {
1669 	if (!event) return;
1670 	BWidgets::ValueWidget* widget = (BWidgets::ValueWidget*) event->getWidget ();
1671 	if (!widget) return;
1672 	float value = widget->getValue();
1673 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1674 	if (!ui) return;
1675 
1676 	if (widget == &ui->levelDial)
1677 	{
1678 		for (int i = 0; i < 5; ++i)
1679 		{
1680 			if (value == 1.0 - double (i) * 0.25) ui->levelButtons[i].setValue (1.0);
1681 			else ui->levelButtons[i].setValue (0.0);
1682 		}
1683 	}
1684 
1685 	else
1686 	{
1687 
1688 		int buttonNr = -1;
1689 
1690 		// Identify controller
1691 		for (int i = 0; i < 5; ++i)
1692 		{
1693 			if (widget == &ui->levelButtons[i])
1694 			{
1695 				buttonNr = i;
1696 				break;
1697 			}
1698 		}
1699 
1700 		// Controllers
1701 		if ((buttonNr >= 0) && (value == 1.0)) ui->levelDial.setValue (1.0 - double (buttonNr) * 0.25);
1702 	}
1703 }
1704 
edit1ChangedCallback(BEvents::Event * event)1705 void BJumblrGUI::edit1ChangedCallback(BEvents::Event* event)
1706 {
1707 	if (!event) return;
1708 	BWidgets::ValueWidget* widget = (BWidgets::ValueWidget*) event->getWidget ();
1709 	if (!widget) return;
1710 	float value = widget->getValue();
1711 	if (value != 1.0) return;
1712 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1713 	if (!ui) return;
1714 
1715 	// Identify editButtons: CUT ... PASTE
1716 	int widgetNr = -1;
1717 	for (int i = 0; i < EDIT_RESET; ++i)
1718 	{
1719 		if (widget == &ui->edit1Buttons[i])
1720 		{
1721 			widgetNr = i;
1722 			break;
1723 		}
1724 	}
1725 
1726 	// Untoggle all other edit1Buttons
1727 	if (widgetNr >= 0)
1728 	{
1729 		// Allow only one button pressed
1730 		for (int i = 0; i < EDIT_RESET; ++i)
1731 		{
1732 			if (i != widgetNr) ui->edit1Buttons[i].setValue (0.0);
1733 		}
1734 	}
1735 }
1736 
edit2ChangedCallback(BEvents::Event * event)1737 void BJumblrGUI::edit2ChangedCallback(BEvents::Event* event)
1738 {
1739 	if (!event) return;
1740 	BWidgets::ValueWidget* widget = (BWidgets::ValueWidget*) event->getWidget ();
1741 	if (!widget) return;
1742 	float value = widget->getValue();
1743 	if (value != 1.0) return;
1744 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1745 	if (!ui) return;
1746 
1747 	int page = ui->actPage;
1748 
1749 	// Identify editButtons: RESET ... REDO
1750 	int widgetNr = -1;
1751 	for (int i = 0; i < MAXEDIT - EDIT_RESET; ++i)
1752 	{
1753 		if (widget == &ui->edit2Buttons[i])
1754 		{
1755 			widgetNr = i + EDIT_RESET;
1756 			break;
1757 		}
1758 	}
1759 
1760 	// RESET ... REDO
1761 	switch (widgetNr)
1762 	{
1763 		case EDIT_RESET:
1764 		{
1765 			if (ui->wheelScrolled)
1766 			{
1767 				ui->pattern[page].store ();
1768 				ui->wheelScrolled = false;
1769 			}
1770 
1771 			Pad p0 = Pad ();
1772 			for (int r = 0; r < MAXSTEPS; ++r)
1773 			{
1774 				for (int s = 0; s < MAXSTEPS; ++s)
1775 				{
1776 					if (s == r) ui->pattern[page].setPad (r, s, Pad (1.0));
1777 					else ui->pattern[page].setPad (r, s, p0);
1778 					ui->send_pad (page, r, s);
1779 				}
1780 			}
1781 
1782 			ui->drawPad ();
1783 			ui->pattern[page].store ();
1784 		}
1785 		break;
1786 
1787 		case EDIT_UNDO:
1788 		{
1789 			std::vector<PadMessage> padMessages = ui->pattern[page].undo ();
1790 			for (PadMessage const& p : padMessages)
1791 			{
1792 				size_t r = LIMIT (p.row, 0, MAXSTEPS);
1793 				size_t s = LIMIT (p.step, 0, MAXSTEPS);
1794 				ui->send_pad (page, r, s);
1795 			}
1796 			ui->validatePad (page);
1797 			ui->drawPad ();
1798 		}
1799 		break;
1800 
1801 		case EDIT_REDO:
1802 		{
1803 			std::vector<PadMessage> padMessages = ui->pattern[page].redo ();
1804 			for (PadMessage const& p : padMessages)
1805 			{
1806 				size_t r = LIMIT (p.row, 0, MAXSTEPS);
1807 				size_t s = LIMIT (p.step, 0, MAXSTEPS);
1808 				ui->send_pad (page, r, s);
1809 			}
1810 			ui->validatePad (page);
1811 			ui->drawPad ();
1812 		}
1813 		break;
1814 
1815 		default:	break;
1816 	}
1817 }
1818 
padsPressedCallback(BEvents::Event * event)1819 void BJumblrGUI::padsPressedCallback (BEvents::Event* event)
1820 {
1821 	if (!event) return;
1822 	BEvents::PointerEvent* pointerEvent = (BEvents::PointerEvent*) event;
1823 	BWidgets::DrawingSurface* widget = (BWidgets::DrawingSurface*) event->getWidget ();
1824 	if (!widget) return;
1825 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
1826 	if (!ui) return;
1827 
1828 	int page = ui->actPage;
1829 
1830 	if
1831 	(
1832 		(event->getEventType () == BEvents::BUTTON_PRESS_EVENT) ||
1833 		(event->getEventType () == BEvents::BUTTON_RELEASE_EVENT) ||
1834 		(event->getEventType () == BEvents::POINTER_DRAG_EVENT)
1835 	)
1836 	{
1837 		if (ui->wheelScrolled)
1838 		{
1839 			ui->pattern[page].store ();
1840 			ui->wheelScrolled = false;
1841 		}
1842 
1843 		// Get size of drawing area
1844 		const double width = ui->padSurface.getEffectiveWidth ();
1845 		const double height = ui->padSurface.getEffectiveHeight ();
1846 
1847 		const int maxstep = ui->controllerWidgets[NR_OF_STEPS]->getValue ();
1848 		const int s0 = maxstep - 1 - int ((pointerEvent->getPosition ().y - widget->getYOffset()) / (height / maxstep));
1849 		const int r0 = (pointerEvent->getPosition ().x - widget->getXOffset()) / (width / maxstep);
1850 		const int step = (ui->patternFlipped ? r0 : s0);
1851 		const int row = (ui->patternFlipped ? s0 : r0);
1852 
1853 
1854 		if ((event->getEventType () == BEvents::BUTTON_PRESS_EVENT) || (event->getEventType () == BEvents::POINTER_DRAG_EVENT))
1855 		{
1856 
1857 			if ((row >= 0) && (row < maxstep) && (step >= 0) && (step < maxstep))
1858 			{
1859 				Pad oldPad = ui->pattern[page].getPad (row, step);
1860 
1861 				// Left button
1862 				if (pointerEvent->getButton() == BDevices::LEFT_BUTTON)
1863 				{
1864 					// Check if edit mode
1865 					int editNr = -1;
1866 					for (int i = 0; i < EDIT_RESET; ++i)
1867 					{
1868 						if (ui->edit1Buttons[i].getValue() != 0.0)
1869 						{
1870 							editNr = i;
1871 							break;
1872 						}
1873 					}
1874 
1875 					// Edit
1876 					if (editNr >= 0)
1877 					{
1878 						if ((editNr == EDIT_CUT) || (editNr == EDIT_COPY) || (editNr == EDIT_XFLIP) || (editNr == EDIT_YFLIP))
1879 						{
1880 							if (ui->clipBoard.ready)
1881 							{
1882 								ui->clipBoard.origin = std::make_pair (row, step);
1883 								ui->clipBoard.extends = std::make_pair (0, 0);
1884 								ui->clipBoard.ready = false;
1885 								ui->drawPad (row, step);
1886 							}
1887 
1888 							else
1889 							{
1890 								std::pair<int, int> newExtends = std::make_pair (row - ui->clipBoard.origin.first, step - ui->clipBoard.origin.second);
1891 								if (newExtends != ui->clipBoard.extends)
1892 								{
1893 									ui->clipBoard.extends = newExtends;
1894 									ui->drawPad ();
1895 								}
1896 							}
1897 						}
1898 
1899 						else if (editNr == EDIT_PASTE)
1900 						{
1901 							if (!ui->clipBoard.data.empty ())
1902 							{
1903 								bool valid = true;
1904 								const int f = (ui->patternFlipped ? -1 : 1);
1905 								for (int r = 0; r < int (ui->clipBoard.data.size ()); ++r)
1906 								{
1907 									for (int s = 0; s < int (ui->clipBoard.data[r].size ()); ++s)
1908 									{
1909 										if
1910 										(
1911 											(row + f * r >= 0) &&
1912 											(row + f * r < maxstep) &&
1913 											(step - f * s >= 0) &&
1914 											(step - f * s < maxstep)
1915 										)
1916 										{
1917 											const int clr = (ui->patternFlipped ? ui->clipBoard.data.size () - 1 - r : r);
1918 											const int cls = (ui->patternFlipped ? ui->clipBoard.data[r].size () - 1 - s : s);
1919 											if (!ui->validatePad (page, row + f * r, step - f * s, ui->clipBoard.data.at(clr).at(cls)))
1920 											{
1921 												valid = false;
1922 											}
1923 											else if (valid) ui->drawPad (row + f * r, step - f * s);
1924 										}
1925 									}
1926 								}
1927 								if (!valid) ui->drawPad();
1928 							}
1929 						}
1930 
1931 					}
1932 
1933 					// Set (or unset) pad
1934 					else
1935 					{
1936 						if (!ui->padPressed) ui->deleteMode = ((oldPad.level == float (ui->levelDial.getValue())) && (ui->editMode != 1));
1937 						Pad newPad = (ui->deleteMode ? Pad (0.0) : Pad (ui->levelDial.getValue()));
1938 						if (!ui->validatePad (page, row, step, newPad)) ui->drawPad();
1939 						else ui->drawPad (row,step);
1940 					}
1941 
1942 					ui->padPressed = true;
1943 				}
1944 
1945 				else if (pointerEvent->getButton() == BDevices::RIGHT_BUTTON)
1946 				{
1947 					ui->levelDial.setValue (ui->pattern[page].getPad (row, step).level);
1948 				}
1949 			}
1950 		}
1951 
1952 		else if ((event->getEventType () == BEvents::BUTTON_RELEASE_EVENT) && (pointerEvent->getButton() == BDevices::LEFT_BUTTON))
1953 		{
1954 			// Check if edit mode
1955 			int editNr = -1;
1956 			for (int i = 0; i < EDIT_RESET; ++i)
1957 			{
1958 				if (ui->edit1Buttons[i].getValue() != 0.0)
1959 				{
1960 					editNr = i;
1961 					break;
1962 				}
1963 			}
1964 
1965 			// Edit mode
1966 			if (editNr >= 0)
1967 			{
1968 				if ((editNr == EDIT_CUT) || (editNr == EDIT_COPY) || (editNr == EDIT_XFLIP) || (editNr == EDIT_YFLIP))
1969 				{
1970 					int clipRMin = ui->clipBoard.origin.first;
1971 					int clipRMax = ui->clipBoard.origin.first + ui->clipBoard.extends.first;
1972 					if (clipRMin > clipRMax) std::swap (clipRMin, clipRMax);
1973 					int clipSMin = ui->clipBoard.origin.second;
1974 					int clipSMax = ui->clipBoard.origin.second + ui->clipBoard.extends.second;
1975 					if (clipSMin > clipSMax) std::swap (clipSMin, clipSMax);
1976 
1977 					// XFLIP
1978 					// No need to validate
1979 					if (((editNr == EDIT_XFLIP) && (!ui->patternFlipped)) || ((editNr == EDIT_YFLIP) && (ui->patternFlipped)))
1980 					{
1981 						for (int dr = 0; dr < int ((clipRMax + 1 - clipRMin) / 2); ++dr)
1982 						{
1983 							for (int s = clipSMin; s <= clipSMax; ++s)
1984 							{
1985 								Pad pd = ui->pattern[page].getPad (clipRMin + dr, s);
1986 								ui->pattern[page].setPad (clipRMin + dr, s, ui->pattern[page].getPad (clipRMax -dr, s));
1987 								ui->send_pad (page, clipRMin + dr, s);
1988 								ui->pattern[page].setPad (clipRMax - dr, s, pd);
1989 								ui->send_pad (page, clipRMax - dr, s);
1990 							}
1991 						}
1992 						ui->pattern[page].store ();
1993 					}
1994 
1995 					// YFLIP
1996 					// Validation required for REPLACE mode
1997 					if (((editNr == EDIT_YFLIP) && (!ui->patternFlipped)) || ((editNr == EDIT_XFLIP) && (ui->patternFlipped)))
1998 					{
1999 						// Temp. copy selection
2000 						Pad pads[clipRMax + 1 - clipRMin][clipSMax + 1 - clipSMin];
2001 						for (int ds = 0; ds <= clipSMax - clipSMin; ++ds)
2002 						{
2003 							for (int dr = 0; dr <= clipRMax - clipRMin; ++dr)
2004 							{
2005 								pads[dr][ds] = ui->pattern[page].getPad (clipRMin + dr, clipSMin + ds);
2006 							}
2007 						}
2008 
2009 						// X flip temp. copy & paste selection
2010 						bool valid = true;
2011 						for (int ds = 0; ds <= clipSMax - clipSMin; ++ds)
2012 						{
2013 							for (int dr = 0; dr <= clipRMax - clipRMin; ++dr)
2014 							{
2015 								if (!ui->validatePad (page, clipRMin + dr, clipSMax - ds, pads[dr][ds]))
2016 								{
2017 									valid = false;
2018 								}
2019 								else if (valid) ui->drawPad (clipRMin + dr, clipSMax - ds);
2020 							}
2021 						}
2022 						if (!valid) ui->drawPad();
2023 						ui->pattern[page].store ();
2024 					}
2025 
2026 					// Store selected data in clipboard after flip (XFLIP, YFLIP)
2027 					// Or store selected data in clipboard before deletion (CUT)
2028 					// Or store selected data anyway (COPY)
2029 					ui->clipBoard.data.clear ();
2030 					for (int r = clipRMin; r <= clipRMax; ++r)
2031 					{
2032 						std::vector<Pad> padRow;
2033 						padRow.clear ();
2034 						for (int s = clipSMax; s >= clipSMin; --s) padRow.push_back (ui->pattern[page].getPad (r, s));
2035 						ui->clipBoard.data.push_back (padRow);
2036 					}
2037 
2038 					// CUT
2039 					// Validation required for REPLACE mode
2040 					if (editNr == EDIT_CUT)
2041 					{
2042 						for (int s = clipSMin; s <= clipSMax; ++s)
2043 						{
2044 							bool empty = false;
2045 
2046 							for (int r = clipRMin; r <= clipRMax; ++r)
2047 							{
2048 								// Limit action to not empty pads
2049 								if (ui->pattern[page].getPad (r, s) != Pad())
2050 								{
2051 									// REPLACE mode: delete everything except default pads
2052 									if (ui->editMode == 1)
2053 									{
2054 										if (r != s)
2055 										{
2056 											ui->pattern[page].setPad (r, s,  Pad ());
2057 											ui->send_pad (page, r, s);
2058 											empty = true;
2059 										}
2060 
2061 										else empty = true;
2062 									}
2063 
2064 									// ADD mode: simply delete
2065 									else
2066 									{
2067 										ui->pattern[page].setPad (r, s,  Pad ());
2068 										ui->send_pad (page, r, s);
2069 									}
2070 								}
2071 							}
2072 
2073 							// Emptied column in REPLACE mode: set default
2074 							if (empty)
2075 							{
2076 								ui->pattern[page].setPad (s, s,  Pad (1.0));
2077 								ui->send_pad (page, s, s);
2078 							}
2079 						}
2080 						ui->pattern[page].store ();
2081 					}
2082 
2083 					ui->clipBoard.ready = true;
2084 					ui->drawPad ();
2085 				}
2086 			}
2087 
2088 			else
2089 			{
2090 				ui->padPressed = false;
2091 				ui->pattern[page].store ();
2092 			}
2093 		}
2094 	}
2095 }
2096 
padsScrolledCallback(BEvents::Event * event)2097 void BJumblrGUI::padsScrolledCallback (BEvents::Event* event)
2098 {
2099 	if ((event) && (event->getWidget ()) && (event->getWidget()->getMainWindow()) &&
2100 		((event->getEventType () == BEvents::WHEEL_SCROLL_EVENT)))
2101 	{
2102 		BWidgets::DrawingSurface* widget = (BWidgets::DrawingSurface*) event->getWidget ();
2103 		BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
2104 		BEvents::WheelEvent* wheelEvent = (BEvents::WheelEvent*) event;
2105 
2106 		// Get size of drawing area
2107 		const double width = ui->padSurface.getEffectiveWidth ();
2108 		const double height = ui->padSurface.getEffectiveHeight ();
2109 
2110 		const int page = ui->actPage;
2111 		const int maxstep = ui->controllerWidgets[NR_OF_STEPS]->getValue ();
2112 		const int s0 = maxstep - 1 - int ((wheelEvent->getPosition ().y - widget->getYOffset()) / (height / maxstep));
2113 		const int r0 = (wheelEvent->getPosition ().x - widget->getXOffset()) / (width / maxstep);
2114 		const int step = (ui->patternFlipped ? r0 : s0);
2115 		const int row = (ui->patternFlipped ? s0 : r0);
2116 
2117 		if ((row >= 0) && (row < maxstep) && (step >= 0) && (step < maxstep))
2118 		{
2119 			Pad pd = ui->pattern[page].getPad (row, step);
2120 			pd.level = LIMIT (pd.level + 0.01 * wheelEvent->getDelta().y, 0.0, 1.0);
2121 			if (!ui->validatePad (page, row, step, pd)) ui->drawPad();
2122 			else ui->drawPad (row, step);
2123 			ui->wheelScrolled = true;
2124 		}
2125 	}
2126 }
2127 
padsFocusedCallback(BEvents::Event * event)2128 void BJumblrGUI::padsFocusedCallback (BEvents::Event* event)
2129 {
2130 	if (!event) return;
2131 	BEvents::FocusEvent* focusEvent = (BEvents::FocusEvent*) event;
2132 	BWidgets::DrawingSurface* widget = (BWidgets::DrawingSurface*) event->getWidget ();
2133 	if (!widget) return;
2134 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
2135 	if (!ui) return;
2136 
2137 	// Get size of drawing area
2138 	const double width = ui->padSurface.getEffectiveWidth ();
2139 	const double height = ui->padSurface.getEffectiveHeight ();
2140 
2141 	const int page = ui->actPage;
2142 	const int maxstep = ui->controllerWidgets[NR_OF_STEPS]->getValue ();
2143 	const int s0 = maxstep - 1 - int ((focusEvent->getPosition ().y - widget->getYOffset()) / (height / maxstep));
2144 	const int r0 = (focusEvent->getPosition ().x - widget->getXOffset()) / (width / maxstep);
2145 	const int step = (ui->patternFlipped ? r0 : s0);
2146 	const int row = (ui->patternFlipped ? s0 : r0);
2147 
2148 	if ((row >= 0) && (row < maxstep) && (step >= 0) && (step < maxstep))
2149 	{
2150 		ui->padSurface.focusText.setText
2151 		(
2152 			BJUMBLR_LABEL_ROW ": " + std::to_string (row + 1) + "\n" +
2153 			BJUMBLR_LABEL_STEP ": " + std::to_string (step + 1) + "\n" +
2154 			BJUMBLR_LABEL_LEVEL ": " + BUtilities::to_string (ui->pattern[page].getPad (row, step).level, "%1.2f")
2155 		);
2156 	}
2157 }
2158 
syncButtonClickedCallback(BEvents::Event * event)2159 void BJumblrGUI::syncButtonClickedCallback(BEvents::Event* event)
2160 {
2161 	if (!event) return;
2162 	BWidgets::ValueWidget* widget = (BWidgets::ValueWidget*) event->getWidget ();
2163 	if (!widget) return;
2164 	float value = widget->getValue();
2165 	if (value != 1.0) return;
2166 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
2167 	if (!ui) return;
2168 
2169 	int offset = 0;
2170 
2171 	if (widget == &ui->zeroStepOffsetButton)
2172 	{
2173 		offset = (int (ui->controllers[NR_OF_STEPS] - int (ui->cursor) + ui->controllers[STEP_OFFSET])) % int (ui->controllers[NR_OF_STEPS]);
2174 	}
2175 
2176 	else if (widget == &ui->decStepOffsetButton)
2177 	{
2178 		offset = (int (ui->controllers[STEP_OFFSET] + ui->controllers[NR_OF_STEPS]) - 1) % int (ui->controllers[NR_OF_STEPS]);
2179 	}
2180 
2181 	else if (widget == &ui->hostSyncButton) offset = 0;
2182 
2183 	else if (widget == &ui->incStepOffsetButton)
2184 	{
2185 		offset = int (ui->controllers[STEP_OFFSET] + 1) % int (ui->controllers[NR_OF_STEPS]);
2186 	}
2187 
2188 	else return;
2189 
2190 	ui->syncWidget.setValue (offset);
2191 }
2192 
loadButtonClickedCallback(BEvents::Event * event)2193 void BJumblrGUI::loadButtonClickedCallback (BEvents::Event* event)
2194 {
2195 	if (!event) return;
2196 	BWidgets::Widget* widget = event->getWidget ();
2197 	if (!widget) return;
2198 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
2199 	if (!ui) return;
2200 
2201 	if (ui->fileChooser) delete ui->fileChooser;
2202 	ui->fileChooser = new SampleChooser
2203 	(
2204 		200, 140, 640, 400, "filechooser", ui->samplePath,
2205 		std::vector<BWidgets::FileFilter>
2206 		{
2207 			BWidgets::FileFilter {BJUMBLR_LABEL_ALL_FILES, std::regex (".*")},
2208 			BWidgets::FileFilter {BJUMBLR_LABEL_AUDIO_FILES, std::regex (".*\\.((wav)|(wave)|(aif)|(aiff)|(au)|(sd2)|(flac)|(caf)|(ogg)|(mp3))$", std::regex_constants::icase)}
2209 		},
2210 		std::vector<std::string>
2211 		{
2212 			BJUMBLR_LABEL_OK, BJUMBLR_LABEL_OPEN, BJUMBLR_LABEL_CANCEL,
2213 			"", "", BJUMBLR_LABEL_NEW_FOLDER, BJUMBLR_LABEL_CANT_CREATE_NEW_FOLDER, BJUMBLR_LABEL_PLAY_AS_LOOP,
2214 			BJUMBLR_LABEL_FILE, BJUMBLR_LABEL_SELECTION_START, BJUMBLR_LABEL_SELECTION_END,
2215 			BJUMBLR_LABEL_FRAMES, BJUMBLR_LABEL_NO_FILE_SELECTED
2216 		}
2217 	);
2218 	if (ui->fileChooser)
2219 	{
2220 		const std::string filename = ui->sampleNameLabel.getText();
2221 		if (filename != "")
2222 		{
2223 			ui->fileChooser->setFileName (ui->sampleNameLabel.getText());
2224 			ui->fileChooser->setStart (ui->sampleStart);
2225 			ui->fileChooser->setEnd (ui->sampleEnd);
2226 			ui->fileChooser->setLoop (ui->sampleLoop);
2227 		}
2228 
2229 		RESIZE ((*ui->fileChooser), 200, 140, 640, 400, ui->sz);
2230 		ui->fileChooser->applyTheme (ui->theme);
2231 		ui->fileChooser->selectFilter (BJUMBLR_LABEL_AUDIO_FILES);
2232 		ui->mContainer.add (*ui->fileChooser);
2233 	}
2234 }
2235 
delayButtonsClickedCallback(BEvents::Event * event)2236 void BJumblrGUI::delayButtonsClickedCallback (BEvents::Event* event)
2237 {
2238 	if (!event) return;
2239 	HaloButton* widget = (HaloButton*) event->getWidget ();
2240 	if (!widget) return;
2241 	double val = widget->getValue();
2242 	if (!val) return;
2243 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
2244 	if (!ui) return;
2245 
2246 	if (widget == &ui->resetDelayButton) ui->manualProgressionDelayWidget.setValue (0.0);
2247 	else if (widget == &ui->increaseDelayButton) ui->manualProgressionDelayWidget.setValue (ui->manualProgressionDelayWidget.getValue() + 1.0);
2248 	else if (widget == &ui->decreaseDelayButton) ui->manualProgressionDelayWidget.setValue (ui->manualProgressionDelayWidget.getValue() - 1.0);
2249 	else if (widget == &ui->setStartDelayButton)
2250 	{
2251 		ui->manualProgressionDelayWidget.setValue
2252 		(
2253 			floormod
2254 			(
2255 				ui->manualProgressionDelayWidget.getValue() - floorfrac (ui->cursor),
2256 				ui->controllerWidgets[NR_OF_STEPS]->getValue ()
2257 			)
2258 		);
2259 	}
2260 }
2261 
patternFlippedClickedCallback(BEvents::Event * event)2262 void BJumblrGUI::patternFlippedClickedCallback (BEvents::Event* event)
2263 {
2264 	if (!event) return;
2265 	BWidgets::Widget* widget = event->getWidget ();
2266 	if (!widget) return;
2267 	BJumblrGUI* ui = (BJumblrGUI*) widget->getMainWindow();
2268 	if (!ui) return;
2269 	ui->patternFlipped = !ui->patternFlipped;
2270 	ui->monitorWidget.flip (ui->patternFlipped);
2271 	ui->setMarkers();
2272 	ui->drawPad();
2273 	ui->send_flip();
2274 }
2275 
helpButtonClickedCallback(BEvents::Event * event)2276 void BJumblrGUI::helpButtonClickedCallback (BEvents::Event* event)
2277 {
2278 	char cmd[] = WWW_BROWSER_CMD;
2279 	char param[] = HELP_URL;
2280 	char* argv[] = {cmd, param, NULL};
2281 	std::cerr << "BJumblr.lv2#GUI: Call " << HELP_URL << " for help.\n";
2282 	if (BUtilities::vsystem (argv) == -1) std::cerr << "BJumblr.lv2#GUI: Couldn't fork.\n";
2283 }
2284 
ytButtonClickedCallback(BEvents::Event * event)2285 void BJumblrGUI::ytButtonClickedCallback (BEvents::Event* event)
2286 {
2287 	char cmd[] = WWW_BROWSER_CMD;
2288 	char param[] = YT_URL;
2289 	char* argv[] = {cmd, param, NULL};
2290 	std::cerr << "BJumblr.lv2#GUI: Call " << YT_URL << " for tutorial video.\n";
2291 	if (BUtilities::vsystem (argv) == -1) std::cerr << "BJumblr.lv2#GUI: Couldn't fork.\n";
2292 }
2293 
setMarkers()2294 void BJumblrGUI::setMarkers()
2295 {
2296 	const double maxstep = controllerWidgets[NR_OF_STEPS]->getValue ();
2297 	markerFwd.resize (20 * sz, 20 * sz);
2298 	markerRev.resize (20 * sz, 20 * sz);
2299 	if (patternFlipped)
2300 	{
2301 		markerFwd.setMarker (MARKER_DOWN);
2302 		markerRev.setMarker (MARKER_UP);
2303 		markerFwd.moveTo ((20 + (0.5 + int (cursor)) * (920.0 / maxstep) - 10) * sz, 110 * sz);
2304 		markerRev.moveTo ((20 + (0.5 + int (cursor)) * (920.0 / maxstep) - 10) * sz, 560 * sz);
2305 	}
2306 	else
2307 	{
2308 		markerFwd.setMarker (MARKER_RIGHT);
2309 		markerRev.setMarker (MARKER_LEFT);
2310 		markerFwd.moveTo (0, (130 + (maxstep - 0.5 - int (cursor)) * (430.0 / maxstep) - 10) * sz);
2311 		markerRev.moveTo (940 * sz, (130 + (maxstep - 0.5 - int (cursor)) * (430.0 / maxstep) - 10) *sz);
2312 	}
2313 }
2314 
drawPad()2315 void BJumblrGUI::drawPad ()
2316 {
2317 	cairo_surface_t* surface = padSurface.getDrawingSurface();
2318 	cairo_t* cr = cairo_create (surface);
2319 	int maxstep = controllerWidgets[NR_OF_STEPS]->getValue ();
2320 	for (int row = 0; row < maxstep; ++row)
2321 	{
2322 		for (int step = 0; step < maxstep; ++step) drawPad (cr, row, step);
2323 	}
2324 	cairo_destroy (cr);
2325 	padSurface.update();
2326 }
2327 
drawPad(int row,int step)2328 void BJumblrGUI::drawPad (int row, int step)
2329 {
2330 	cairo_surface_t* surface = padSurface.getDrawingSurface();
2331 	cairo_t* cr = cairo_create (surface);
2332 	drawPad (cr, row, step);
2333 	cairo_destroy (cr);
2334 	padSurface.update();
2335 }
2336 
drawPad(cairo_t * cr,int row,int step)2337 void BJumblrGUI::drawPad (cairo_t* cr, int row, int step)
2338 {
2339 	int maxstep = controllerWidgets[NR_OF_STEPS]->getValue ();
2340 	if ((!cr) || (cairo_status (cr) != CAIRO_STATUS_SUCCESS) || (row < 0) || (row >= maxstep) || (step < 0) ||
2341 		(step >= maxstep)) return;
2342 
2343 	// Get size of drawing area
2344 	const double width = padSurface.getEffectiveWidth ();
2345 	const double height = padSurface.getEffectiveHeight ();
2346 	const double w = width / maxstep;
2347 	const double h = height / maxstep;
2348 	const double x = (patternFlipped ? step : row) * w;
2349 	const double y = (maxstep - 1 - (patternFlipped ? row : step)) * h;
2350 	const double xr = round (x);
2351 	const double yr = round (y);
2352 	const double wr = round (x + w) - xr;
2353 	const double hr = round (y + h) - yr;
2354 
2355 	// Draw background
2356 	// Odd or even?
2357 	BColors::Color bg =
2358 	(
2359 		int (cursor) == step ?
2360 		BColors::Color (0.25, 0.25, 0.0, 1.0) :
2361 		((int (row / 4) % 2) ? oddPadBgColor : evenPadBgColor)
2362 	);
2363 
2364 	// Highlight selection
2365 	int clipRMin = clipBoard.origin.first;
2366 	int clipRMax = clipBoard.origin.first + clipBoard.extends.first;
2367 	if (clipRMin > clipRMax) std::swap (clipRMin, clipRMax);
2368 	int clipSMin = clipBoard.origin.second;
2369 	int clipSMax = clipBoard.origin.second + clipBoard.extends.second;
2370 	if (clipSMin > clipSMax) std::swap (clipSMin, clipSMax);
2371 	if ((!clipBoard.ready) && (row >= clipRMin) && (row <= clipRMax) && (step >= clipSMin) && (step <= clipSMax)) bg.applyBrightness (1.5);
2372 
2373 	cairo_set_source_rgba (cr, CAIRO_RGBA (bg));
2374 	cairo_set_line_width (cr, 0.0);
2375 	cairo_rectangle (cr, xr, yr, wr, hr);
2376 	cairo_fill (cr);
2377 
2378 	// Draw pad
2379 	Pad pd = pattern[actPage].getPad (row, step);
2380 	Pad pdc = pattern[actPage].getPad (row, cursor);
2381 	BColors::Color color = BColors::yellow;
2382 	color.applyBrightness (pd.level - 1.0);
2383 	if ((tabs[actPage].playSymbol.getState() == BColors::ACTIVE) && (pdc.level != 0.0)) color.applyBrightness (pdc.level * 0.75);
2384 	drawButton (cr, xr + 1, yr + 1, wr - 2, hr - 2, color);
2385 }
2386 
instantiate(const LV2UI_Descriptor * descriptor,const char * plugin_uri,const char * bundle_path,LV2UI_Write_Function write_function,LV2UI_Controller controller,LV2UI_Widget * widget,const LV2_Feature * const * features)2387 static LV2UI_Handle instantiate (const LV2UI_Descriptor *descriptor,
2388 						  const char *plugin_uri,
2389 						  const char *bundle_path,
2390 						  LV2UI_Write_Function write_function,
2391 						  LV2UI_Controller controller,
2392 						  LV2UI_Widget *widget,
2393 						  const LV2_Feature *const *features)
2394 {
2395 	PuglNativeView parentWindow = 0;
2396 	LV2UI_Resize* resize = NULL;
2397 
2398 	if (strcmp(plugin_uri, BJUMBLR_URI) != 0)
2399 	{
2400 		std::cerr << "BJumblr.lv2#GUI: GUI does not support plugin with URI " << plugin_uri << std::endl;
2401 		return NULL;
2402 	}
2403 
2404 	for (int i = 0; features[i]; ++i)
2405 	{
2406 		if (!strcmp(features[i]->URI, LV2_UI__parent)) parentWindow = (PuglNativeView) features[i]->data;
2407 		else if (!strcmp(features[i]->URI, LV2_UI__resize)) resize = (LV2UI_Resize*)features[i]->data;
2408 	}
2409 	if (parentWindow == 0) std::cerr << "BJumblr.lv2#GUI: No parent window.\n";
2410 
2411 	// New instance
2412 	BJumblrGUI* ui;
2413 	try {ui = new BJumblrGUI (bundle_path, features, parentWindow);}
2414 	catch (std::exception& exc)
2415 	{
2416 		std::cerr << "BJumblr.lv2#GUI: Instantiation failed. " << exc.what () << std::endl;
2417 		return NULL;
2418 	}
2419 
2420 	ui->controller = controller;
2421 	ui->write_function = write_function;
2422 
2423 	// Reduce min GUI size for small displays
2424 	double sz = 1.0;
2425 	int screenWidth  = getScreenWidth ();
2426 	int screenHeight = getScreenHeight ();
2427 	if ((screenWidth < 730) || (screenHeight < 460)) sz = 0.5;
2428 	else if ((screenWidth < 1060) || (screenHeight < 660)) sz = 0.66;
2429 
2430 	if (resize) resize->ui_resize(resize->handle, 1020 * sz, 620 * sz);
2431 
2432 	*widget = (LV2UI_Widget) puglGetNativeWindow (ui->getPuglView ());
2433 
2434 	ui->send_ui_on();
2435 
2436 	return (LV2UI_Handle) ui;
2437 }
2438 
cleanup(LV2UI_Handle ui)2439 static void cleanup(LV2UI_Handle ui)
2440 {
2441 	BJumblrGUI* self = (BJumblrGUI*) ui;
2442 	if (self) delete self;
2443 }
2444 
port_event(LV2UI_Handle ui,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)2445 static void port_event(LV2UI_Handle ui, uint32_t port_index, uint32_t buffer_size,
2446 	uint32_t format, const void* buffer)
2447 {
2448 	BJumblrGUI* self = (BJumblrGUI*) ui;
2449 	if (self) self->port_event(port_index, buffer_size, format, buffer);
2450 }
2451 
call_idle(LV2UI_Handle ui)2452 static int call_idle (LV2UI_Handle ui)
2453 {
2454 	BJumblrGUI* self = (BJumblrGUI*) ui;
2455 	if (self) self->handleEvents ();
2456 	return 0;
2457 }
2458 
call_resize(LV2UI_Handle ui,int width,int height)2459 static int call_resize (LV2UI_Handle ui, int width, int height)
2460 {
2461 	BJumblrGUI* self = (BJumblrGUI*) ui;
2462 	if (!self) return 0;
2463 
2464 	BEvents::ExposeEvent* ev = new BEvents::ExposeEvent (self, self, BEvents::CONFIGURE_REQUEST_EVENT, self->getPosition().x, self->getPosition().y, width, height);
2465 	self->addEventToQueue (ev);
2466 	return 0;
2467 }
2468 
2469 static const LV2UI_Idle_Interface idle = {call_idle};
2470 static const LV2UI_Resize resize = {nullptr, call_resize} ;
2471 
extension_data(const char * uri)2472 static const void* extension_data(const char* uri)
2473 {
2474 	if (!strcmp(uri, LV2_UI__idleInterface)) return &idle;
2475 	if(!strcmp(uri, LV2_UI__resize)) return &resize;
2476 	else return NULL;
2477 }
2478 
2479 static const LV2UI_Descriptor guiDescriptor = {
2480 		BJUMBLR_GUI_URI,
2481 		instantiate,
2482 		cleanup,
2483 		port_event,
2484 		extension_data
2485 };
2486 
2487 // LV2 Symbol Export
lv2ui_descriptor(uint32_t index)2488 LV2_SYMBOL_EXPORT const LV2UI_Descriptor *lv2ui_descriptor(uint32_t index)
2489 {
2490 	switch (index) {
2491 	case 0: return &guiDescriptor;
2492 	default:return NULL;
2493     }
2494 }
2495 
2496 /* End of LV2 specific declarations
2497  *
2498  * *****************************************************************************
2499  *
2500  *
2501  */
2502