import mido
import sys
import os

keymap = ['z', ' ', 'x', ' ', 'c', 'v', ' ', 'b', ' ', 'n', ' ', 'm', 'a', ' ', 's', ' ', 'd', 'f', ' ', 'g', ' ', 'h', ' ', 'j', 'q', ' ', 'w', ' ', 'e', 'r', ' ', 't', ' ', 'y', ' ', 'u']

octave_range = (48, 84)  #change to allow other ranges (within 0-127, see midi note numbers), range must be <=36 (3 octaves)
offset = 5 #transpose how many steps up or down (positive = higher); for shifting to C major to eliminate half notes
delay = 5000 #ms before arduino code runs for alt tabbing to genshin etc

#genshin ignores SendInput without admin, since it runs elevated
#to work around this (and also most anticheat detection if any),
#this code does not play via pyautogui but generates arduino code for the macro instead,
#in order to play it directly as from an HID device instead of virtual keystrokes

#midilyre-gen.py <file path>
#generates a simple 32u4 or SAMD micro based Arduino-compatible code file that performs the inputted midi on Genshin Impact's lyre via keystrokes.

try:
    mid_file = sys.argv[1]
except IndexError:
    print('Please include the input file path as an argument!')
    quit()

mid = mido.MidiFile(mid_file)
print('Opening: ' + str(mid))

try:
    os.mkdir('output')
except:
    pass

with open('output/' + mid.filename.rsplit('.', 1)[0] + '.ino', "w") as f:
    f.write('#include <Keyboard.h>\n\n')
    f.write('void setup() {\n')
    f.write('  Keyboard.begin();\n')
    f.write('  delay(' + str(delay) + ');\n')

    for msg in mid:
        if msg.type == 'key_signature':
            print(str(msg))

        if msg.type == 'note_on':
            if msg.velocity != 0:  #not muted
                note = msg.note + offset
                if note < octave_range[0] or note >= octave_range[1] or (note % 12) in [1, 3, 6, 8, 10]: #out of range and sharp not supported
                    print('Discarding unsupported note: ' + str(note))
                else:
                    f.write('  Keyboard.write(\'' + keymap[(note - octave_range[0])] + '\');\n')

        if msg.time != 0: #only include actual delays
            f.write('  delay(' + str(msg.time * 1000) + ");\n")  #mido.tick2second(msg.time, ticks_per_beat = mid.ticks_per_beat, tempo = current_tempo) needed if single track object

    f.write('}\n')
    f.write('void loop() {}')

print("Finished!")