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