diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/README.md b/README.md
index 25fa707..dc0928b 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,66 @@
-# midi-micro-bit_sound-converter
+
+
Midi to Micro:bit Sound converter
+
+
+
+

+

+

+

+
+

+

+

+

+

+
+
)
+
)
+

+

+
+
This app allows you to convert a midi file to the code needed for micro:bit programming
Creating Music with the micro:bit is a hassle. This little app will allow you to take any midi-file and convert it into the list needed for micro:bit.
-## INSTALLATION:
-Download the files by clicking on code, then on zip.
-You will need to install some dependencies, as well as python 3.8. If you haven't already go ahead and download python 3.8 and make sure to also include pip,
-as this will be used right after. Now, you will need to type the following commands in the terminal / command prompt:
-pip install kivy[base]
-pip install kivymd
-pip install pyperclip
-pip install mido
+# Installation
+Download the files by clicking on code, then on zip or clone the repo locally using
+```
+git clone https://github.com/janishutz/midi-micro-bit_sound-converter
+```
-You can run the app by heading into the folder you downloaded the zip file into, unzipping it and then by running the midi-converter.py file in the terminal / command prompt. (python3 midi-converter.py)
-You may do this as follows:
-### Linux and MacOS:
-Use cd./Path/To/File
+Then, run
+```
+pip install -r requirements.txt
+```
+in the repo's folder (i.e. the folder you just cloned or downloaded and extracted)
-### Linux
-You may download the bash script that is located under releases. Give it execute permissions (e.g run chmod +x ./Path/To/File) and then run the file with ./Path/To/File
+Alternatively, create a venv using
+```
+python -m venv midi-converter
+```
-### Windows:
-Click on the navigation bar (the one where the path is displayed) and type: cmd
+and activate it using
+```
+source ./midi-converter/bin/active
+```
+
+The dependencies of this project are `mido`, `pyperclip`, `kivymd` and `kivy[base]`
-### SPECIAL notes for Linux users:
-You'll need to install some other dependencies first. Use your distro's package manager (apt-get for Debian based distros, dnf for Fedora based and pacman for arch-based distros). I'll show an example with debian based distros here: (you may also run the script under releases if you run a debian based distro.
-sudo apt-get install xclip
-sudo apt-get install xsel
-sudo apt-get install wl-clipboard
+# Running
+Open a terminal in the file location where you saved / cloned this repo to. Type
+```
+python midi_converter.py
+```
-### OTHER OPTION:
-You may also run it in a venv (Virtual Environment), e.g. in Thonny. You still must install kivy[base], kivymd, pyperclip and mido in that venv (by using the manager of the IDE you are running)
+to run the app
+
+
+## Notes for Linux users:
+On some Linux distros, `xclip` and `xsel` don't come pre-installed. Install these dependencies.
+
+
+# Development
+Be warned, the code base is still very ugly. I only spent about two hours cleaning up the old code, so it still looks ugly. I will probably not clean up the code much more. Some variable names will simply stay weird
diff --git a/backend/csv_parsers.py b/backend/csv_parsers.py
deleted file mode 100644
index d1f593c..0000000
--- a/backend/csv_parsers.py
+++ /dev/null
@@ -1,122 +0,0 @@
-"""@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
deleted file mode 100644
index 2d1a962..0000000
--- a/backend/midi_management.py
+++ /dev/null
@@ -1,96 +0,0 @@
-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))
- if len(self.tracks) > 1:
- self.tracks.pop(0)
- else:
- pass
- 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]
- if self.ext[0:8] == "note_on ":
- 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 += str(self.note_decod_oct)
- self.__output += f":{self.timing_exp}"
- self.__output_list.append(str(self.__output))
-
- except:
- pass
- elif self.ext[0:8] == "note_off":
- 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.__output = "R"
- self.__output += f":{self.pos}"
-
- self.timing_exp = self.ext_shortened[self.pos:]
- self.__output_list.append(self.__output)
- else:
- pass
-
- self.addToClipboard(str(self.__output_list))
-
diff --git a/backend/temp.csv b/backend/temp.csv
deleted file mode 100644
index d867947..0000000
--- a/backend/temp.csv
+++ /dev/null
@@ -1 +0,0 @@
-/home/janis/Desktop/Water Drops Melodie.mid
diff --git a/dev/hr.py b/dev/hot_reload.py
similarity index 100%
rename from dev/hr.py
rename to dev/hot_reload.py
diff --git a/gui/loading_screen.kv b/gui/loading_screen.kv
index d6656e0..aadbc86 100644
--- a/gui/loading_screen.kv
+++ b/gui/loading_screen.kv
@@ -10,15 +10,15 @@ TrackChooseScreen:
italic: True
color: (50, 50, 255, 1)
FloatLayout:
- Spinner:
- id: track_spinner
+ MDRaisedButton:
+ size: "200dp", "50dp"
+ pos_hint: {"center_x": 0.5, "center_y": 0.5}
+ on_release: root.show_dropdown(self)
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
+ root.extract()
diff --git a/lib/midi_management.py b/lib/midi_management.py
new file mode 100644
index 0000000..e11a238
--- /dev/null
+++ b/lib/midi_management.py
@@ -0,0 +1,99 @@
+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):
+ mid = MidiFile(path, clip=True)
+ tracks = []
+ for i, track in enumerate(mid.tracks):
+ tracks.append('{} (Track {})'.format(track.name, i))
+ if len(tracks) > 1:
+ tracks.pop(0)
+ else:
+ pass
+ extracted_track = tracks.pop(0)
+ tracknumber = 0
+ while extracted_track != trackname:
+ extracted_track = tracks.pop(0)
+ tracknumber += 1
+ output_list = []
+
+ # Track messages
+ for msg in mid.tracks[tracknumber]:
+ midi_msg = str(msg)
+ if midi_msg[0:8] == "note_on ":
+ try:
+ msg_loc = midi_msg.index('note=')
+ note = midi_msg[msg_loc + 5:msg_loc + 7]
+ note_height = int(note)
+ note_decod_oct = note_height // 12
+ note_decode_tone = note_height % 12
+ note_ext = ""
+ if note_decode_tone == 1:
+ note_ext = "C"
+ elif note_decode_tone == 2:
+ note_ext = "C#"
+ elif note_decode_tone == 3:
+ note_ext = "D"
+ elif note_decode_tone == 4:
+ note_ext = "D#"
+ elif note_decode_tone == 5:
+ note_ext = "E"
+ elif note_decode_tone == 6:
+ note_ext = "F"
+ elif note_decode_tone == 7:
+ note_ext = "F#"
+ elif note_decode_tone == 8:
+ note_ext = "G"
+ elif note_decode_tone == 9:
+ note_ext = "G#"
+ elif note_decode_tone == 10:
+ note_ext = "A"
+ elif note_decode_tone == 11:
+ note_ext = "A#"
+ elif note_decode_tone == 12:
+ note_ext = "H"
+
+ ext_shortened = midi_msg[40:]
+ pos = 0
+ for buchstabe in ext_shortened:
+ if buchstabe == "=":
+ pos += 1
+ break
+ else:
+ pos += 1
+
+ timing_exp = ext_shortened[pos:]
+ output = note_ext
+ output += str(note_decod_oct)
+ output += f":{timing_exp}"
+ output_list.append(str(output))
+
+ except:
+ pass
+ elif midi_msg[0:8] == "note_off":
+ ext_shortened = midi_msg[40:]
+ pos = 0
+ for buchstabe in ext_shortened:
+ if buchstabe == "=":
+ pos += 1
+ break
+ else:
+ pos += 1
+ output = "R"
+ output += f":{pos}"
+
+ timing_exp = ext_shortened[pos:]
+ output_list.append(output)
+ else:
+ pass
+
+ self.addToClipboard(str(output_list))
+
diff --git a/midi-micro-bit_sound_converter-installer.sh b/midi-micro-bit_sound_converter-installer.sh
index 0754076..a2c59a9 100644
--- a/midi-micro-bit_sound_converter-installer.sh
+++ b/midi-micro-bit_sound_converter-installer.sh
@@ -6,14 +6,9 @@ mkdir ./midi-micro_bit-converter && cd ./midi-micro_bit-converter
git clone https://github.com/simplePCBuilding/midi-micro-bit_sound-converter
-pip install kivy[base]
-pip install kivymd
-pip install pyperclip
-pip install mido
+pip install kivy[base] kivymd pyperclip mido
-sudo apt-get install xclip
-sudo apt-get install xsel
-sudo apt-get install wl-clipboard
+sudo apt-get install xclip xsel
cd ./midi-micro-bit_sound-converter
python3 midi_converter.py
diff --git a/midi_converter.py b/midi_converter.py
index a21f348..7007bae 100644
--- a/midi_converter.py
+++ b/midi_converter.py
@@ -1,5 +1,8 @@
+from typing import Optional
from kivy.uix.screenmanager import ScreenManager
from kivymd.uix.screen import MDScreen
+from kivymd.uix.menu import MDDropdownMenu
+from kivymd.uix.screen import MDScreen
from kivy.uix.popup import Popup
from kivy.properties import ObjectProperty
from kivy.lang import Builder
@@ -7,10 +10,13 @@ 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
+import lib.midi_management
+global tracks
+tracks = []
+
+global filepath
+filepath = ''
class HomeScreen(MDScreen):
pass
@@ -19,25 +25,27 @@ class HomeScreen(MDScreen):
class FileChooserScreen(MDScreen):
loadfile = ObjectProperty(None)
def load(self, path, filename):
+ global filepath
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))
- if len(self.tracks) > 1:
- self.tracks.pop(0)
- screen_manager.get_screen("Track").ids.track_spinner.values = self.tracks
+ mid = MidiFile(self.path, clip=True)
+ filepath = self.path
+ global tracks
+ tracks = []
+ for i, track in enumerate(mid.tracks):
+ tracks.append('{} (Track {})'.format(track.name, i))
+ if len(tracks) > 1:
+ tracks.pop(0)
screen_manager.current = "Track"
screen_manager.transition.direction = "up"
else:
- backend.midi_management.MidiManagement().analyse_track(str(self.path), self.tracks.pop(0))
+ lib.midi_management.MidiManagement().analyse_track(str(self.path), tracks.pop(0))
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"
- except:
+ except Exception as e:
+ print(e)
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()
@@ -49,16 +57,40 @@ class FileChooserScreen(MDScreen):
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!"),
+ def show_dropdown(self, button):
+ global tracks
+ menu_items = []
+ for track in tracks:
+ menu_items.append(
+ {
+ "viewclass": "OneLineListItem",
+ "text": track,
+ "on_release": lambda t=track, menu=None : self.extract(t, menu)
+ }
+ )
+
+ menu = MDDropdownMenu(
+ caller=button,
+ items=menu_items,
+ width_mult=8,
+ )
+
+ # Pass the menu to each item in the lambda callback to dismiss the menu
+ for item in menu_items:
+ item["on_release"] = lambda track=item["text"], menu=menu: self.extract(track, menu)
+
+ menu.open()
+
+ def extract(self, item: str, menu: Optional[MDDropdownMenu] = None):
+ global filepath
+ if menu != None:
+ menu.dismiss()
+ if item == "Select a track":
+ 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()
+ 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)
+ lib.midi_management.MidiManagement().analyse_track(str(filepath), item)
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"