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