├── PSR530.pdf ├── key_scheme.png ├── keyboardscanner.jpg ├── README.md └── keyboardscanner.ino /PSR530.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oxesoft/keyboardscanner/HEAD/PSR530.pdf -------------------------------------------------------------------------------- /key_scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oxesoft/keyboardscanner/HEAD/key_scheme.png -------------------------------------------------------------------------------- /keyboardscanner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oxesoft/keyboardscanner/HEAD/keyboardscanner.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moura's Keyboard Scanner 2 | Turn your broken (or unused) keyboard in a MIDI controller (with pedal and veloticy) 3 | 4 | This Arduino sketch was the one that I used to make the project demonstrated 5 | in [this](https://www.youtube.com/watch?v=z840N9P-T2k) video. 6 | It is about a keyboard controller that I've made using an old Alesis QS6 Keyboard 7 | directly connected to an Arduino Mega rev3 acting as keyboard scanner with 8 | velocity reading and sustain pedal support. 9 | 10 | In 2017 I did the same with another keyboard (an old Casio from a friend). 11 | The code was refactored and a great library called [DIO2](https://github.com/FryDay/DIO2) 12 | was used to speed up the scanning and clean up the old code. 13 | 14 | In 2020, thanks to Leandro Meucchi, from Argentina, the code is simpler to be used with any keyboard. 15 | He made the PDF showing the keyboard wiring for Yamaha PSR530 keyboard, that helps a lot do understand what needs to be done. 16 | 17 | Warning: this sketch is for keyboard with velocity support only. 18 | 19 | ![keyboardscanner](https://raw.githubusercontent.com/oxesoft/keyboardscanner/master/keyboardscanner.jpg) 20 | 21 | ## How velocity works 22 | Normally it is a ribbon rubber with two contacts for each key that touch the board in two diffent moments: 23 | since the key was pressed until it slopes the board completly. The code measure the difference, varying between 24 | 2 and 120 ms, depending on the keyboard. It is transformed in a MIDI value from 0 to 127. 25 | 26 | ## Diagram of one key 27 | This scheme makes clear how to identify input and output pins. This has been the main question of guys on Youtube. 28 | I hope it helps: 29 | 30 | ![key](https://raw.githubusercontent.com/oxesoft/keyboardscanner/master/key_scheme.png) 31 | 32 | ## How to make your own MIDI controller 33 | 1) Disassemble the keyboard to have access to the flat cables (one, two or even three, depending on the number of keys and manufacturer); 34 | 2) Using a multimeter with the diode testing function selected, find out and understand the matrix, starting from the first key. Some keyboards have a logical pattern, some doesn't; 35 | 3) Connect the ribbon pins **directly** to the Arduino Mega (because it has enough pins to connect any keyboard with velocity). You **dont't** need to change anything in the keyboard circuit board; 36 | 4) Change the pins in the code (output_pins + input_pins), uncomment DEBUG_MIDI_MESSAGE and see the console output; 37 | 5) If the MIDI messages looks OK, comment DEBUG_MIDI_MESSAGE back and use some Serial<->MIDI Bridge like the excelent [Hairless](https://projectgus.github.io/hairless-midiserial/); 38 | 6) If everything goes well, consider turn you Arduino in a MIDI interface using [HIDUINO](https://github.com/ddiakopoulos/hiduino) or similar firmware. 39 | 7) Enjoy! 40 | -------------------------------------------------------------------------------- /keyboardscanner.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Moura's Keyboard Scanner: turn you broken (or unused) keyboard in a MIDI controller 3 | Copyright (C) 2017 Daniel Moura 4 | 5 | This code is originally hosted at https://github.com/oxesoft/keyboardscanner 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #include // install the library DIO2 22 | 23 | #define KEYS_NUMBER 61 24 | 25 | #define KEY_OFF 0 26 | #define KEY_START 1 27 | #define KEY_ON 2 28 | #define KEY_RELEASED 3 29 | #define KEY_SUSTAINED 4 30 | #define KEY_SUSTAINED_RESTART 5 31 | 32 | #define MIN_TIME_MS 3 33 | #define MAX_TIME_MS 50 34 | #define MAX_TIME_MS_N (MAX_TIME_MS - MIN_TIME_MS) 35 | 36 | #define PEDAL_PIN 21 37 | 38 | //find out the pins using a multimeter, starting from the first key 39 | //see the picture key_scheme.png to understand how to map the inputs and outputs 40 | 41 | //the following configuration is specific for PSR530 42 | //thanks Leandro Meucchi, from Argentina, by the PDF 43 | //take a look at the scheme detailed in PSR530.pdf and modify the following mapping according to the wiring of your keyboard 44 | #define PIN_A1 33 45 | #define PIN_A2 32 46 | #define PIN_A3 31 47 | #define PIN_A4 30 48 | #define PIN_A5 29 49 | #define PIN_A6 28 50 | #define PIN_A7 27 51 | #define PIN_A8 26 52 | #define PIN_A9 25 53 | #define PIN_A10 24 54 | #define PIN_A11 23 55 | #define PIN_A12 22 56 | #define PIN_B1 40 57 | #define PIN_B2 39 58 | #define PIN_B3 38 59 | #define PIN_B4 37 60 | #define PIN_B5 36 61 | #define PIN_B6 35 62 | #define PIN_B7 34 63 | #define PIN_C1 45 64 | #define PIN_C2 44 65 | #define PIN_C3 43 66 | #define PIN_C4 42 67 | #define PIN_C5 41 68 | 69 | byte output_pins[] = { 70 | PIN_B6, //C0 71 | PIN_B6, 72 | PIN_B7, 73 | PIN_B7, 74 | PIN_B7, 75 | PIN_B7, 76 | PIN_B7, 77 | PIN_B7, 78 | PIN_B7, 79 | PIN_B7, 80 | PIN_B7, 81 | PIN_B7, 82 | PIN_B7, 83 | PIN_B7, 84 | PIN_B5, 85 | PIN_B5, 86 | PIN_B5, 87 | PIN_B5, 88 | PIN_B5, 89 | PIN_B5, 90 | PIN_B5, 91 | PIN_B5, 92 | PIN_B5, 93 | PIN_B5, 94 | PIN_B5, //C1 95 | PIN_B5, 96 | PIN_B4, 97 | PIN_B4, 98 | PIN_B4, 99 | PIN_B4, 100 | PIN_B4, 101 | PIN_B4, 102 | PIN_B4, 103 | PIN_B4, 104 | PIN_B4, 105 | PIN_B4, 106 | PIN_B4, 107 | PIN_B4, 108 | PIN_B3, 109 | PIN_B3, 110 | PIN_B3, 111 | PIN_B3, 112 | PIN_B3, 113 | PIN_B3, 114 | PIN_B3, 115 | PIN_B3, 116 | PIN_B3, 117 | PIN_B3, 118 | PIN_B3, //C2 119 | PIN_B3, 120 | PIN_B2, 121 | PIN_B2, 122 | PIN_B2, 123 | PIN_B2, 124 | PIN_B2, 125 | PIN_B2, 126 | PIN_B2, 127 | PIN_B2, 128 | PIN_B2, 129 | PIN_B2, 130 | PIN_B2, 131 | PIN_B2, 132 | PIN_B1, 133 | PIN_B1, 134 | PIN_B1, 135 | PIN_B1, 136 | PIN_B1, 137 | PIN_B1, 138 | PIN_C1, 139 | PIN_C1, 140 | PIN_C1, 141 | PIN_C1, 142 | PIN_C1, //C3 143 | PIN_C1, 144 | PIN_C2, 145 | PIN_C2, 146 | PIN_C2, 147 | PIN_C2, 148 | PIN_C2, 149 | PIN_C2, 150 | PIN_C2, 151 | PIN_C2, 152 | PIN_C2, 153 | PIN_C2, 154 | PIN_C2, 155 | PIN_C2, 156 | PIN_C3, 157 | PIN_C3, 158 | PIN_C3, 159 | PIN_C3, 160 | PIN_C3, 161 | PIN_C3, 162 | PIN_C3, 163 | PIN_C3, 164 | PIN_C3, 165 | PIN_C3, 166 | PIN_C3, //C4 167 | PIN_C3, 168 | PIN_C4, 169 | PIN_C4, 170 | PIN_C4, 171 | PIN_C4, 172 | PIN_C4, 173 | PIN_C4, 174 | PIN_C4, 175 | PIN_C4, 176 | PIN_C4, 177 | PIN_C4, 178 | PIN_C4, 179 | PIN_C4, 180 | PIN_C5, 181 | PIN_C5, 182 | PIN_C5, 183 | PIN_C5, 184 | PIN_C5, 185 | PIN_C5, 186 | PIN_C5, 187 | PIN_C5, 188 | PIN_C5, 189 | PIN_C5, 190 | PIN_C5, //C5 191 | PIN_C5 192 | }; 193 | byte input_pins[] = { 194 | PIN_A9, //C0 195 | PIN_A10, 196 | PIN_A9, 197 | PIN_A10, 198 | PIN_A6, 199 | PIN_A5, 200 | PIN_A8, 201 | PIN_A7, 202 | PIN_A3, 203 | PIN_A4, 204 | PIN_A1, 205 | PIN_A2, 206 | PIN_A11, 207 | PIN_A12, 208 | PIN_A11, 209 | PIN_A12, 210 | PIN_A1, 211 | PIN_A2, 212 | PIN_A3, 213 | PIN_A4, 214 | PIN_A8, 215 | PIN_A7, 216 | PIN_A6, 217 | PIN_A5, 218 | PIN_A9, //C1 219 | PIN_A10, 220 | PIN_A9, 221 | PIN_A10, 222 | PIN_A6, 223 | PIN_A5, 224 | PIN_A8, 225 | PIN_A7, 226 | PIN_A3, 227 | PIN_A4, 228 | PIN_A1, 229 | PIN_A2, 230 | PIN_A11, 231 | PIN_A12, 232 | PIN_A11, 233 | PIN_A12, 234 | PIN_A1, 235 | PIN_A2, 236 | PIN_A3, 237 | PIN_A4, 238 | PIN_A8, 239 | PIN_A7, 240 | PIN_A6, 241 | PIN_A5, 242 | PIN_A9, //C2 243 | PIN_A10, 244 | PIN_A9, 245 | PIN_A10, 246 | PIN_A1, 247 | PIN_A2, 248 | PIN_A6, 249 | PIN_A5, 250 | PIN_A8, 251 | PIN_A7, 252 | PIN_A3, 253 | PIN_A4, 254 | PIN_A11, 255 | PIN_A12, 256 | PIN_A11, 257 | PIN_A12, 258 | PIN_A3, 259 | PIN_A4, 260 | PIN_A9, 261 | PIN_A10, 262 | PIN_A6, 263 | PIN_A5, 264 | PIN_A8, 265 | PIN_A7, 266 | PIN_A1, //C3 267 | PIN_A2, 268 | PIN_A1, 269 | PIN_A2, 270 | PIN_A6, 271 | PIN_A5, 272 | PIN_A8, 273 | PIN_A7, 274 | PIN_A3, 275 | PIN_A4, 276 | PIN_A9, 277 | PIN_A10, 278 | PIN_A11, 279 | PIN_A12, 280 | PIN_A11, 281 | PIN_A12, 282 | PIN_A1, 283 | PIN_A2, 284 | PIN_A3, 285 | PIN_A4, 286 | PIN_A8, 287 | PIN_A7, 288 | PIN_A6, 289 | PIN_A5, 290 | PIN_A9, //C4 291 | PIN_A10, 292 | PIN_A9, 293 | PIN_A10, 294 | PIN_A11, 295 | PIN_A12, 296 | PIN_A6, 297 | PIN_A5, 298 | PIN_A8, 299 | PIN_A7, 300 | PIN_A3, 301 | PIN_A4, 302 | PIN_A1, 303 | PIN_A2, 304 | PIN_A1, 305 | PIN_A2, 306 | PIN_A11, 307 | PIN_A12, 308 | PIN_A3, 309 | PIN_A4, 310 | PIN_A8, 311 | PIN_A7, 312 | PIN_A6, 313 | PIN_A5, 314 | PIN_A9, //C5 315 | PIN_A10 316 | }; 317 | 318 | //cheap keyboards often has the black keys softer or harder than the white ones 319 | //uncomment the next line to allow a soft correction 320 | //#define BLACK_KEYS_CORRECTION 321 | 322 | #ifdef BLACK_KEYS_CORRECTION 323 | #define MULTIPLIER 192 // 127 is the central value (corresponding to 1.0) 324 | byte black_keys[] = { 325 | 0,1,0,1,0,0,1,0,1,0,1,0, 326 | 0,1,0,1,0,0,1,0,1,0,1,0, 327 | 0,1,0,1,0,0,1,0,1,0,1,0, 328 | 0,1,0,1,0,0,1,0,1,0,1,0, 329 | 0,1,0,1,0,0,1,0,1,0,1,0, 330 | 0 331 | }; 332 | #endif 333 | 334 | //uncomment the next line to inspect the number of scans per seconds 335 | //#define DEBUG_SCANS_PER_SECOND 336 | 337 | /* 338 | 426 cyles per second (2,35ms per cycle) using standard digitalWrite/digitalRead 339 | 896 cyles per second (1,11ms per cycle) using DIO2 digitalWrite2/digitalRead2 340 | */ 341 | 342 | //uncoment the next line to get text midi message at output 343 | //#define DEBUG_MIDI_MESSAGE 344 | 345 | byte keys_state[KEYS_NUMBER]; 346 | unsigned long keys_time[KEYS_NUMBER]; 347 | boolean signals[KEYS_NUMBER * 2]; 348 | boolean pedal_enabled; 349 | 350 | void setup() { 351 | Serial.begin(115200); 352 | pinMode(13, OUTPUT); 353 | digitalWrite(13, LOW); 354 | int i; 355 | for (i = 0; i < KEYS_NUMBER; i++) 356 | { 357 | keys_state[i] = KEY_OFF; 358 | keys_time[i] = 0; 359 | } 360 | for (byte pin = 0; pin < sizeof(output_pins); pin++) 361 | { 362 | pinMode(output_pins[pin], OUTPUT); 363 | } 364 | for (byte pin = 0; pin < sizeof(input_pins); pin++) 365 | { 366 | pinMode(input_pins[pin], INPUT_PULLUP); 367 | } 368 | pinMode(PEDAL_PIN, INPUT_PULLUP); 369 | pedal_enabled = digitalRead(PEDAL_PIN) != HIGH; 370 | } 371 | 372 | void send_midi_event(byte status_byte, byte key_index, unsigned long time) 373 | { 374 | unsigned long t = time; 375 | #ifdef BLACK_KEYS_CORRECTION 376 | if (black_keys[key_index]) 377 | { 378 | t = (t * MULTIPLIER) >> 7; 379 | } 380 | #endif 381 | if (t > MAX_TIME_MS) 382 | t = MAX_TIME_MS; 383 | if (t < MIN_TIME_MS) 384 | t = MIN_TIME_MS; 385 | t -= MIN_TIME_MS; 386 | unsigned long velocity = 127 - (t * 127 / MAX_TIME_MS_N); 387 | byte vel = (((velocity * velocity) >> 7) * velocity) >> 7; 388 | byte key = 36 + key_index; 389 | #ifdef DEBUG_MIDI_MESSAGE 390 | char out[32]; 391 | sprintf(out, "%02X %02X %03d %d", status_byte, key, vel, time); 392 | Serial.println(out); 393 | #else 394 | Serial.write(status_byte); 395 | Serial.write(key); 396 | Serial.write(vel); 397 | #endif 398 | } 399 | 400 | void loop() { 401 | #ifdef DEBUG_SCANS_PER_SECOND 402 | static unsigned long cycles = 0; 403 | static unsigned long start = 0; 404 | static unsigned long current = 0; 405 | cycles++; 406 | current = millis(); 407 | if (current - start >= 1000) 408 | { 409 | Serial.println(cycles); 410 | cycles = 0; 411 | start = current; 412 | } 413 | #endif 414 | byte pedal = LOW; 415 | if (pedal_enabled) 416 | { 417 | pedal = digitalRead2(PEDAL_PIN); 418 | } 419 | 420 | boolean *s = signals; 421 | for (byte i = 0; i < KEYS_NUMBER * 2; i++) 422 | { 423 | byte output_pin = output_pins[i]; 424 | byte input_pin = input_pins[i]; 425 | digitalWrite2(output_pin, LOW); 426 | *(s++) = !digitalRead2(input_pin); 427 | digitalWrite2(output_pin, HIGH); 428 | } 429 | 430 | byte *state = keys_state; 431 | unsigned long *ktime = keys_time; 432 | boolean *signal = signals; 433 | for (byte key = 0; key < KEYS_NUMBER; key++) 434 | { 435 | for (byte state_index = 0; state_index < 2; state_index++) 436 | { 437 | switch (*state) 438 | { 439 | case KEY_OFF: 440 | if (state_index == 0 && *signal) 441 | { 442 | *state = KEY_START; 443 | *ktime = millis(); 444 | } 445 | break; 446 | case KEY_START: 447 | if (state_index == 0 && !*signal) 448 | { 449 | *state = KEY_OFF; 450 | break; 451 | } 452 | if (state_index == 1 && *signal) 453 | { 454 | *state = KEY_ON; 455 | send_midi_event(0x90, key, millis() - *ktime); 456 | } 457 | break; 458 | case KEY_ON: 459 | if (state_index == 1 && !*signal) 460 | { 461 | *state = KEY_RELEASED; 462 | *ktime = millis(); 463 | } 464 | break; 465 | case KEY_RELEASED: 466 | if (state_index == 0 && !*signal) 467 | { 468 | if (pedal) 469 | { 470 | *state = KEY_SUSTAINED; 471 | break; 472 | } 473 | *state = KEY_OFF; 474 | send_midi_event(0x80, key, millis() - *ktime); 475 | } 476 | break; 477 | case KEY_SUSTAINED: 478 | if (!pedal) 479 | { 480 | *state = KEY_OFF; 481 | send_midi_event(0x80, key, MAX_TIME_MS); 482 | } 483 | if (state_index == 0 && *signal) 484 | { 485 | *state = KEY_SUSTAINED_RESTART; 486 | *ktime = millis(); 487 | } 488 | break; 489 | case KEY_SUSTAINED_RESTART: 490 | if (state_index == 0 && !*signal) 491 | { 492 | *state = KEY_SUSTAINED; 493 | digitalWrite(13, HIGH); 494 | break; 495 | } 496 | if (state_index == 1 && *signal) 497 | { 498 | *state = KEY_ON; 499 | send_midi_event(0x80, key, MAX_TIME_MS); 500 | send_midi_event(0x90, key, millis() - *ktime); 501 | } 502 | break; 503 | } 504 | signal++; 505 | } 506 | state++; 507 | ktime++; 508 | } 509 | } 510 | --------------------------------------------------------------------------------