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