Skip to content

Instantly share code, notes, and snippets.

@neko-neko-nyan
Created May 18, 2022 09:15
Show Gist options
  • Save neko-neko-nyan/2ce06d005fb706c6e3c689a53f99bdf8 to your computer and use it in GitHub Desktop.
Save neko-neko-nyan/2ce06d005fb706c6e3c689a53f99bdf8 to your computer and use it in GitHub Desktop.
MIDI file -> Genshin Impact Lyre
import asyncio
import time
import mido
import pynput.keyboard
KEY_STEPS = [
(0, pynput.keyboard.KeyCode.from_char('z')),
(2, pynput.keyboard.KeyCode.from_char('x')),
(4, pynput.keyboard.KeyCode.from_char('c')),
(5, pynput.keyboard.KeyCode.from_char('v')),
(7, pynput.keyboard.KeyCode.from_char('b')),
(9, pynput.keyboard.KeyCode.from_char('n')),
(11, pynput.keyboard.KeyCode.from_char('m')),
(12, pynput.keyboard.KeyCode.from_char('a')),
(14, pynput.keyboard.KeyCode.from_char('s')),
(16, pynput.keyboard.KeyCode.from_char('d')),
(17, pynput.keyboard.KeyCode.from_char('f')),
(19, pynput.keyboard.KeyCode.from_char('g')),
(21, pynput.keyboard.KeyCode.from_char('h')),
(23, pynput.keyboard.KeyCode.from_char('j')),
(24, pynput.keyboard.KeyCode.from_char('q')),
(26, pynput.keyboard.KeyCode.from_char('w')),
(28, pynput.keyboard.KeyCode.from_char('e')),
(29, pynput.keyboard.KeyCode.from_char('r')),
(31, pynput.keyboard.KeyCode.from_char('t')),
(33, pynput.keyboard.KeyCode.from_char('y')),
(35, pynput.keyboard.KeyCode.from_char('u')),
]
loop = asyncio.get_event_loop()
playing = False
class NoteKeyMap:
def __init__(self, i):
self.map = {i + n: key for n, key in KEY_STEPS}
self.max = max(self.map.keys())
self.min = min(self.map.keys())
def get_key(self, note):
if note > self.max:
if note - self.max > 2:
return None
return self.map[self.max]
if note < self.min:
if self.min - note > 2:
return None
return self.map[self.min]
if note in self.map:
return self.map[note]
i = j = note
while True:
iv = self.map.get(i)
jv = self.map.get(j)
if iv and jv:
return iv
if iv:
return iv
if jv:
return jv
i += 1
j -= 1
def has_key(self, note):
return note in self.map
def find_best_map(mid):
note_count = {}
for track in mid.tracks:
for msg in track:
if msg.type == "note_on":
if msg.note not in note_count:
note_count[msg.note] = 1
else:
note_count[msg.note] += 1
if not note_count:
return NoteKeyMap(0)
notes = sorted(note_count.keys())
best_map = None
best_hits = -1
for cur_root in range(max(notes[0] - 24, 0), min(notes[-1] + 25, 128)):
cur_map = NoteKeyMap(cur_root)
cur_hits = 0
for note, count in note_count.items():
if 48 <= note < 84:
if note in cur_map:
cur_hits += count
if cur_hits > best_hits:
best_hits = cur_hits
best_map = cur_map
return best_map
async def play():
global playing
mid = mido.MidiFile("song.mid")
map = find_best_map(mid)
for t in mid.tracks:
i = 0
while i < len(t):
if isinstance(t[i], mido.MetaMessage):
del t[i]
else:
i += 1
keyboard = pynput.keyboard.Controller()
last_clock = time.time()
for msg in mid:
if not playing:
return
if msg.time > 0:
await asyncio.sleep(msg.time - (time.time() - last_clock))
last_clock += msg.time
if msg.type == "note_on" and msg.channel != 9:
if key := map.get_key(msg.note):
keyboard.press(key)
elif msg.type == "note_off" and msg.channel != 9:
if key := map.get_key(msg.note):
keyboard.release(key)
playing = False
def on_press(key):
global playing
if key == pynput.keyboard.Key.tab:
if playing:
playing = False
else:
playing = True
loop.call_soon_threadsafe(lambda: loop.create_task(play()))
if __name__ == "__main__":
pynput.keyboard.Listener(on_press=on_press).start()
loop.run_forever()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment