Tommy Bianco
Published © CC BY-NC

Remake of S. Francis Writing Machine

Modern QWERTY keyboards are simply not challenging enough. Let's use a piano roll instead!

BeginnerFull instructions provided1 hour85
Remake of S. Francis Writing Machine

Things used in this project

Hardware components

MIDI Keyboard
×1
RGB LED Pixel Matrix, NeoPixel NeoMatrix
RGB LED Pixel Matrix, NeoPixel NeoMatrix
×1
Arduino UNO
Arduino UNO
×1
Male/Female Jumper Wires
Male/Female Jumper Wires
×1

Software apps and online services

loopMIDI
Hairless MIDI

Story

Read more

Code

MIDI to Keyboard input

Python
Guess what it does from the name...
import time
import pygame
import pygame.midi
import keyboard

# Initialise a MIDI instance
def MIDI_init ():
    if pygame.midi.get_init() == 1:
        pygame.midi.quit()
    pygame.midi.init()

# Selecting input channel
def input_select():
    for n in range(pygame.midi.get_count()):
        print (n,pygame.midi.get_device_info(n))
    device_ID = int(input ("Enter your INPUT device number\n"))
    return (pygame.midi.Input(device_ID))

# Selecting output channel for release
def output_select():
    for n in range(pygame.midi.get_count()):
        print (n,pygame.midi.get_device_info(n))
    device_ID = int(input ("Enter your OUTPUT device number []\n"))
    return (pygame.midi.Output(device_ID))

# Read and release a MIDI message
def MIDI_read (midi_input, midi_output):
    if pygame.midi.Input.poll(midi_input) == True:
        # Read the MIDI message and pass it on
        midi_data = midi_input.read (1) 
        midi_output.write_short(midi_data[0][0][0],midi_data[0][0][1],127) # converts velocity to MAX
        # Dissect the midi message
        midi_note, timestamp = midi_data[0]
        note_status, keynum, velocity, unused = midi_note
        print("Note Status: ", note_status, " Key Number: ", keynum," Velocity: " , velocity, "\n\tTime Stamp: ", timestamp)
        
        status_byte = bin(note_status)[2:6]
        channel = int(bin(note_status)[6:], 2) + 1
        if status_byte == "1001":
            key_down = True
        else: 
            key_down = False
        return (keynum, key_down, channel)
    else:
        time.sleep(.01)
        return False

# Map controller keys to letters 
letters_on_keys = ['c','p','d','k','h','f','g','u','j','l','b','r','t','y','n,'m','e','i','w','o','x','a','v','s']

letters_on_pads = ['"',':','!','?','','','','','','','','','#5','#6','#7','#8','#1','#2','#3','#4','#13',',','.',' ']

shift = False # remember the SHIFT key state

# Convert a MIDI key number to a letter
def MIDI_to_TXT (message, shift):
    keynum = messsage[0]
    channel = message [2]
    shift = shift
    keynum = keynum % 24
    if channel == 1:
        keyboard.write(letters_on_keys[keynum])
        # Release SHIFT if it was pressed
        if shift == True:
            keyboard.release("shift")
            shift = False
    elif channel == 10:
      # Press shift if #13 is pressed
        if letters_on_pads[keynum] == '#13':
            keyboard.press("shift")
            shift = True
        else: 
            keyboard.write(letters_on_pads[keynum])

# Main body
MIDI_init ()
midi_input = input_select()
midi_output = output_select()

while (True):
    messsage = MIDI_read(midi_input,midi_output)
    if messsage != False:
        if messsage[1] == True:
            MIDI_to_TXT (messsage, shift)

Keyboard to MIDI signal

Python
Guess what it does from the name...
import pygame
import pygame.midi
import keyboard
import time

# Initialise a MIDI instance
def MIDI_init ():
    if pygame.midi.get_init() == 1:
        pygame.midi.quit()
    pygame.midi.init()

def output_select():
    for n in range(pygame.midi.get_count()):
        print (n,pygame.midi.get_device_info(n))
    device_ID = int(input ("Enter your OUTPUT device number []\n"))
    return (pygame.midi.Output(device_ID))

def keyboard_read():
    key = ''
    while (key == ''):
        key = keyboard.read_key()
        if keyboard.is_pressed(key) != True:
            key = 'Released'
    keyboard.unhook_all()
    if key != 'Released':
        return key

myDict = {'a': 22,  'A': 22,
          'b': 10,  'B': 10,
          'c': 0,   'C': 0,
          'd': 2,   'D': 2,
          'e': 17,  'E': 17,
          'f': 5,   'F': 5,
          'g': 6,   'G': 6,
          'h': 4,   'H': 4,
          'i': 18,  'I': 18,
          'j': 8,   'J': 8,
          'k': 3,   'K': 3,
          'l': 9,   'L': 9,
          'm': 16,  'M': 16,
          'n': 15,  'N': 15,
          'o': 20,  'O': 20,
          'p': 1,   'P': 1,
          'q': 13,  'Q': 13,
          'r': 11,  'R': 11,
          's': 24,  'S': 24,
          't': 12,  'T': 12,
          'u': 7,   'U': 7,
          'v': 23,  'V': 23,
          'w': 19,  'W': 19, 
          'x': 21,  'X': 21,
          'y': 14,  'Y': 14,
          'z': 26,  'Z':26}

signal_log = [False] * 27
octave = 2

def TXT_to_MIDI (midi_output, key):
    keynum = myDict.get (key) #use get() to avoid keyerror
    if (keynum == None):   #redundant
        return
    midi_output.note_off (keynum + 24*octave, velocity=127, channel=0) 
    midi_output.note_on (keynum + 24*octave, velocity=127, channel=0)
    time.sleep(0.1) # MIDI signal duration of 0.1 seconds
    midi_output.note_off (keynum + 24*octave, velocity=127, channel=0)
    

# Main body
MIDI_init ()
midi_output = output_select()

key = None
print ("Press ESC to stop the script")

while (key != 'esc'):
    key = keyboard_read()
    TXT_to_MIDI (midi_output, key)

MIDI to Arduino

Python
Run this code together with "Arduino Serial Visualization" to make a little lightshow :)
import time
import pygame
import pygame.midi

def MIDI_init ():
    if pygame.midi.get_init() == 1:
        pygame.midi.quit()
    pygame.midi.init()

def input_select():
    for n in range(pygame.midi.get_count()):
        print (n,pygame.midi.get_device_info(n))
    device_ID = int(input ("Enter your INPUT device number\n"))
    return (pygame.midi.Input(device_ID))

def output_select():
    for n in range(pygame.midi.get_count()):
        print (n,pygame.midi.get_device_info(n))
    device_ID = int(input ("Enter your OUTPUT device number []\n"))
    return (pygame.midi.Output(device_ID))


def MIDI_read (midi_input, midi_output):
    if pygame.midi.Input.poll(midi_input) == True:
        # Read the MIDI message and pass it on
        midi_data = midi_input.read (1) #or midi_data = pygame.midi.Input.read(midi_input,1)
        midi_output.write_short(midi_data[0][0][0],midi_data[0][0][1],127) # converts velocity to MAX
        # Dissect the midi message
        midi_note, timestamp = midi_data[0]
        note_status, keynum, velocity, unused = midi_note
        #print("Note Status: ", note_status, " Key Number: ", keynum," Velocity: " , velocity, "\n\tTime Stamp: ", timestamp)
        
        # https://www.cs.cmu.edu/~music/cmsip/readings/MIDI%20tutorial%20for%20programmers.html
        status_byte = bin(note_status)[2:6]
        channel = int(bin(note_status)[6:], 2) + 1
        
        if status_byte == "1001":
            key_down = True
        else: 
            key_down = False
        return (keynum, key_down, channel)
    else:
        time.sleep(.01)
        return False


import serial
 
ser = serial.Serial()
ser.baudrate = 115200
ser.port = 'COM3'
ser.open()  

# message = [keynum, key_down, channel]
def MIDI_pass_to_Arduino(keynum): 
    value_sent = bytearray([int(keynum)])
    ser.write(value_sent)
    value_received = int.from_bytes(ser.read(1), 'big')
    print (value_received)
    return (value_sent)
    

MIDI_init ()
midi_input = pygame.midi.Input(3)
midi_output = pygame.midi.Output(6)


while (True):
    message = MIDI_read(midi_input,midi_output)
    if message != False:
        if message[1] == True:
            MIDI_pass_to_Arduino(message[0])

# Run this line at the end to prevent odd errors
ser.close() 

Arduino Serial Visualization

Arduino
Run this code together with "MIDI to Arduino" or any other script sending numeric Serial input to your Arduino to make a little lightshow :)
#include <Adafruit_NeoPixel.h>
#define PIXEL_PIN D5
#define PIXEL_COUNT 64
Adafruit_NeoPixel RGB_matrix = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  Serial.begin(115200);
  RGB_matrix.begin();
  RGB_matrix.setBrightness(20); 
  RGB_matrix.clear();
  RGB_matrix.show();
}

int colors [13][3] = {
  {255, 0, 0},
  {0, 255, 0},
  {0, 0, 255},
  {255, 0, 255},
  {255, 255, 50},
  {50, 255, 255},
  {255, 222, 30},
  {0, 255, 120},
  {0,255,255},
  {255, 175, 0},
  {255, 75, 0},
  {0, 255, 40}
};

int input = 0;
 
void loop() {  
  while (Serial.available()){
    input = Serial.read();
    Serial.write(input);
  }
  int note = input % 12;
  if (input > 0) {
    for (int X=0; X<PIXEL_COUNT; X++) {
      RGB_matrix.setPixelColor(X, colors[note][0], colors[note][1], colors[note][2]);
      RGB_matrix.show();
    }
    input = 0;
  }
}

Credits

Tommy Bianco

Tommy Bianco

5 projects • 2 followers
A biologist procrastinating

Comments