From 51507bf354571a775269dac1fd547f29a8219172 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sat, 20 Jul 2024 18:14:56 +0300 Subject: [PATCH] add binary_to_midi.py --- python-programming/.gitignore | 3 + python-programming/binary_to_midi.py | 87 ++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 python-programming/binary_to_midi.py diff --git a/python-programming/.gitignore b/python-programming/.gitignore index 68bc17f..f455f24 100644 --- a/python-programming/.gitignore +++ b/python-programming/.gitignore @@ -158,3 +158,6 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# Output files +output.* diff --git a/python-programming/binary_to_midi.py b/python-programming/binary_to_midi.py new file mode 100644 index 0000000..b52af07 --- /dev/null +++ b/python-programming/binary_to_midi.py @@ -0,0 +1,87 @@ +#!/usr/bin/python3 + +# binary_to_midi.py +# +# Author: Intel A80486DX2-66 +# License: Creative Commons Zero 1.0 Universal or Unlicense +# SPDX-License-Identifier: CC0-1.0 OR Unlicense + +from decimal import Decimal +from midiutil import MIDIFile +from os import stat +from sys import argv + +NOTE_TABLE = {i: 52 + i for i in range(8 * 2)} + +is_prime = lambda n: n > 1 and all(n % i for i in range(2, n)) + +TEMPO = 110 +TIME_SIGNATURE = (4, 4) +INSTRUMENT_SELECTED = 1 +STEP_SIZE = 1 +DURATION_CONSTANT = 32 + + +def binary_to_midi(input_file, output_file): + print("Reading file") + with open(input_file, "rb") as f: + byte_data = f.read() + + print("Initialization") + pattern = MIDIFile(1, TIME_SIGNATURE, TEMPO) + pattern.addTempo(0, 0, TEMPO) + track = 0 + channel = 0 + time = 0 + + chords = [] + current_chord = [] + + previous_note = NOTE_TABLE[0] + byte_step = (len(byte_data) // 2**6) + 1 + + print("Converting") + taking_chord = False + for i, byte in enumerate(byte_data[::STEP_SIZE]): + is_rest = False + note = NOTE_TABLE[byte % len(NOTE_TABLE)] + is_previous_note = i > 0 and note == previous_note + + if byte % 51 == 25: + is_rest = True + elif byte % 12 == 0: + current_chord = [note] + chords.append(current_chord) + taking_chord = True + elif taking_chord: + current_chord.append(note) + taking_chord = False + + duration = (Decimal('0.25') if is_prime(byte) or byte % 11 < 6 else \ + Decimal('0.015')) * DURATION_CONSTANT + + if abs(previous_note - note) > 3 and not is_previous_note: + current_chord.append(0) + previous_note = note + + pattern.addProgramChange(0, 0, 0, INSTRUMENT_SELECTED) + for chord in chords: + for note in chord: + if is_rest: + pattern.addNote(track, channel, 0, time, duration, 0) + else: + pattern.addNote(track, channel, note, time, duration, 100) + time += duration + + print("Writing out") + with open(output_file, "wb") as file: + pattern.writeFile(file) + print(f"Saved, {stat(output_file).st_size} bytes") + + +if __name__ == "__main__": + if len(argv) < 2: + print("Fatal error: No file specified as argument.") + exit(1) + + binary_to_midi(argv[1], "output.mid")