Further enhance and add in advanced linear course with tempo automation and some accuracy stuff.

```
import mido
from mido import MidiFile
import math
from collections import defaultdict

# Generate Piano Key Mappings for 88 Keys
def generate_piano_keys():
    note_names_sharp = ['C', 'C#', 'D', 'D#', 'E', 'F',
                        'F#', 'G', 'G#', 'A', 'A#', 'B']
    note_names_flat  = ['C', 'Db', 'D', 'Eb', 'E', 'F',
                        'Gb', 'G', 'Ab', 'A', 'Bb', 'B']
    piano_keys = {}

    for midi_number in range(21, 109):  # MIDI numbers for piano keys
        key_number = midi_number - 20
        octave = (midi_number // 12) - 1
        note_index = midi_number % 12
        note_name_sharp = note_names_sharp[note_index] + str(octave)
        note_name_flat = note_names_flat[note_index] + str(octave)

        if note_names_sharp[note_index] != note_names_flat[note_index]:
            note_name = f"{note_name_sharp}/{note_name_flat}"
        else:
            note_name = note_name_sharp

        frequency = 2 ** ((midi_number - 69) / 12) * 440.0

        piano_keys[midi_number] = {
            'key_number': key_number,
            'note_name': note_name,
            'frequency': round(frequency, 4),
            'midi_number': midi_number,
            'octave': octave
        }
    return piano_keys

# Parse MIDI File and Extract Notes
def parse_midi_file(file_path):
    midi = MidiFile(file_path)
    piano_keys = generate_piano_keys()
    notes = []

    tempo = 500000  # Default tempo (microseconds per beat)
    ticks_per_beat = midi.ticks_per_beat

    # Dictionary to keep track of note on times
    note_on_times = defaultdict(list)

    current_time = 0

    for track in midi.tracks:
        absolute_time = 0
        for msg in track:
            absolute_time += msg.time
            if msg.type == 'set_tempo':
                tempo = msg.tempo
            if not msg.is_meta and msg.channel in [0, 1]:  # Piano channels
                if msg.type == 'note_on' and msg.velocity > 0:
                    note_on_times[msg.note].append(absolute_time)
                elif (msg.type == 'note_off') or (msg.type == 'note_on' and msg.velocity == 0):
                    if note_on_times[msg.note]:
                        start_time = note_on_times[msg.note].pop(0)
                        duration = absolute_time - start_time
                        # Convert time to seconds
                        start_sec = mido.tick2second(start_time, ticks_per_beat, tempo)
                        duration_sec = mido.tick2second(duration, ticks_per_beat, tempo)
                        note_info = piano_keys.get(msg.note, {'note_name': 'Unknown'})
                        notes.append({
                            'note_name': note_info['note_name'],
                            'start_time': round(start_sec, 4),
                            'duration': round(duration_sec, 4),
                            'velocity': msg.velocity,
                            'midi_number': msg.note
                        })
    return notes

# Main Function to Convert MIDI to Keys
def midi_to_keys(file_path):
    notes = parse_midi_file(file_path)
    # Sort notes by start time
    notes.sort(key=lambda x: x['start_time'])
    # Display the notes
    for note in notes:
        print(f"Time: {note['start_time']:>6}s, Duration: {note['duration']:>6}s, "
              f"Note: {note['note_name']:>4}, Velocity: {note['velocity']:>3}, "
              f"MIDI: {note['midi_number']}")
    return notes

# Example usage
if __name__ == "__main__":
    midi_file_path = 'Fiction_-_Wotakoi_Love_is_Hard_for_Otaku_Piano.mscz.mid'  # Replace with your MIDI file path
    midi_to_keys(midi_file_path)
```

Reference:

```
    config.key_mappings["LIMITED"] = {
        {"C2", "1"}, {"C#2", "!"}, {"D2", "2"}, {"D#2", "@"}, {"E2", "3"}, {"F2", "4"},
        {"F#2", "$"}, {"G2", "5"}, {"G#2", "%"}, {"A2", "6"}, {"A#2", "^"}, {"B2", "7"},
        {"C3", "8"}, {"C#3", "*"}, {"D3", "9"}, {"D#3", "("}, {"E3", "0"}, {"F3", "q"},
        {"F#3", "Q"}, {"G3", "w"}, {"G#3", "W"}, {"A3", "e"}, {"A#3", "E"}, {"B3", "r"},
        {"C4", "t"}, {"C#4", "T"}, {"D4", "y"}, {"D#4", "Y"}, {"E4", "u"}, {"F4", "i"},
        {"F#4", "I"}, {"G4", "o"}, {"G#4", "O"}, {"A4", "p"}, {"A#4", "P"}, {"B4", "a"},
        {"C5", "s"}, {"C#5", "S"}, {"D5", "d"}, {"D#5", "D"}, {"E5", "f"}, {"F5", "g"},
        {"F#5", "G"}, {"G5", "h"}, {"G#5", "H"}, {"A5", "j"}, {"A#5", "J"}, {"B5", "k"},
        {"C6", "l"}, {"C#6", "L"}, {"D6", "z"}, {"D#6", "Z"}, {"E6", "x"}, {"F6", "c"},
        {"F#6", "C"}, {"G6", "v"}, {"G#6", "V"}, {"A6", "b"}, {"A#6", "B"}, {"B6", "n"},
        {"C7", "m"}
    };

    config.key_mappings["FULL"] = {
        {"A0", "ctrl+1"}, {"A#0", "ctrl+2"}, {"B0", "ctrl+3"}, {"C1", "ctrl+4"}, {"C#1", "ctrl+5"},
        {"D1", "ctrl+6"}, {"D#1", "ctrl+7"}, {"E1", "ctrl+8"}, {"F1", "ctrl+9"}, {"F#1", "ctrl+0"},
        {"G1", "ctrl+q"}, {"G#1", "ctrl+w"}, {"A1", "ctrl+e"}, {"A#1", "ctrl+r"}, {"B1", "ctrl+t"},
        {"C2", "1"}, {"C#2", "!"}, {"D2", "2"}, {"D#2", "@"}, {"E2", "3"}, {"F2", "4"},
        {"F#2", "$"}, {"G2", "5"}, {"G#2", "%"}, {"A2", "6"}, {"A#2", "^"}, {"B2", "7"},
        {"C3", "8"}, {"C#3", "*"}, {"D3", "9"}, {"D#3", "("}, {"E3", "0"}, {"F3", "q"},
        {"F#3", "Q"}, {"G3", "w"}, {"G#3", "W"}, {"A3", "e"}, {"A#3", "E"}, {"B3", "r"},
        {"C4", "t"}, {"C#4", "T"}, {"D4", "y"}, {"D#4", "Y"}, {"E4", "u"}, {"F4", "i"},
        {"F#4", "I"}, {"G4", "o"}, {"G#4", "O"}, {"A4", "p"}, {"A#4", "P"}, {"B4", "a"},
        {"C5", "s"}, {"C#5", "S"}, {"D5", "d"}, {"D#5", "D"}, {"E5", "f"}, {"F5", "g"},
        {"F#5", "G"}, {"G5", "h"}, {"G#5", "H"}, {"A5", "j"}, {"A#5", "J"}, {"B5", "k"},
        {"C6", "l"}, {"C#6", "L"}, {"D6", "z"}, {"D#6", "Z"}, {"E6", "x"}, {"F6", "c"},
        {"F#6", "C"}, {"G6", "v"}, {"G#6", "V"}, {"A6", "b"}, {"A#6", "B"}, {"B6", "n"},
        {"C7", "m"}, {"C#7", "ctrl+y"}, {"D7", "ctrl+u"}, {"D#7", "ctrl+i"}, {"E7", "ctrl+o"},
        {"F7", "ctrl+p"}, {"F#7", "ctrl+a"}, {"G7", "ctrl+s"}, {"G#7", "ctrl+d"}, {"A7", "ctrl+f"},
        {"A#7", "ctrl+g"}, {"B7", "ctrl+h"}, {"C8", "ctrl+j"}
    };