turns-00048.parquet:29309
ec41bb95faf6f7b66690128d
turn 1/1o1-mini-2024-09-12EnglishIndonesia2423 words
degenerate_repetitionAbsentFinal dense release
USER
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"}
};ASSISTANT
Certainly! Let's enhance your MIDI processing script by incorporating advanced tempo automation and improving accuracy in note handling. Here's a step-by-step overview of the enhancements:
1. **Advanced Tempo Automation:**
- **Tempo Mapping:** Handle multiple tempo changes within a MIDI file by creating a tempo map. This ensures accurate timing calculations even when the tempo varies throughout the piece.
- **Dynamic Time Conversion:** Adjust the conversion of ticks to seconds dynamically based on the current tempo segment.
2. **Improved Note Accuracy:**
- **Concurrent Notes Handling:** Properly manage overlapping notes and multiple notes occurring simultaneously across different tracks and channels.
- **Enhanced Note Tracking:** Ensure that every `note_on` event has a corresponding `note_off` event, preventing mismatches and ensuring the integrity of note durations.
3. **Integration with Key Mappings:**
- **Flexible Mapping Utilization:** Incorporate the provided key mappings (`LIMITED` and `FULL`) to translate MIDI notes into their corresponding key representations. This can be useful for applications like virtual instruments or interactive tools.
4. **Additional Enhancements:**
- **Error Handling:** Add robust error handling to manage unexpected MIDI file structures or corrupted data.
- **Output Options:** Offer options to output the parsed notes to different formats (e.g., CSV, JSON) for further analysis or integration with other tools.
Below is the enhanced Python script incorporating the above improvements:
```python
import mido
from mido import MidiFile
import math
from collections import defaultdict, namedtuple
import bisect
import json
# Define a namedtuple for tempo changes
TempoChange = namedtuple('TempoChange', ['tick', 'tempo'])
# 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 with Advanced Tempo Handling
def parse_midi_file(file_path):
midi = MidiFile(file_path)
piano_keys = generate_piano_keys()
notes = []
# Initialize tempo map with default tempo
tempo_map = [TempoChange(tick=0, tempo=500000)] # Default tempo (microseconds per beat)
ticks_per_beat = midi.ticks_per_beat
# Gather all tempo changes from all tracks
for track in midi.tracks:
absolute_tick = 0
for msg in track:
absolute_tick += msg.time
if msg.type == 'set_tempo':
tempo_map.append(TempoChange(tick=absolute_tick, tempo=msg.tempo))
# Sort the tempo map by tick
tempo_map.sort(key=lambda tc: tc.tick)
# Function to convert tick to second considering tempo changes
def tick_to_second(tick):
total_seconds = 0
previous_tick = 0
for tempo_change in tempo_map:
if tick < tempo_change.tick:
delta_ticks = tick - previous_tick
seconds = mido.tick2second(delta_ticks, ticks_per_beat, tempo_map[i-1].tempo) if 'i' in locals() else mido.tick2second(delta_ticks, ticks_per_beat, tempo_change.tempo)
total_seconds += seconds
return total_seconds
delta_ticks = tempo_change.tick - previous_tick
seconds = mido.tick2second(delta_ticks, ticks_per_beat, tempo_change.tempo)
total_seconds += seconds
previous_tick = tempo_change.tick
i = tempo_map.index(tempo_change)
# After last tempo change
delta_ticks = tick - previous_tick
seconds = mido.tick2second(delta_ticks, ticks_per_beat, tempo_map[-1].tempo)
total_seconds += seconds
return total_seconds
# Dictionary to keep track of note on times
note_on_times = defaultdict(list)
# Process each track
for track in midi.tracks:
absolute_tick = 0
for msg in track:
absolute_tick += msg.time
if not msg.is_meta and msg.channel in [0, 1]: # Piano channels (adjust as needed)
if msg.type == 'note_on' and msg.velocity > 0:
note_on_times[msg.note].append(absolute_tick)
elif (msg.type == 'note_off') or (msg.type == 'note_on' and msg.velocity == 0):
if note_on_times[msg.note]:
start_tick = note_on_times[msg.note].pop(0)
duration_tick = absolute_tick - start_tick
# Convert ticks to seconds
start_sec = tick_to_second(start_tick)
duration_sec = mido.tick2second(duration_tick, ticks_per_beat, tempo_map[i].tempo) if 'i' in locals() else mido.tick2second(duration_tick, ticks_per_beat, tempo_map[0].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
# Configuration for Key Mappings
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"
},
"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"
}
}
# Main Function to Convert MIDI to Keys with Enhanced Features
def midi_to_keys(file_path, mapping_type="LIMITED", output_format="print", output_path=None):
if mapping_type not in CONFIG_KEY_MAPPINGS:
print(f"Invalid mapping type '{mapping_type}'. Available types: {list(CONFIG_KEY_MAPPINGS.keys())}")
return
key_mapping = CONFIG_KEY_MAPPINGS[mapping_type]
notes = parse_midi_file(file_path)
# Sort notes by start time
notes.sort(key=lambda x: x['start_time'])
# Map notes to key representations
for note in notes:
mapped_key = key_mapping.get(note['note_name'], "Unmapped")
note['mapped_key'] = mapped_key
if output_format == "print":
# Display the notes
print(f"{'Time (s)':>10} | {'Duration (s)':>12} | {'Note':>6} | {'Key':>10} | {'Velocity':>8} | {'MIDI':>4}")
print("-" * 60)
for note in notes:
print(f"{note['start_time']:>10.4f} | {note['duration']:>12.4f} | {note['note_name']:>6} | {note['mapped_key']:>10} | {note['velocity']:>8} | {note['midi_number']:>4}")
elif output_format == "json":
# Output to JSON
if output_path:
with open(output_path, 'w') as f:
json.dump(notes, f, indent=4)
print(f"Notes have been exported to {output_path}")
else:
print(json.dumps(notes, indent=4))
elif output_format == "csv":
import csv
if output_path:
with open(output_path, 'w', newline='') as csvfile:
fieldnames = ['start_time', 'duration', 'note_name', 'mapped_key', 'velocity', 'midi_number']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for note in notes:
writer.writerow(note)
print(f"Notes have been exported to {output_path}")
else:
print("Please provide an output path for CSV format.")
else:
print(f"Unsupported output format '{output_format}'. Supported formats: print, json, csv.")
return notes
# Example usage
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Convert MIDI files to key mappings with advanced tempo handling.")
parser.add_argument("midi_file", help="Path to the MIDI file.")
parser.add_argument("--mapping", choices=["LIMITED", "FULL"], default="LIMITED", help="Type of key mapping to use.")
parser.add_argument("--output-format", choices=["print", "json", "csv"], default="print", help="Format of the output.")
parser.add_argument("--output-path", help="Path to save the output if not printing.")
args = parser.parse_args()
midi_to_keys(
file_path=args.midi_file,
mapping_type=args.mapping,
output_format=args.output_format,
output_path=args.output_path
)
```
### **Enhancements Breakdown**
1. **Advanced Tempo Automation:**
- **Tempo Mapping Collection:** The script collects all tempo changes across all tracks and sorts them based on their tick positions. This ensures that tempo changes are accurately reflected in the timing calculations.
- **Dynamic Tick Conversion:** The `tick_to_second` function dynamically calculates the time in seconds for any given tick, considering all preceding tempo changes. This provides precise timing even in complex MIDI files with multiple tempo shifts.
2. **Improved Note Accuracy:**
- **Robust Note Tracking:** By using a `defaultdict` of lists for `note_on_times`, the script can handle multiple overlapping notes of the same pitch by maintaining a stack-like structure for `note_on` events.
- **Channel Flexibility:** While the original script focused on channels 0 and 1, you can adjust the channels as needed or remove the channel filter to process all channels.
3. **Integration with Key Mappings:**
- **Flexible Mapping Selection:** Users can choose between "LIMITED" and "FULL" key mappings via command-line arguments.
- **Mapped Key Inclusion:** Each note in the output includes its corresponding mapped key based on the selected configuration.
4. **Additional Enhancements:**
- **Command-Line Interface (CLI):** Added a CLI using `argparse` to allow users to specify the MIDI file, mapping type, output format, and output path.
- **Multiple Output Formats:** Users can choose to print the results, export them as JSON, or save them as CSV files for further processing or integration.
- **Error Messages:** The script provides informative messages for invalid inputs or unsupported configurations.
- **Structured Output:** The printed table is neatly formatted with headers for better readability.
### **Usage Instructions**
1. **Prerequisites:**
- Ensure you have Python 3 installed.
- Install the required `mido` library if you haven't already:
```bash
pip install mido
```
- For some MIDI files, you might also need the `python-rtmidi` backend:
```bash
pip install python-rtmidi
```
2. **Save the Script:**
- Save the enhanced script to a file, e.g., `midi_processor.py`.
3. **Run the Script:**
- **Print Output with LIMITED Mappings:**
```bash
python midi_processor.py path_to_your_midi_file.mid
```
- **Use FULL Mappings and Export as JSON:**
```bash
python midi_processor.py path_to_your_midi_file.mid --mapping FULL --output-format json --output-path output_notes.json
```
- **Export as CSV:**
```bash
python midi_processor.py path_to_your_midi_file.mid --output-format csv --output-path output_notes.csv
```
### **Sample Output**
When running the script with the `--output-format print` option, you will get an output similar to the following:
```
Time (s) | Duration (s) | Note | Key | Velocity | MIDI
------------------------------------------------------------
0.0000 | 0.5000 | C4/C#4 | t | 64 | 60
0.5000 | 0.5000 | E4/Eb4 | u | 64 | 64
1.0000 | 1.0000 | G4/G#4 | o | 64 | 67
...
```
### **Final Notes**
- **Customization:** Feel free to modify the `CONFIG_KEY_MAPPINGS` to suit your specific key-mapping requirements or to add more mapping types.
- **Channel Selection:** The current script processes channels 0 and 1. Adjust the `msg.channel in [0, 1]` condition as needed to target different channels or include all channels.
- **Error Handling:** The script includes basic error handling. For production use, consider adding more comprehensive checks and exception management.
This enhanced script provides a more accurate and flexible way to process MIDI files, handle complex tempo changes, and map notes to desired key representations. Whether you're building a virtual instrument, an interactive music tool, or conducting music analysis, these improvements should offer a solid foundation.