Created
May 18, 2022 09:15
-
-
Save neko-neko-nyan/2ce06d005fb706c6e3c689a53f99bdf8 to your computer and use it in GitHub Desktop.
MIDI file -> Genshin Impact Lyre
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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