From 523c11281c24b1d63baa87085a6b65683750b429 Mon Sep 17 00:00:00 2001 From: janis Date: Sun, 27 Feb 2022 13:48:51 +0100 Subject: [PATCH] Finished Version 1.0. Guide for installation will be available soon --- backend/csv_parsers.py | 122 +++++++++++++++++++++++++++++++++++++ backend/midi_management.py | 77 +++++++++++++++++++++++ backend/temp.csv | 1 + dev/hr.py | 32 ++++++++++ gui/filechooser.kv | 22 +++++++ gui/gui.kv | 20 ++++++ gui/loading_screen.kv | 24 ++++++++ midi_converter.py | 80 ++++++++++++++++++++++++ 8 files changed, 378 insertions(+) create mode 100644 backend/csv_parsers.py create mode 100644 backend/midi_management.py create mode 100644 backend/temp.csv create mode 100644 dev/hr.py create mode 100644 gui/filechooser.kv create mode 100644 gui/gui.kv create mode 100644 gui/loading_screen.kv create mode 100644 midi_converter.py diff --git a/backend/csv_parsers.py b/backend/csv_parsers.py new file mode 100644 index 0000000..d1f593c --- /dev/null +++ b/backend/csv_parsers.py @@ -0,0 +1,122 @@ +"""@package docstring +This is a simplification of the csv module""" + +import csv + + +class CsvRead: + """This is a class that reads csv files and depending on the module selected does do different things with it""" + def __init__(self): + self.__imp = "" + self.__raw = "" + self.__raw_list = "" + + def importing(self, path): + """Returns a list of the imported csv-file, requires path, either direct system path or relative path""" + self.__imp = open(path) + self.__raw = csv.reader(self.__imp, delimiter=',') + self.__raw_list = list(self.__raw) + self.__imp.close() + return self.__raw_list + + +class CsvWrite: + """This is a class that modifies csv files""" + def __init__(self): + self.__impl = [] + self.__strpop = [] + self.__removed = [] + self.__removing = 0 + self.__change = 0 + self.__appending = 0 + self.__imp = [] + self.__raw = [] + + def rem_str(self, path, row): + """Opens the csv-file in write mode which is specified as an argument either as direct or relative path""" + self.__imp = open(path) + self.__raw = csv.reader(self.__imp, delimiter=',') + self.__impl = list(self.__raw) + self.__removed = self.__impl.pop(row + 1) + with open(path, "w") as removedata: + self.__removing = csv.writer(removedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__removing.writerow(self.__impl.pop(0)) + while len(self.__impl) > 0: + with open(path, "a") as removedata: + self.__removing = csv.writer(removedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__removing.writerow(self.__impl.pop(0)) + self.__imp.close() + removedata.close() + + + def chg_str(self, path, row, pos, new_value): + """Opens the csv-file in write mode to change a value, e.g. if a recipes is changed.""" + self.__imp = open(path) + self.__raw = csv.reader(self.__imp, delimiter=',') + self.__impl = list(self.__raw) + self.__strpop = self.__impl.pop(row) + self.__strpop.pop(pos) + self.__strpop.insert(pos, new_value) + self.__impl.insert(row, self.__strpop) + with open(path, "w") as changedata: + self.__change = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__change.writerow(self.__impl.pop(0)) + while len(self.__impl) > 0: + with open(path, "a") as changedata: + self.__removing = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__removing.writerow(self.__impl.pop(0)) + self.__imp.close() + changedata.close() + + def chg_str_rem(self, path, row, pos): + """Opens the csv-file in write mode to change a value, e.g. if a recipes is changed.""" + self.__imp = open(path) + self.__raw = csv.reader(self.__imp, delimiter=',') + self.__impl = list(self.__raw) + self.__strpop = self.__impl.pop(row) + self.__strpop.pop(pos) + self.__strpop.pop(pos) + self.__impl.insert(row, self.__strpop) + with open(path, "w") as changedata: + self.__change = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__change.writerow(self.__impl.pop(0)) + while len(self.__impl) > 0: + with open(path, "a") as changedata: + self.__removing = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__removing.writerow(self.__impl.pop(0)) + self.__imp.close() + changedata.close() + + def chg_str_add(self, path, row, new_value1, new_value2): + """Opens the csv-file in write mode to change a value, e.g. if a recipes is changed.""" + self.__imp = open(path) + self.__raw = csv.reader(self.__imp, delimiter=',') + self.__impl = list(self.__raw) + self.__strpop = self.__impl.pop(row) + self.__strpop.append(new_value1) + self.__strpop.append(new_value2) + self.__impl.insert(row, self.__strpop) + with open(path, "w") as changedata: + self.__change = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__change.writerow(self.__impl.pop(0)) + while len(self.__impl) > 0: + with open(path, "a") as changedata: + self.__removing = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__removing.writerow(self.__impl.pop(0)) + self.__imp.close() + changedata.close() + + def app_str(self, path, value): + """Opens the csv-file in append mode and writes given input. CsvWrite.app_str(path, value). + Path can be specified both as direct or relative. value is a list. Will return an error if type of value is + not a list.""" + with open(path, "a") as appenddata: + self.__appending = csv.writer(appenddata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__appending.writerow(value) + appenddata.close() + + def write_str(self, path, value): + with open(path, "w") as writedata: + self.__change = csv.writer(writedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__change.writerow(value) + writedata.close() diff --git a/backend/midi_management.py b/backend/midi_management.py new file mode 100644 index 0000000..ec32bf6 --- /dev/null +++ b/backend/midi_management.py @@ -0,0 +1,77 @@ +from mido import MidiFile +import pyperclip as pc + + +class MidiManagement: + def __init__(self): + pass + + def addToClipboard(self, text): + pc.copy(text) + + def analyse_track(self, path, trackname): + self.midi_imp = MidiFile(path, clip=True) + self.tracks = [] + for self.track in self.midi_imp.tracks: + self.tracks.append(str(self.track)) + self.tracks.pop(0) + self.track_ext = self.tracks.pop(0) + self.trackn = 0 + while self.track_ext != trackname: + self.track_ext = self.tracks.pop(0) + self.trackn += 1 + self.extracted_track = self.track_ext + self.__output_list = [] + for self.msg in self.midi_imp.tracks[self.trackn]: + self.ext = str(self.msg) + self.note = self.ext[23:25] + try: + self.note_height = int(self.note) + self.note_decod_oct = self.note_height // 12 + self.note_decode_tone = self.note_height % 12 + if self.note_decode_tone == 1: + self.note_ext = "C" + elif self.note_decode_tone == 2: + self.note_ext = "C#" + elif self.note_decode_tone == 3: + self.note_ext = "D" + elif self.note_decode_tone == 4: + self.note_ext = "D#" + elif self.note_decode_tone == 5: + self.note_ext = "E" + elif self.note_decode_tone == 6: + self.note_ext = "F" + elif self.note_decode_tone == 7: + self.note_ext = "F#" + elif self.note_decode_tone == 8: + self.note_ext = "G" + elif self.note_decode_tone == 9: + self.note_ext = "G#" + elif self.note_decode_tone == 10: + self.note_ext = "A" + elif self.note_decode_tone == 11: + self.note_ext = "A#" + elif self.note_decode_tone == 12: + self.note_ext = "H" + + self.ext_shortened = self.ext[40:] + self.pos = 0 + for buchstabe in self.ext_shortened: + if buchstabe == "=": + self.pos += 1 + break + else: + self.pos += 1 + + self.timing_exp = self.ext_shortened[self.pos:] + self.__output = self.note_ext + self.__output += f":{self.timing_exp}" + self.__output_list.append(str(self.__output)) + + except: + pass + + self.addToClipboard(str(self.__output_list)) + + + diff --git a/backend/temp.csv b/backend/temp.csv new file mode 100644 index 0000000..a75b81c --- /dev/null +++ b/backend/temp.csv @@ -0,0 +1 @@ +/home/janis/Desktop/Victory.mid diff --git a/dev/hr.py b/dev/hr.py new file mode 100644 index 0000000..11f7878 --- /dev/null +++ b/dev/hr.py @@ -0,0 +1,32 @@ +from kivy.lang import Builder + +from kivymd.app import MDApp + +KV = ''' +#:import KivyLexer kivy.extras.highlight.KivyLexer +#:import HotReloadViewer kivymd.utils.hot_reload_viewer.HotReloadViewer + + +BoxLayout: + HotReloadViewer: + size_hint_x: .3 + path: app.path_to_kv_file + errors: True + errors_text_color: 1, 1, 0, 1 + errors_background_color: app.theme_cls.bg_dark +''' + + +class Example(MDApp): + path_to_kv_file = "../gui/loading_screen.kv" + + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + def update_kv_file(self, text): + with open(self.path_to_kv_file, "w") as kv_file: + kv_file.write(text) + + +Example().run() diff --git a/gui/filechooser.kv b/gui/filechooser.kv new file mode 100644 index 0000000..acd773e --- /dev/null +++ b/gui/filechooser.kv @@ -0,0 +1,22 @@ +FileChooserScreen: + name: "ChooseFile" + md_bg_color: (0, 0, 0, 1) + BoxLayout: + size: root.size + pos: root.pos + orientation: "vertical" + FileChooserListView: + id: filechooser + + BoxLayout: + size_hint_y: None + height: 30 + Button: + text: "Cancel" + on_release: + app.root.current = "Home" + root.manager.transition.direction = "right" + + Button: + text: "Load" + on_release: root.load(filechooser.path, filechooser.selection) \ No newline at end of file diff --git a/gui/gui.kv b/gui/gui.kv new file mode 100644 index 0000000..340581f --- /dev/null +++ b/gui/gui.kv @@ -0,0 +1,20 @@ +HomeScreen: + name: "Home" + md_bg_color: app.theme_cls.accent_color + GridLayout: + cols:1 + Label: + text: "MIDI to Micro:bit Sound converter" + font_size: 30 + bold: True + italic: True + color: (50, 50, 255, 1) + Label: + id: infobox + text: "Output will be automatically added to your clipboard once you have selected a file" + Button: + text: "choose file to start" + background_color: app.theme_cls.primary_color + on_release: + app.root.current = "ChooseFile" + root.manager.transition.direction = "left" diff --git a/gui/loading_screen.kv b/gui/loading_screen.kv new file mode 100644 index 0000000..d6656e0 --- /dev/null +++ b/gui/loading_screen.kv @@ -0,0 +1,24 @@ +TrackChooseScreen: + name: "Track" + md_bg_color: app.theme_cls.accent_color + GridLayout: + cols:1 + Label: + text: "Track selection" + font_size: 30 + bold: True + italic: True + color: (50, 50, 255, 1) + FloatLayout: + Spinner: + id: track_spinner + size_hint: 0.7, 0.2 + pos_hint: {"x": 0.15, "y":0.5} + background_color: (0, 0, 0, 1) + text: "Select a track" + values: ["Test"] + Button: + text: "confirm" + background_color: app.theme_cls.primary_color + on_release: + root.extract() \ No newline at end of file diff --git a/midi_converter.py b/midi_converter.py new file mode 100644 index 0000000..ac11b73 --- /dev/null +++ b/midi_converter.py @@ -0,0 +1,80 @@ +from kivy.uix.screenmanager import ScreenManager +from kivymd.uix.screen import MDScreen +from kivy.uix.popup import Popup +from kivy.properties import ObjectProperty +from kivy.lang import Builder +from kivy.uix.label import Label +from kivymd.app import MDApp +from mido import MidiFile +import os +import backend.midi_management +import backend.csv_parsers +import time + + +class HomeScreen(MDScreen): + pass + + +class FileChooserScreen(MDScreen): + loadfile = ObjectProperty(None) + def load(self, path, filename): + try: + self.path = os.path.join(path, filename[0]) + try: + self.mid = MidiFile(self.path, clip=True) + backend.csv_parsers.CsvWrite().write_str("./backend/temp.csv", [self.path]) + self.tracks = [] + for self.track in self.mid.tracks: + self.tracks.append(str(self.track)) + self.tracks.pop(0) + if len(self.tracks) > 1: + screen_manager.get_screen("Track").ids.track_spinner.values = self.tracks + screen_manager.current = "Track" + screen_manager.transition.direction = "up" + else: + screen_manager.current = "Home" + screen_manager.transition.direction = "right" + except: + self.popup_fe = Popup(title="FileError", content=Label(text="Please select a MIDI-File!"), + size_hint=(0.4, 0.4), auto_dismiss=True) + self.popup_fe.open() + + except: + self.popup_foldererror = Popup(title="FileError", content=Label(text="Only MIDI-Files allowed, not folder"), + size_hint=(0.4, 0.4), auto_dismiss=True) + self.popup_foldererror.open() + + +class TrackChooseScreen(MDScreen): + def extract(self): + self.chosen_track = self.ids.track_spinner.text + if self.chosen_track == "Select a track": + self.popup_ns = Popup(title="NoSelectionError", content=Label(text="Please select a Track!"), + size_hint=(0.4, 0.4), auto_dismiss=True) + self.popup_ns.open() + else: + self.path = backend.csv_parsers.CsvRead().importing("./backend/temp.csv").pop(0) + self.path_transmit = self.path.pop(0) + backend.midi_management.MidiManagement().analyse_track(str(self.path_transmit), self.chosen_track) + screen_manager.get_screen("Home").ids.infobox.text = "The command has been copied to the clipboard" + screen_manager.current = "Home" + screen_manager.transition.direction = "right" + + + +class MidiConverter(MDApp): + global screen_manager + screen_manager = ScreenManager() + + def build(self): + self.title = "Midi-Microbit-Converter" + self.theme_cls.primary_palette = "Blue" + self.theme_cls.accent_palette = "BlueGray" + screen_manager.add_widget(Builder.load_file("./gui/gui.kv")) + screen_manager.add_widget(Builder.load_file("./gui/filechooser.kv")) + screen_manager.add_widget(Builder.load_file("./gui/loading_screen.kv")) + + return screen_manager + +MidiConverter().run() \ No newline at end of file