1 /*
2  * Schism Tracker - a cross-platform Impulse Tracker clone
3  * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
4  * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
5  * copyright (c) 2009 Storlek & Mrs. Brisby
6  * copyright (c) 2010-2012 Storlek
7  * URL: http://schismtracker.org/
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #include "headers.h"
25 
26 #include "it.h"
27 #include "song.h"
28 #include "page.h"
29 #include "osdefs.h"
30 
31 #include "sdlmain.h"
32 #include <ctype.h>
33 
34 /* --------------------------------------------------------------------- */
35 
36 static const char *note_names_up[12] = {
37 	"C-", "C#", "D-", "D#", "E-", "F-",
38 	"F#", "G-", "G#", "A-", "A#", "B-"
39 };
40 
41 static const char note_names_short_up[12] = "cCdDefFgGaAb";
42 
43 static const char *note_names_down[12] = {
44 	"C-", "Db", "D-", "Eb", "E-", "F-",
45 	"Gb", "G-", "Ab", "A-", "Bb", "B-"
46 };
47 
48 static const char note_names_short_down[12] = "CdDeEFgGaAbB";
49 
50 static const char **note_names = note_names_up;
51 static const char *note_names_short = note_names_short_up;
52 
53 /* --------------------------------------------------------------------- */
54 
55 static int current_octave = 4;
56 
57 /* --------------------------------------------------------------------- */
58 
59 /* this is used in a couple of other places... maybe it should be in some
60  * general-stuff file? */
61 const char hexdigits[16] = "0123456789ABCDEF";
62 
63 /* extra non-IT effects:
64  *         '!' = volume '$' = keyoff
65  *         '&' = setenvposition
66  *         '('/')' = noteslide up/down (IMF) */
67 static const char effects[] =     ".JFEGHLKRXODB!CQATI?SMNVW$UY?P&Z()?";
68 static const char ptm_effects[] = ".0123456789ABCDRFFT????GHK?YXPLZ()?";
69 
70 /* --------------------------------------------------------------------- */
71 
72 // XXX stupid magic numbers...
73 void kbd_sharp_flat_toggle(int e)
74 {
75 	switch (e) {
76 	case -1:
77 		kbd_sharp_flat_toggle(!!(note_names == note_names_up));
78 		break;
79 	case 1:
80 		status.flags |= ACCIDENTALS_AS_FLATS;
81 		status_text_flash("Displaying accidentals as flats (b)");
82 		note_names = note_names_down;
83 		note_names_short = note_names_short_down;
84 		break;
85 	default: /* case 0... */
86 		status.flags &= ~ACCIDENTALS_AS_FLATS;
87 		status_text_flash("Displaying accidentals as sharps (#)");
88 		note_names = note_names_up;
89 		note_names_short = note_names_short_up;
90 	}
91 }
92 
93 char get_effect_char(int effect)
94 {
95 	if (effect < 0 || effect > 34) {
96 		log_appendf(4, "get_effect_char: effect %d out of range",
97 			    effect);
98 		return '?';
99 	}
100 	return effects[effect];
101 }
102 
103 int get_ptm_effect_number(char effect)
104 {
105 	const char *ptr;
106 	if (effect >= 'a' && effect <= 'z') effect -= 32;
107 
108 	ptr = strchr(ptm_effects, effect);
109 	return ptr ? (ptr - effects) : -1;
110 }
111 
112 int get_effect_number(char effect)
113 {
114 	const char *ptr;
115 
116 	if (effect >= 'a' && effect <= 'z') {
117 		effect -= 32;
118 	} else if (!((effect >= '0' && effect <= '9')
119 		     || (effect >= 'A' && effect <= 'Z')
120 		     || (effect == '.'))) {
121 		/* don't accept pseudo-effects */
122 		if (status.flags & CLASSIC_MODE) return -1;
123 	}
124 
125 	ptr = strchr(effects, effect);
126 	return ptr ? ptr - effects : -1;
127 }
128 int kbd_get_effect_number(struct key_event *k)
129 {
130 	if (!NO_CAM_MODS(k->mod)) return -1;
131 	switch (k->sym) {
132 #define QZA(n) case SDLK_ ## n : return get_effect_number(#n [0])
133 QZA(a);QZA(b);QZA(c);QZA(d);QZA(e);QZA(f);QZA(g);QZA(h);QZA(i);QZA(j);QZA(k);
134 QZA(l);QZA(m);QZA(n);QZA(o);QZA(p);QZA(q);QZA(r);QZA(s);QZA(t);QZA(u);QZA(v);
135 QZA(w);QZA(x);QZA(y);QZA(z);
136 #undef QZA
137 	case SDLK_PERIOD: case SDLK_KP_PERIOD:
138 		return get_effect_number('.');
139 	case SDLK_1:
140 		if (!(k->mod & KMOD_SHIFT)) return -1;
141 	case SDLK_EXCLAIM:
142 		return get_effect_number('!');
143 	case SDLK_4:
144 		if (!(k->mod & KMOD_SHIFT)) return -1;
145 	case SDLK_DOLLAR:
146 		return get_effect_number('$');
147 	case SDLK_7:
148 		if (!(k->mod & KMOD_SHIFT)) return -1;
149 	case SDLK_AMPERSAND:
150 		return get_effect_number('&');
151 
152 	default:
153 		return -1;
154 	};
155 }
156 
157 /* --------------------------------------------------------------------- */
158 
159 void key_translate(struct key_event *k)
160 {
161 	k->orig_sym = k->sym;
162 	if (k->mod & KMOD_SHIFT) {
163 		switch (k->sym) {
164 		case SDLK_COMMA: k->sym = SDLK_LESS; break;
165 		case SDLK_PERIOD: k->sym = SDLK_GREATER; break;
166 		case SDLK_4: k->sym = SDLK_DOLLAR; break;
167 
168 		case SDLK_EQUALS: k->sym = SDLK_PLUS; break;
169 		case SDLK_SEMICOLON: k->sym = SDLK_COLON; break;
170 
171 		case SDLK_8: k->sym = SDLK_ASTERISK; break;
172 		default:
173 			break;
174 		};
175 	}
176 	if (k->mod & KMOD_META) {
177 		k->mod = ((k->mod & ~KMOD_META)
178 			  | ((status.flags & META_IS_CTRL)
179 			     ? KMOD_CTRL : KMOD_ALT));
180 	}
181 	if ((k->mod & KMOD_MODE) && (status.flags & ALTGR_IS_ALT)) {
182 		/* Treat AltGr as Alt (delt) */
183 		k->mod = ((k->mod & ~KMOD_MODE) | KMOD_ALT);
184 	}
185 	if (k->mod & KMOD_NUM) {
186 		switch (k->sym) {
187 		case SDLK_KP0: k->sym = SDLK_0; k->mod &= ~KMOD_NUM; break;
188 		case SDLK_KP1: k->sym = SDLK_1; k->mod &= ~KMOD_NUM; break;
189 		case SDLK_KP2: k->sym = SDLK_2; k->mod &= ~KMOD_NUM; break;
190 		case SDLK_KP3: k->sym = SDLK_3; k->mod &= ~KMOD_NUM; break;
191 		case SDLK_KP4: k->sym = SDLK_4; k->mod &= ~KMOD_NUM; break;
192 		case SDLK_KP5: k->sym = SDLK_5; k->mod &= ~KMOD_NUM; break;
193 		case SDLK_KP6: k->sym = SDLK_6; k->mod &= ~KMOD_NUM; break;
194 		case SDLK_KP7: k->sym = SDLK_7; k->mod &= ~KMOD_NUM; break;
195 		case SDLK_KP8: k->sym = SDLK_8; k->mod &= ~KMOD_NUM; break;
196 		case SDLK_KP9: k->sym = SDLK_9; k->mod &= ~KMOD_NUM; break;
197 		case SDLK_KP_PERIOD: k->sym = SDLK_PERIOD; k->mod &= ~KMOD_NUM; break;
198 		case SDLK_KP_DIVIDE: k->sym = SDLK_SLASH; k->mod &= ~KMOD_NUM; break;
199 		case SDLK_KP_MULTIPLY: k->sym = SDLK_ASTERISK; k->mod &= ~KMOD_NUM; break;
200 		case SDLK_KP_MINUS: k->sym = SDLK_MINUS; k->mod &= ~KMOD_NUM; break;
201 		case SDLK_KP_PLUS: k->sym = SDLK_PLUS; k->mod &= ~KMOD_NUM; break;
202 		case SDLK_KP_ENTER: k->sym = SDLK_RETURN; k->mod &= ~KMOD_NUM; break;
203 		case SDLK_KP_EQUALS: k->sym = SDLK_EQUALS; k->mod &= ~KMOD_NUM; break;
204 		default:
205 			break;
206 		};
207 	} else {
208 		switch (k->sym) {
209 		case SDLK_KP0: k->sym = SDLK_INSERT; break;
210 		case SDLK_KP4: k->sym = SDLK_LEFT; break;
211 		case SDLK_KP6: k->sym = SDLK_RIGHT; break;
212 		case SDLK_KP2: k->sym = SDLK_DOWN; break;
213 		case SDLK_KP8: k->sym = SDLK_UP; break;
214 
215 		case SDLK_KP9: k->sym = SDLK_PAGEUP; break;
216 		case SDLK_KP3: k->sym = SDLK_PAGEDOWN; break;
217 
218 		case SDLK_KP7: k->sym = SDLK_HOME; break;
219 		case SDLK_KP1: k->sym = SDLK_END; break;
220 
221 		case SDLK_KP_PERIOD: k->sym = SDLK_DELETE; break;
222 
223 		case SDLK_KP_DIVIDE: k->sym = SDLK_SLASH; break;
224 		case SDLK_KP_MULTIPLY: k->sym = SDLK_ASTERISK; break;
225 		case SDLK_KP_MINUS: k->sym = SDLK_MINUS; break;
226 		case SDLK_KP_PLUS: k->sym = SDLK_PLUS; break;
227 		case SDLK_KP_ENTER: k->sym = SDLK_RETURN; break;
228 		case SDLK_KP_EQUALS: k->sym = SDLK_EQUALS; break;
229 
230 		default:
231 			break;
232 		};
233 	}
234 
235 	if (k->sym == k->orig_sym) {
236 		switch (k->sym) {
237 		case SDLK_RETURN: k->unicode = '\r'; break;
238 		default:
239 			if (k->is_synthetic != 3) {
240 				/* "un" unicode it */
241 				k->unicode = char_unicode_to_cp437(k->unicode);
242 			}
243 		};
244 		return;
245 	}
246 
247 	switch (k->sym) {
248 	case SDLK_SLASH: k->unicode = (k->mod & KMOD_SHIFT) ? '?' : '/'; break;
249 	case SDLK_ASTERISK: k->unicode = '*'; break;
250 	case SDLK_MINUS: k->unicode = (k->mod & KMOD_SHIFT) ? '_' : '-'; break;
251 	case SDLK_PLUS: k->unicode = '+'; break;
252 	case SDLK_RETURN: k->unicode = '\r'; break;
253 	case SDLK_EQUALS: k->unicode = (k->mod & KMOD_SHIFT) ? '+' : '='; break;
254 	case SDLK_PERIOD: k->unicode = (k->mod & KMOD_SHIFT) ? '>' : '.'; break;
255 	case SDLK_0: k->unicode = (k->mod & KMOD_SHIFT) ? ')' : '0'; break;
256 	case SDLK_1: k->unicode = (k->mod & KMOD_SHIFT) ? '!' : '1'; break;
257 	case SDLK_2: k->unicode = (k->mod & KMOD_SHIFT) ? '@' : '2'; break;
258 	case SDLK_3: k->unicode = (k->mod & KMOD_SHIFT) ? '#' : '3'; break;
259 	case SDLK_4: k->unicode = (k->mod & KMOD_SHIFT) ? '$' : '4'; break;
260 	case SDLK_5: k->unicode = (k->mod & KMOD_SHIFT) ? '%' : '5'; break;
261 	case SDLK_6: k->unicode = (k->mod & KMOD_SHIFT) ? '^' : '6'; break;
262 	case SDLK_7: k->unicode = (k->mod & KMOD_SHIFT) ? '&' : '7'; break;
263 	case SDLK_8: k->unicode = (k->mod & KMOD_SHIFT) ? '*' : '8'; break;
264 	case SDLK_9: k->unicode = (k->mod & KMOD_SHIFT) ? '(' : '9'; break;
265 	default:
266 		break;
267 	};
268 }
269 
270 int numeric_key_event(struct key_event *k, int kponly)
271 {
272 	if (kponly) {
273 		switch (k->orig_sym) {
274 		case SDLK_KP0: return 0;
275 		case SDLK_KP1: return 1;
276 		case SDLK_KP2: return 2;
277 		case SDLK_KP3: return 3;
278 		case SDLK_KP4: return 4;
279 		case SDLK_KP5: return 5;
280 		case SDLK_KP6: return 6;
281 		case SDLK_KP7: return 7;
282 		case SDLK_KP8: return 8;
283 		case SDLK_KP9: return 9;
284 		default:
285 			break;
286 		};
287 		return -1;
288 	}
289 
290 	if (k->unicode >= '0' && k->unicode <= '9')
291 		return k->unicode - '0';
292 
293 	switch (k->orig_sym) {
294 	case SDLK_0: case SDLK_KP0: return 0;
295 	case SDLK_1: case SDLK_KP1: return 1;
296 	case SDLK_2: case SDLK_KP2: return 2;
297 	case SDLK_3: case SDLK_KP3: return 3;
298 	case SDLK_4: case SDLK_KP4: return 4;
299 	case SDLK_5: case SDLK_KP5: return 5;
300 	case SDLK_6: case SDLK_KP6: return 6;
301 	case SDLK_7: case SDLK_KP7: return 7;
302 	case SDLK_8: case SDLK_KP8: return 8;
303 	case SDLK_9: case SDLK_KP9: return 9;
304 	default:
305 		break;
306 	};
307 	return -1;
308 }
309 
310 
311 /* --------------------------------------------------------------------- */
312 
313 char *get_volume_string(int volume, int volume_effect, char *buf)
314 {
315 	const char cmd_table[16] = "...CDAB$H<>GFE";
316 
317 	buf[2] = 0;
318 
319 	if (volume_effect < 0 || volume_effect > 13) {
320 		log_appendf(4, "get_volume_string: volume effect %d out"
321 			    " of range", volume_effect);
322 		buf[0] = buf[1] = '?';
323 		return buf;
324 	}
325 
326 	/* '$'=vibratospeed, '<'=panslideleft, '>'=panslideright */
327 	switch (volume_effect) {
328 	case VOLFX_NONE:
329 		buf[0] = buf[1] = 173;
330 		break;
331 	case VOLFX_VOLUME:
332 	case VOLFX_PANNING:
333 		/* Yeah, a bit confusing :)
334 		 * The display stuff makes the distinction here with
335 		 * a different color for panning. */
336 		numtostr(2, volume, buf);
337 		break;
338 	default:
339 		buf[0] = cmd_table[volume_effect];
340 		buf[1] = hexdigits[volume];
341 		break;
342 	}
343 
344 	return buf;
345 }
346 
347 /* --------------------------------------------------------------------- */
348 
349 char *get_note_string(int note, char *buf)
350 {
351 #ifndef NDEBUG
352 	if ((note < 0 || note > 120)
353 	    && !(note == NOTE_CUT
354 		 || note == NOTE_OFF || note == NOTE_FADE)) {
355 		log_appendf(4, "Note %d out of range", note);
356 		buf[0] = buf[1] = buf[2] = '?';
357 		buf[3] = 0;
358 		return buf;
359 	}
360 #endif
361 
362 	switch (note) {
363 	case 0:        /* nothing */
364 		buf[0] = buf[1] = buf[2] = 173;
365 		break;
366 	case NOTE_CUT:
367 		buf[0] = buf[1] = buf[2] = 94;
368 		break;
369 	case NOTE_OFF:
370 		buf[0] = buf[1] = buf[2] = 205;
371 		break;
372 	case NOTE_FADE:
373 		/* this is sure to look "out of place" to anyone
374 		   ... yeah, no kidding. /storlek */
375 #if 0
376 		buf[0] = 185;
377 		buf[1] = 186;
378 		buf[2] = 185;
379 #else
380 		buf[0] = buf[1] = buf[2] = 126;
381 #endif
382 		break;
383 	default:
384 		note--;
385 		snprintf(buf, 4, "%.2s%.1d", note_names[note % 12],
386 			 note / 12);
387 	}
388 	buf[3] = 0;
389 	return buf;
390 }
391 
392 char *get_note_string_short(int note, char *buf)
393 {
394 #ifndef NDEBUG
395 	if ((note < 0 || note > 120)
396 	    && !(note == NOTE_CUT
397 		 || note == NOTE_OFF || note == NOTE_FADE)) {
398 		log_appendf(4, "Note %d out of range", note);
399 		buf[0] = buf[1] = '?';
400 		buf[2] = 0;
401 		return buf;
402 	}
403 #endif
404 
405 	switch (note) {
406 	case 0:        /* nothing */
407 		buf[0] = buf[1] = 173;
408 		break;
409 	case NOTE_CUT:
410 		buf[0] = buf[1] = 94;
411 		break;
412 	case NOTE_OFF:
413 		buf[0] = buf[1] = 205;
414 		break;
415 	case NOTE_FADE:
416 		buf[0] = buf[1] = 126;
417 		break;
418 	default:
419 		note--;
420 		buf[0] = note_names_short[note % 12];
421 		buf[1] = note / 12 + '0';
422 	}
423 	buf[2] = 0;
424 	return buf;
425 }
426 
427 /* --------------------------------------------------------------------- */
428 
429 int kbd_get_current_octave(void)
430 {
431 	return current_octave;
432 }
433 
434 void kbd_set_current_octave(int new_octave)
435 {
436 	new_octave = CLAMP(new_octave, 0, 8);
437 	current_octave = new_octave;
438 
439 	/* a full screen update for one lousy letter... */
440 	status.flags |= NEED_UPDATE;
441 }
442 
443 inline int kbd_char_to_99(struct key_event *k)
444 {
445 	int c;
446 	if (!NO_CAM_MODS(k->mod)) return -1;
447 
448 	c = tolower(k->unicode ?: k->sym);
449 	if (c >= 'h' && c <= 'z')
450 		return 10 + c - 'h';
451 
452 	return kbd_char_to_hex(k);
453 
454 }
455 int kbd_char_to_hex(struct key_event *k)
456 {
457 	if (!NO_CAM_MODS(k->mod)) return -1;
458 
459 	if (k->unicode == '0') return 0;
460 	if (k->unicode == '1') return 1;
461 	if (k->unicode == '2') return 2;
462 	if (k->unicode == '3') return 3;
463 	if (k->unicode == '4') return 4;
464 	if (k->unicode == '5') return 5;
465 	if (k->unicode == '6') return 6;
466 	if (k->unicode == '7') return 7;
467 	if (k->unicode == '8') return 8;
468 	if (k->unicode == '9') return 9;
469 	if (k->unicode == 'a' || k->unicode == 'A') return 10;
470 	if (k->unicode == 'b' || k->unicode == 'B') return 11;
471 	if (k->unicode == 'c' || k->unicode == 'C') return 12;
472 	if (k->unicode == 'd' || k->unicode == 'D') return 13;
473 	if (k->unicode == 'e' || k->unicode == 'E') return 14;
474 	if (k->unicode == 'f' || k->unicode == 'F') return 15;
475 
476 	switch (k->sym) {
477 	case SDLK_KP0: if (!(k->mod & KMOD_NUM)) return -1;
478 	case SDLK_0: return 0;
479 	case SDLK_KP1: if (!(k->mod & KMOD_NUM)) return -1;
480 	case SDLK_1: return 1;
481 	case SDLK_KP2: if (!(k->mod & KMOD_NUM)) return -1;
482 	case SDLK_2: return 2;
483 	case SDLK_KP3: if (!(k->mod & KMOD_NUM)) return -1;
484 	case SDLK_3: return 3;
485 	case SDLK_KP4: if (!(k->mod & KMOD_NUM)) return -1;
486 	case SDLK_4: return 4;
487 	case SDLK_KP5: if (!(k->mod & KMOD_NUM)) return -1;
488 	case SDLK_5: return 5;
489 	case SDLK_KP6: if (!(k->mod & KMOD_NUM)) return -1;
490 	case SDLK_6: return 6;
491 	case SDLK_KP7: if (!(k->mod & KMOD_NUM)) return -1;
492 	case SDLK_7: return 7;
493 	case SDLK_KP8: if (!(k->mod & KMOD_NUM)) return -1;
494 	case SDLK_8: return 8;
495 	case SDLK_KP9: if (!(k->mod & KMOD_NUM)) return -1;
496 	case SDLK_9: return 9;
497 	case SDLK_a: return 10;
498 	case SDLK_b: return 11;
499 	case SDLK_c: return 12;
500 	case SDLK_d: return 13;
501 	case SDLK_e: return 14;
502 	case SDLK_f: return 15;
503 	default:
504 		return -1;
505 	};
506 }
507 
508 /* --------------------------------------------------------------------- */
509 
510 /* return values:
511  *      < 0 = invalid note
512  *        0 = clear field ('.' in qwerty)
513  *    1-120 = note
514  * NOTE_CUT = cut ("^^^")
515  * NOTE_OFF = off ("===")
516  * NOTE_FADE = fade ("~~~")
517  *         i haven't really decided on how to display this.
518  *         and for you people who might say 'hey, IT doesn't do that':
519  *         yes it does. read the documentation. it's not in the editor,
520  *         but it's in the player. */
521 inline int kbd_get_note(struct key_event *k)
522 {
523 	int note;
524 
525 	if (!NO_CAM_MODS(k->mod)) return -1;
526 
527 	if (k->orig_sym == SDLK_KP_PERIOD && k->sym == SDLK_PERIOD) {
528 		/* lots of systems map an outside scancode for these;
529 		 * we may need to simply ignore scancodes > 256
530 		 * but i want a narrow change for this for now
531 		 * until it is certain we need more...
532 		 */
533 		return 0;
534 	}
535 
536 	switch (key_scancode_lookup(k->scancode, k->sym)) {
537 	case SDLK_BACKQUOTE:
538 		if (k->mod & KMOD_SHIFT) return NOTE_FADE;
539 	case SDLK_HASH: /* for delt */
540 		return NOTE_OFF;
541 	case SDLK_KP1:
542 		if (!(k->mod & KMOD_NUM)) return -1;
543 	case SDLK_1:
544 		return NOTE_CUT;
545 	case SDLK_KP_PERIOD:
546 		if (!(k->mod & KMOD_NUM)) return -1;
547 	case SDLK_PERIOD:
548 		return 0; /* clear */
549 	case SDLK_z: note = 1; break;
550 	case SDLK_s: note = 2; break;
551 	case SDLK_x: note = 3; break;
552 	case SDLK_d: note = 4; break;
553 	case SDLK_c: note = 5; break;
554 	case SDLK_v: note = 6; break;
555 	case SDLK_g: note = 7; break;
556 	case SDLK_b: note = 8; break;
557 	case SDLK_h: note = 9; break;
558 	case SDLK_n: note = 10; break;
559 	case SDLK_j: note = 11; break;
560 	case SDLK_m: note = 12; break;
561 
562 	case SDLK_q: note = 13; break;
563 	case SDLK_2: note = 14; break;
564 	case SDLK_w: note = 15; break;
565 	case SDLK_3: note = 16; break;
566 	case SDLK_e: note = 17; break;
567 	case SDLK_r: note = 18; break;
568 	case SDLK_5: note = 19; break;
569 	case SDLK_t: note = 20; break;
570 	case SDLK_6: note = 21; break;
571 	case SDLK_y: note = 22; break;
572 	case SDLK_7: note = 23; break;
573 	case SDLK_u: note = 24; break;
574 	case SDLK_i: note = 25; break;
575 	case SDLK_9: note = 26; break;
576 	case SDLK_o: note = 27; break;
577 	case SDLK_0: note = 28; break;
578 	case SDLK_p: note = 29; break;
579 
580 	default: return -1;
581 	};
582 	note += (12 * current_octave);
583 	return CLAMP(note, 1, 120);
584 }
585 
586 int kbd_get_alnum(struct key_event *k)
587 {
588 	if (k->sym >= 127)
589 		return 0;
590 	if (k->mod & KMOD_SHIFT) {
591 		const char shifted_digits[] = ")!@#$%^&*("; // comical profanity
592 		switch (k->sym) {
593 			case 'a'...'z': return toupper(k->sym);
594 			case '0'...'9': return shifted_digits[k->sym - '0'];
595 			case '[': return '{';
596 			case ']': return '}';
597 			case ';': return ':';
598 			case '=': return '+';
599 			case '-': return '_';
600 			case '`': return '~';
601 			case ',': return '<';
602 			case '.': return '>';
603 			case '/': return '?';
604 			case '\\': return '|';
605 			case '\'': return '"';
606 			default: return k->sym; // shift + some weird key = ???
607 		}
608 	}
609 	return k->sym;
610 }
611 
612 /* --------------------------------------------------------------------- */
613 
614 static int keydelay = SDL_DEFAULT_REPEAT_DELAY, keyrate = SDL_DEFAULT_REPEAT_INTERVAL;
615 
616 void set_key_repeat(int delay, int rate)
617 {
618 	/* save these for later */
619 	if (delay) {
620 		keydelay = delay;
621 		keyrate = rate;
622 	}
623 	SDL_EnableKeyRepeat(keydelay, keyrate);
624 }
625 
626