1 // Copyright 2015 Emilie Gillet.
2 //
3 // Author: Emilie Gillet (emilie.o.gillet@gmail.com)
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 // THE SOFTWARE.
22 //
23 // See http://creativecommons.org/licenses/MIT/ for more information.
24 //
25 // -----------------------------------------------------------------------------
26 //
27 // User interface.
28
29 #include "rings/ui.h"
30
31 #include <algorithm>
32
33 #include "stmlib/system/system_clock.h"
34
35 #include "rings/cv_scaler.h"
36 #include "rings/dsp/part.h"
37 #include "rings/dsp/string_synth_part.h"
38
39 namespace rings {
40
41 const int32_t kLongPressDuration = 3000;
42
43 using namespace std;
44 using namespace stmlib;
45
Init(Settings * settings,CvScaler * cv_scaler,Part * part,StringSynthPart * string_synth)46 void Ui::Init(
47 Settings* settings,
48 CvScaler* cv_scaler,
49 Part* part,
50 StringSynthPart* string_synth) {
51 leds_.Init();
52 switches_.Init();
53
54 settings_ = settings;
55 cv_scaler_ = cv_scaler;
56 part_ = part;
57 string_synth_ = string_synth;
58
59 if (switches_.pressed_immediate(1)) {
60 State* state = settings_->mutable_state();
61 if (state->color_blind == 1) {
62 state->color_blind = 0;
63 } else {
64 state->color_blind = 1;
65 }
66 settings_->Save();
67 }
68
69 part_->set_polyphony(settings_->state().polyphony);
70 part_->set_model(static_cast<ResonatorModel>(settings_->state().model));
71 string_synth_->set_polyphony(settings_->state().polyphony);
72 string_synth_->set_fx(static_cast<FxType>(settings_->state().model));
73 mode_ = settings_->state().easter_egg
74 ? UI_MODE_EASTER_EGG_INTRO
75 : UI_MODE_NORMAL;
76 }
77
SaveState()78 void Ui::SaveState() {
79 settings_->mutable_state()->polyphony = part_->polyphony();
80 settings_->mutable_state()->model = part_->model();
81 settings_->Save();
82 }
83
AnimateEasterEggLeds()84 void Ui::AnimateEasterEggLeds() {
85 mode_ = settings_->state().easter_egg
86 ? UI_MODE_EASTER_EGG_INTRO
87 : UI_MODE_EASTER_EGG_OUTRO;
88 }
89
Poll()90 void Ui::Poll() {
91 // 1kHz.
92 system_clock.Tick();
93 switches_.Debounce();
94
95 for (uint8_t i = 0; i < kNumSwitches; ++i) {
96 if (switches_.just_pressed(i)) {
97 queue_.AddEvent(CONTROL_SWITCH, i, 0);
98 press_time_[i] = system_clock.milliseconds();
99 }
100 if (switches_.pressed(i) && press_time_[i] != 0) {
101 int32_t pressed_time = system_clock.milliseconds() - press_time_[i];
102 if (pressed_time > kLongPressDuration) {
103 queue_.AddEvent(CONTROL_SWITCH, i, pressed_time);
104 press_time_[i] = 0;
105 }
106 }
107 if (switches_.released(i) && press_time_[i] != 0) {
108 queue_.AddEvent(
109 CONTROL_SWITCH,
110 i,
111 system_clock.milliseconds() - press_time_[i] + 1);
112 press_time_[i] = 0;
113 }
114 }
115
116 bool blink = (system_clock.milliseconds() & 127) > 64;
117 bool slow_blink = (system_clock.milliseconds() & 255) > 128;
118 switch (mode_) {
119 case UI_MODE_NORMAL:
120 {
121 uint8_t pwm_counter = system_clock.milliseconds() & 15;
122 uint8_t triangle = (system_clock.milliseconds() >> 5) & 31;
123 triangle = triangle < 16 ? triangle : 31 - triangle;
124
125 if (settings_->state().color_blind == 1) {
126 uint8_t mode_red_brightness[] = {
127 0, 15, 1,
128 0, triangle, uint8_t(triangle >> 3)
129 };
130 uint8_t mode_green_brightness[] = {
131 4, 15, 0,
132 uint8_t(triangle >> 1), triangle, 0,
133 };
134
135 uint8_t poly_counter = (system_clock.milliseconds() >> 7) % 12;
136 uint8_t poly_brightness = (poly_counter >> 1) < part_->polyphony() &&
137 (poly_counter & 1);
138 uint8_t poly_red_brightness = part_->polyphony() >= 2
139 ? 8 + 8 * poly_brightness
140 : 0;
141 uint8_t poly_green_brightness = part_->polyphony() <= 3
142 ? 8 + 8 * poly_brightness
143 : 0;
144 if (part_->polyphony() == 1 || part_->polyphony() == 4) {
145 poly_red_brightness >>= 3;
146 poly_green_brightness >>= 2;
147 }
148 leds_.set(
149 0,
150 pwm_counter < poly_red_brightness,
151 pwm_counter < poly_green_brightness);
152 leds_.set(
153 1,
154 pwm_counter < mode_red_brightness[part_->model()],
155 pwm_counter < mode_green_brightness[part_->model()]);
156 } else {
157 leds_.set(0, part_->polyphony() >= 2, part_->polyphony() <= 2);
158 leds_.set(1, part_->model() >= 1, part_->model() <= 1);
159 // Fancy modes!
160 if (part_->polyphony() == 3) {
161 leds_.set(0, true, pwm_counter < triangle);
162 }
163 if (part_->model() >= 3) {
164 bool led_1 = part_->model() >= 4 && pwm_counter < triangle;
165 bool led_2 = part_->model() <= 4 && pwm_counter < triangle;
166 leds_.set(1, led_1, led_2);
167 }
168 }
169 ++strumming_flag_interval_;
170 if (strumming_flag_counter_) {
171 --strumming_flag_counter_;
172 leds_.set(0, false, false);
173 }
174 }
175 break;
176
177 case UI_MODE_CALIBRATION_C1:
178 leds_.set(0, blink, blink);
179 leds_.set(1, false, false);
180 break;
181
182 case UI_MODE_CALIBRATION_C3:
183 leds_.set(0, false, false);
184 leds_.set(1, blink, blink);
185 break;
186
187 case UI_MODE_CALIBRATION_LOW:
188 leds_.set(0, slow_blink, 0);
189 leds_.set(1, slow_blink, 0);
190 break;
191
192 case UI_MODE_CALIBRATION_HIGH:
193 leds_.set(0, false, slow_blink);
194 leds_.set(1, false, slow_blink);
195 break;
196
197 case UI_MODE_EASTER_EGG_INTRO:
198 {
199 uint8_t pwm_counter = system_clock.milliseconds() & 15;
200 uint8_t triangle_1 = (system_clock.milliseconds() / 7) & 31;
201 uint8_t triangle_2 = (system_clock.milliseconds() / 17) & 31;
202 triangle_1 = triangle_1 < 16 ? triangle_1 : 31 - triangle_1;
203 triangle_2 = triangle_2 < 16 ? triangle_2 : 31 - triangle_2;
204 leds_.set(
205 0,
206 triangle_1 > pwm_counter,
207 triangle_2 > pwm_counter);
208 leds_.set(
209 1,
210 triangle_2 > pwm_counter,
211 triangle_1 > pwm_counter);
212 }
213 break;
214
215 case UI_MODE_EASTER_EGG_OUTRO:
216 {
217 uint8_t pwm_counter = 7;
218 uint8_t triangle_1 = (system_clock.milliseconds() / 9) & 31;
219 uint8_t triangle_2 = (system_clock.milliseconds() / 13) & 31;
220 triangle_1 = triangle_1 < 16 ? triangle_1 : 31 - triangle_1;
221 triangle_2 = triangle_2 < 16 ? triangle_2 : 31 - triangle_2;
222 leds_.set(0, triangle_1 < pwm_counter, triangle_1 > pwm_counter);
223 leds_.set(1, triangle_2 > pwm_counter, triangle_2 < pwm_counter);
224 }
225 break;
226
227 case UI_MODE_PANIC:
228 leds_.set(0, blink, false);
229 leds_.set(1, blink, false);
230 break;
231 }
232 leds_.Write();
233 }
234
FlushEvents()235 void Ui::FlushEvents() {
236 queue_.Flush();
237 }
238
OnSwitchPressed(const Event & e)239 void Ui::OnSwitchPressed(const Event& e) {
240
241 }
242
OnSwitchReleased(const Event & e)243 void Ui::OnSwitchReleased(const Event& e) {
244 // Check if the other switch is still pressed.
245 if (switches_.pressed(1 - e.control_id)) {
246 if (mode_ == UI_MODE_CALIBRATION_C1) {
247 StartNormalizationCalibration();
248 } else {
249 StartCalibration();
250 }
251 press_time_[0] = press_time_[1] = 0;
252 return;
253 }
254
255 switch (e.control_id) {
256 case 0:
257 if (e.data >= kLongPressDuration) {
258 if (cv_scaler_->easter_egg()) {
259 settings_->ToggleEasterEgg();
260 AnimateEasterEggLeds();
261 } else {
262 part_->set_polyphony(3);
263 string_synth_->set_polyphony(3);
264 }
265 SaveState();
266 } else {
267 switch (mode_) {
268 case UI_MODE_CALIBRATION_C1:
269 CalibrateC1();
270 break;
271 case UI_MODE_CALIBRATION_C3:
272 CalibrateC3();
273 break;
274 case UI_MODE_CALIBRATION_LOW:
275 CalibrateLow();
276 break;
277 case UI_MODE_CALIBRATION_HIGH:
278 CalibrateHigh();
279 break;
280 default:
281 {
282 int32_t polyphony = part_->polyphony();
283 if (polyphony == 3) {
284 polyphony = 2;
285 }
286 polyphony <<= 1;
287 if (polyphony > 4) {
288 polyphony = 1;
289 }
290 part_->set_polyphony(polyphony);
291 string_synth_->set_polyphony(polyphony);
292 SaveState();
293 }
294 break;
295 }
296 }
297 break;
298
299 case 1:
300 if (e.data >= kLongPressDuration) {
301 if (cv_scaler_->easter_egg()) {
302 settings_->ToggleEasterEgg();
303 AnimateEasterEggLeds();
304 } else {
305 int32_t model = part_->model();
306 if (model >= 3) {
307 model -= 3;
308 } else {
309 model += 3;
310 }
311 part_->set_model(static_cast<ResonatorModel>(model));
312 string_synth_->set_fx(static_cast<FxType>(model));
313 }
314 } else {
315 int32_t model = part_->model();
316 if (model >= 3) {
317 model -= 3;
318 } else {
319 model = (model + 1) % 3;
320 }
321 part_->set_model(static_cast<ResonatorModel>(model));
322 string_synth_->set_fx(static_cast<FxType>(model));
323 }
324 SaveState();
325 break;
326
327 default:
328 break;
329 }
330 }
331
StartCalibration()332 void Ui::StartCalibration() {
333 mode_ = UI_MODE_CALIBRATION_C1;
334 }
335
CalibrateC1()336 void Ui::CalibrateC1() {
337 cv_scaler_->CalibrateC1();
338 cv_scaler_->CalibrateOffsets();
339 mode_ = UI_MODE_CALIBRATION_C3;
340 }
341
CalibrateC3()342 void Ui::CalibrateC3() {
343 bool success = cv_scaler_->CalibrateC3();
344 if (success) {
345 settings_->Save();
346 mode_ = UI_MODE_NORMAL;
347 } else {
348 mode_ = UI_MODE_PANIC;
349 }
350 }
351
StartNormalizationCalibration()352 void Ui::StartNormalizationCalibration() {
353 cv_scaler_->StartNormalizationCalibration();
354 mode_ = UI_MODE_CALIBRATION_LOW;
355 }
356
CalibrateLow()357 void Ui::CalibrateLow() {
358 cv_scaler_->CalibrateLow();
359 mode_ = UI_MODE_CALIBRATION_HIGH;
360 }
361
CalibrateHigh()362 void Ui::CalibrateHigh() {
363 bool success = cv_scaler_->CalibrateHigh();
364 if (success) {
365 settings_->Save();
366 mode_ = UI_MODE_NORMAL;
367 } else {
368 mode_ = UI_MODE_PANIC;
369 }
370 }
371
DoEvents()372 void Ui::DoEvents() {
373 while (queue_.available()) {
374 Event e = queue_.PullEvent();
375 if (e.control_type == CONTROL_SWITCH) {
376 if (e.data == 0) {
377 OnSwitchPressed(e);
378 } else {
379 OnSwitchReleased(e);
380 }
381 }
382 }
383 if (queue_.idle_time() > 800 && mode_ == UI_MODE_PANIC) {
384 mode_ = UI_MODE_NORMAL;
385 }
386 if (mode_ == UI_MODE_EASTER_EGG_INTRO || mode_ == UI_MODE_EASTER_EGG_OUTRO) {
387 if (queue_.idle_time() > 3000) {
388 mode_ = UI_MODE_NORMAL;
389 queue_.Touch();
390 }
391 } else if (queue_.idle_time() > 1000) {
392 queue_.Touch();
393 }
394 }
395
HandleFactoryTestingRequest(uint8_t command)396 uint8_t Ui::HandleFactoryTestingRequest(uint8_t command) {
397 uint8_t argument = command & 0x1f;
398 command = command >> 5;
399 uint8_t reply = 0;
400 switch (command) {
401 case FACTORY_TESTING_READ_POT:
402 case FACTORY_TESTING_READ_CV:
403 reply = cv_scaler_->adc_value(argument);
404 break;
405
406 case FACTORY_TESTING_READ_NORMALIZATION:
407 reply = cv_scaler_->normalization(argument);
408 break;
409
410 case FACTORY_TESTING_READ_GATE:
411 reply = argument == 2
412 ? cv_scaler_->gate_value()
413 : switches_.pressed(argument);
414 break;
415
416 case FACTORY_TESTING_SET_BYPASS:
417 part_->set_bypass(argument);
418 break;
419
420 case FACTORY_TESTING_CALIBRATE:
421 {
422 switch (argument) {
423 case 0:
424 StartCalibration();
425 break;
426
427 case 1:
428 CalibrateC1();
429 break;
430
431 case 2:
432 CalibrateC3();
433 break;
434
435 case 3:
436 StartNormalizationCalibration();
437 break;
438
439 case 4:
440 CalibrateLow();
441 break;
442
443 case 5:
444 CalibrateHigh();
445 queue_.Touch();
446 break;
447 }
448 }
449 break;
450 }
451 return reply;
452 }
453
454 } // namespace rings
455