Spaces:
Running
Running
| from __future__ import annotations | |
| from io import BytesIO | |
| from pathlib import Path | |
| from zipfile import ZIP_DEFLATED, ZipFile | |
| import mido | |
| from .schemas import PhrasePayload | |
| MIDI_EXPORT_TEMPO = 500_000 | |
| MIDI_EXPORT_TICKS_PER_BEAT = 480 | |
| def phrase_payload_to_midi_bytes(payload: PhrasePayload) -> bytes: | |
| midi_file = mido.MidiFile(type=0, ticks_per_beat=MIDI_EXPORT_TICKS_PER_BEAT) | |
| track = mido.MidiTrack() | |
| midi_file.tracks.append(track) | |
| track.append(mido.MetaMessage("set_tempo", tempo=MIDI_EXPORT_TEMPO, time=0)) | |
| current_seconds = 0.0 | |
| previous_tick = 0 | |
| for event in payload.events: | |
| current_seconds += float(event.delta_seconds) | |
| absolute_tick = max( | |
| previous_tick, | |
| int( | |
| round( | |
| mido.second2tick( | |
| current_seconds, | |
| MIDI_EXPORT_TICKS_PER_BEAT, | |
| MIDI_EXPORT_TEMPO, | |
| ) | |
| ) | |
| ), | |
| ) | |
| delta_ticks = absolute_tick - previous_tick | |
| previous_tick = absolute_tick | |
| velocity = int(event.velocity) if event.type == "note_on" else 0 | |
| track.append( | |
| mido.Message( | |
| event.type, | |
| note=int(event.note), | |
| velocity=velocity, | |
| channel=int(event.channel), | |
| time=delta_ticks, | |
| ) | |
| ) | |
| track.append(mido.MetaMessage("end_of_track", time=0)) | |
| buffer = BytesIO() | |
| midi_file.save(file=buffer) | |
| return buffer.getvalue() | |
| def write_phrase_payload_midi(payload: PhrasePayload, path: Path) -> None: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| path.write_bytes(phrase_payload_to_midi_bytes(payload)) | |
| def midi_files_to_zip_bytes(files: list[tuple[str, bytes]]) -> bytes: | |
| buffer = BytesIO() | |
| with ZipFile(buffer, mode="w", compression=ZIP_DEFLATED) as archive: | |
| for file_name, content in files: | |
| archive.writestr(file_name, content) | |
| return buffer.getvalue() | |