1 // Copyright 2012 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 "braids/ui.h"
30
31 #include <cstring>
32
33 #include "stmlib/system/system_clock.h"
34
35 namespace braids {
36
37 using namespace stmlib;
38
39 const uint32_t kEncoderLongPressTime = 800;
40
Init()41 void Ui::Init() {
42 encoder_.Init();
43 display_.Init();
44 queue_.Init();
45 sub_clock_ = 0;
46 value_ = 0;
47 mode_ = MODE_SPLASH;
48 setting_ = SETTING_OSCILLATOR_SHAPE;
49 setting_index_ = 0;
50 }
51
Poll()52 void Ui::Poll() {
53 system_clock.Tick(); // Tick global ms counter.
54 ++sub_clock_;
55 encoder_.Debounce();
56 if (encoder_.just_pressed()) {
57 encoder_press_time_ = system_clock.milliseconds();
58 inhibit_further_switch_events_ = false;
59 }
60 if (!inhibit_further_switch_events_) {
61 if (encoder_.pressed()) {
62 uint32_t duration = system_clock.milliseconds() - encoder_press_time_;
63 if (duration >= kEncoderLongPressTime) {
64 queue_.AddEvent(CONTROL_ENCODER_LONG_CLICK, 0, 0);
65 inhibit_further_switch_events_ = true;
66 }
67 } else if (encoder_.released()) {
68 queue_.AddEvent(CONTROL_ENCODER_CLICK, 0, 0);
69 }
70 }
71 int32_t increment = encoder_.increment();
72 if (increment != 0) {
73 queue_.AddEvent(CONTROL_ENCODER, 0, increment);
74 }
75
76 if ((sub_clock_ & 1) == 0) {
77 display_.Refresh();
78 }
79 }
80
FlushEvents()81 void Ui::FlushEvents() {
82 queue_.Flush();
83 }
84
RefreshDisplay()85 void Ui::RefreshDisplay() {
86 switch (mode_) {
87 case MODE_SPLASH:
88 {
89 char text[] = " ";
90 text[0] = '\x98' + (splash_frame_ & 0x7);
91 display_.Print(text);
92 }
93 break;
94
95 case MODE_EDIT:
96 {
97 uint8_t value = settings.GetValue(setting_);
98 if (setting_ == SETTING_OSCILLATOR_SHAPE &&
99 settings.meta_modulation()) {
100 value = meta_shape_;
101 }
102 display_.Print(settings.metadata(setting_).strings[value]);
103 }
104 break;
105
106 case MODE_MENU:
107 {
108 if (setting_ == SETTING_CV_TESTER) {
109 char text[] = " ";
110 if (!blink_) {
111 for (uint8_t i = 0; i < kDisplayWidth; ++i) {
112 text[i] = '\x90' + (cv_[i] * 7 >> 12);
113 }
114 }
115 display_.Print(text);
116 } else if (setting_ == SETTING_MARQUEE) {
117 uint8_t length = strlen(settings.marquee_text());
118 uint8_t padded_length = length + 2 * kDisplayWidth - 4;
119 uint8_t position = ((cv_[0] >> 4) * (padded_length - 1)) >> 8;
120 position += (marquee_step_ % padded_length);
121 position += 1;
122 char text[] = " ";
123 for (uint8_t i = 0; i < kDisplayWidth; ++i) {
124 uint8_t index = (position + i) % padded_length;
125 if (index >= kDisplayWidth && index < kDisplayWidth + length) {
126 text[i] = settings.marquee_text()[index - kDisplayWidth];
127 }
128 }
129 display_.Print(text);
130 } else {
131 display_.Print(settings.metadata(setting_).name);
132 }
133 }
134 break;
135
136 case MODE_CALIBRATION_STEP_1:
137 display_.Print(">C2 ");
138 break;
139
140 case MODE_CALIBRATION_STEP_2:
141 display_.Print(">C4 ");
142 break;
143
144 case MODE_MARQUEE_EDITOR:
145 {
146 char text[] = " ";
147 for (uint8_t i = 0; i < kDisplayWidth; ++i) {
148 if ((marquee_character_ + i) >= kDisplayWidth - 1) {
149 const char* marquee_text = settings.marquee_text();
150 text[i] = marquee_text[marquee_character_ + i - kDisplayWidth + 1];
151 }
152 }
153 display_.Print(text);
154 }
155 break;
156
157 default:
158 break;
159 }
160 }
161
OnLongClick()162 void Ui::OnLongClick() {
163 switch (mode_) {
164 case MODE_MARQUEE_EDITOR:
165 settings.Save();
166 mode_ = MODE_MENU;
167 break;
168
169 case MODE_MENU:
170 if (setting_ == SETTING_CALIBRATION) {
171 mode_ = MODE_CALIBRATION_STEP_1;
172 } else if (setting_ == SETTING_MARQUEE) {
173 mode_ = MODE_MARQUEE_EDITOR;
174 marquee_character_ = 0;
175 marquee_dirty_character_ = false;
176 uint8_t length = strlen(settings.marquee_text());
177 settings.mutable_marquee_text()[length] = '\xA0';
178 } else if (setting_ == SETTING_VERSION) {
179 settings.Reset();
180 settings.Save();
181 }
182 break;
183
184 default:
185 break;
186 }
187 }
188
OnClick()189 void Ui::OnClick() {
190 switch (mode_) {
191 case MODE_EDIT:
192 mode_ = MODE_MENU;
193 break;
194
195 case MODE_MARQUEE_EDITOR:
196 {
197 if (marquee_character_ == 53) {
198 ++marquee_character_;
199 settings.mutable_marquee_text()[marquee_character_] = '\0';
200 } else if (settings.marquee_text()[marquee_character_] == '\xA0') {
201 settings.mutable_marquee_text()[marquee_character_] = '\0';
202 } else {
203 if (marquee_dirty_character_) {
204 settings.mutable_marquee_text()[marquee_character_ + 1] = \
205 settings.marquee_text()[marquee_character_];
206 }
207 ++marquee_character_;
208 marquee_dirty_character_ = false;
209 }
210 if (settings.marquee_text()[marquee_character_] == '\0') {
211 settings.Save();
212 mode_ = MODE_MENU;
213 }
214 }
215 break;
216
217 case MODE_MENU:
218 if (setting_ <= SETTING_LAST_EDITABLE_SETTING) {
219 mode_ = MODE_EDIT;
220 if (setting_ == SETTING_OSCILLATOR_SHAPE) {
221 settings.Save();
222 }
223 } else if (setting_ == SETTING_VERSION) {
224 mode_ = MODE_SPLASH;
225 }
226 break;
227
228 case MODE_CALIBRATION_STEP_1:
229 adc_code_c2_ = cv_[2];
230 adc_code_min_[0] = cv_[0];
231 adc_code_min_[1] = cv_[1];
232 mode_ = MODE_CALIBRATION_STEP_2;
233 break;
234
235 case MODE_CALIBRATION_STEP_2:
236 settings.Calibrate(
237 adc_code_c2_,
238 cv_[2],
239 cv_[3],
240 adc_code_min_[0],
241 cv_[0],
242 adc_code_min_[1],
243 cv_[1]);
244 mode_ = MODE_MENU;
245 break;
246
247 default:
248 break;
249 }
250 }
251
OnIncrement(const Event & e)252 void Ui::OnIncrement(const Event& e) {
253 switch (mode_) {
254 case MODE_MARQUEE_EDITOR:
255 {
256 char c = settings.marquee_text()[marquee_character_];
257 c += e.data;
258 if (c <= ' ') {
259 c = ' ';
260 } else if (c >= '\xA0') {
261 c = '\xA0';
262 }
263 settings.mutable_marquee_text()[marquee_character_] = c;
264 marquee_dirty_character_ = true;
265 }
266 break;
267
268 case MODE_EDIT:
269 {
270 int16_t value = settings.GetValue(setting_);
271 value = settings.metadata(setting_).Clip(value + e.data);
272 settings.SetValue(setting_, value);
273 display_.set_brightness(settings.GetValue(SETTING_BRIGHTNESS) + 1);
274 }
275 break;
276
277 case MODE_MENU:
278 {
279 setting_index_ += e.data;
280 if (setting_index_ < 0) {
281 setting_index_ = 0;
282 } else if (setting_index_ >= SETTING_LAST) {
283 setting_index_ = SETTING_LAST - 1;
284 }
285 setting_ = settings.setting_at_index(setting_index_);
286 marquee_step_ = 0;
287 }
288 break;
289
290 default:
291 break;
292 }
293 }
294
DoEvents()295 void Ui::DoEvents() {
296 bool refresh_display_ = false;
297 while (queue_.available()) {
298 Event e = queue_.PullEvent();
299 if (e.control_type == CONTROL_ENCODER_CLICK) {
300 OnClick();
301 } else if (e.control_type == CONTROL_ENCODER_LONG_CLICK) {
302 OnLongClick();
303 } else if (e.control_type == CONTROL_ENCODER) {
304 OnIncrement(e);
305 }
306 refresh_display_ = true;
307 }
308 if (queue_.idle_time() > 1000) {
309 refresh_display_ = true;
310 }
311 if (queue_.idle_time() >= 50 && mode_ == MODE_SPLASH) {
312 ++splash_frame_;
313 if (splash_frame_ == 8) {
314 splash_frame_ = 0;
315 mode_ = MODE_EDIT;
316 setting_ = SETTING_OSCILLATOR_SHAPE;
317 }
318 refresh_display_ = true;
319 }
320 if (queue_.idle_time() >= 50 &&
321 (setting_ == SETTING_CV_TESTER ||
322 setting_ == SETTING_MARQUEE)) {
323 refresh_display_ = true;
324 }
325 if (queue_.idle_time() >= 50 &&
326 setting_ == SETTING_OSCILLATOR_SHAPE &&
327 mode_ == MODE_EDIT &&
328 settings.meta_modulation()) {
329 refresh_display_ = true;
330 }
331 if (refresh_display_) {
332 queue_.Touch();
333 RefreshDisplay();
334 blink_ = false;
335 }
336 }
337
338 } // namespace braids
339