204 lines
7 KiB
Python
204 lines
7 KiB
Python
import os
|
|
import json
|
|
from functools import cached_property
|
|
from dataclasses import dataclass, fields
|
|
from constants import RESET, REVERSE, SOUNDFONTS
|
|
|
|
_INSTRUMENT_LIST = [f"[{str(idx).rjust(2)}] {name}" for idx, name in enumerate(SOUNDFONTS.keys())]
|
|
|
|
|
|
_SETTINGS_SAVE_FILE = 'assets/settings.json'
|
|
|
|
_MATRIX_SAVE_DIR = os.path.join(os.path.dirname(__file__), "assets/saves")
|
|
|
|
|
|
def get_saved_matrix_list():
|
|
if not os.path.exists(_MATRIX_SAVE_DIR):
|
|
return [" [No saved matrices found]"]
|
|
|
|
save_files = [
|
|
f for f in os.listdir(_MATRIX_SAVE_DIR)
|
|
if os.path.isfile(os.path.join(_MATRIX_SAVE_DIR, f)) and f.endswith(".sav")
|
|
]
|
|
save_files.sort()
|
|
|
|
if not save_files:
|
|
return [" [No saved matrices found]"]
|
|
|
|
return [f"[{str(idx).rjust(2)}] {name}" for idx, name in enumerate(save_files)]
|
|
|
|
|
|
_SAVED_MATRIX_LIST = get_saved_matrix_list()
|
|
|
|
|
|
@dataclass
|
|
class Settings:
|
|
BPM: int = 140
|
|
INSTRUMENT: str = "Bassoon"
|
|
OCTAVE: int = 3
|
|
OCTAVE_RANDOMIZATION: int = 0
|
|
RHYTHM_COMPLEXITY: int = 0
|
|
DEFAULT_DIVISION: int = 2
|
|
REST_PROBABILITY: int = 0
|
|
SAVE_SETTINGS: None = None
|
|
SAVE_MATRIX: None = None
|
|
LOAD_MATRIX: None = None
|
|
|
|
@classmethod
|
|
def max_len(cls):
|
|
return max(len(f.name) for f in fields(cls)) + 2
|
|
|
|
|
|
@dataclass
|
|
class SettingsHelp:
|
|
BPM: str = "Enter a BPM between 30 - 300"
|
|
INSTRUMENT: str = f"Select Soundfont:\n\n{'\n'.join(_INSTRUMENT_LIST)}"
|
|
OCTAVE: str = "Change the octave. Select an octave between 1 and 5"
|
|
OCTAVE_RANDOMIZATION: int = 0
|
|
RHYTHM_COMPLEXITY: str = "\n[0] Off, All Eigth Notes\n[1] On, Engage Rhythm Matrix\n[2] On, Rhythm Matrix and Rest Randomization\n[3] On, Chaos Mode, A random duration is chosen between 0.05 seconds and 1 second"
|
|
DEFAULT_DIVISION: str = "\n[0] Whole Note\n[1] Half Note\n[2] Quarter Note\n[3] Eight Note"
|
|
REST_PROBABILITY: str = "Probability in which a note in row will be replaced by a rest.\nEnter a value between 0 and 100, where 0 is absolutely no chance of a rest and 100 is all rests.\nThis only works in RHYTHM_COMPLEXITY MODE 2."
|
|
SAVE_SETTINGS: str = "Save current settings to file"
|
|
SAVE_MATRIX: str = "Save current matrix to file, Enter name of matrix to save"
|
|
LOAD_MATRIX: str = f"Select Saved Matrix to Load:\n\n{'\n'.join(_SAVED_MATRIX_LIST)}"
|
|
EXIT: None = None
|
|
|
|
|
|
SETTINGS_RULES = {
|
|
"BPM": {"type": int, "min": 30, "max": 300},
|
|
"OCTAVE": {"type": int, "min": 1, "max": 5},
|
|
"OCTAVE_RANDOMIZATION": {"type": int, "min": 0, "max": 4},
|
|
"RHYTHM_COMPLEXITY": {"type": int, "min": 0, "max": 3},
|
|
"DEFAULT_DIVISION": {"type": int, "min": 0, "max": 3},
|
|
"REST_PROBABILITY": {"type": int, "min": 0, "max": 100},
|
|
"INSTRUMENT": {"type": int, "min": 0, "max": len(SOUNDFONTS)},
|
|
"SAVE_MATRIX": {"type": str, "min": 0, "max": 10},
|
|
"LOAD_MATRIX": {"type": str, "min": 0, "max": 100},
|
|
|
|
}
|
|
|
|
|
|
class SettingsMixin:
|
|
|
|
@cached_property
|
|
def settings(self):
|
|
return [f.name for f in fields(Settings)]
|
|
|
|
def save_settings(self):
|
|
setting_obj = {}
|
|
|
|
for field in fields(Settings):
|
|
setting_obj[field.name] = getattr(Settings, field.name)
|
|
|
|
with open(_SETTINGS_SAVE_FILE, "w+") as f:
|
|
json.dump(setting_obj, f)
|
|
|
|
def load_settings(self):
|
|
with open(_SETTINGS_SAVE_FILE, "r") as f:
|
|
data = json.load(f)
|
|
for k, v in data.items():
|
|
setattr(Settings, k, int(v) if str(v).isdigit() else v)
|
|
|
|
self.update_sf(SOUNDFONTS[Settings.INSTRUMENT])
|
|
self.bpm = int(Settings.BPM)
|
|
|
|
def display_settings(self):
|
|
self.clear_screen()
|
|
print(f"{REVERSE} SETTINGS: {RESET}\n")
|
|
for idx, setting in enumerate(self.settings):
|
|
value = getattr(Settings, setting)
|
|
print(f"[{idx}] {setting.ljust(Settings.max_len())}{": " + str(value) if value != None else ''}")
|
|
|
|
self.user_input()
|
|
|
|
self.display_settings()
|
|
|
|
def user_input(self, setting_name=None):
|
|
request = self.request('Enter ID of setting you want to change, "q" to exit')
|
|
|
|
if request[0] == "q":
|
|
self.loop()
|
|
|
|
request = self.validate_request(request)
|
|
|
|
setting_name = self.settings[request]
|
|
|
|
if setting_name == "SAVE_SETTINGS":
|
|
self.save_settings()
|
|
self.message("Settings Saved..", self.display_settings, type="SUCCESS")
|
|
|
|
self.display_setting(setting_name)
|
|
|
|
value = self.request("Update setting by entering value")
|
|
|
|
value = self.validate_new_setting(setting_name, value)
|
|
|
|
setattr(Settings, setting_name, value)
|
|
|
|
if setting_name == "LOAD_MATRIX":
|
|
self.handle_load_matrix()
|
|
|
|
if setting_name == "SAVE_MATRIX":
|
|
self.handle_save_matrix()
|
|
|
|
if setting_name == "BPM":
|
|
self.bpm = int(Settings.BPM)
|
|
|
|
if setting_name == "INSTRUMENT":
|
|
instrument = list(SOUNDFONTS.keys())[value]
|
|
setattr(Settings, setting_name, instrument)
|
|
self.update_sf(SOUNDFONTS[instrument])
|
|
|
|
if setting_name == "OCTAVE_RANDOMIZATION":
|
|
del self.octave_row
|
|
del self.octave_map
|
|
_ = self.octave_row
|
|
_ = self.octave_map
|
|
|
|
if setting_name == "OCTAVE":
|
|
del self.octave_row
|
|
del self.octave_map
|
|
_ = self.octave_row
|
|
_ = self.octave_map
|
|
|
|
def validate_request(self, request):
|
|
try:
|
|
request = int(request)
|
|
if request >= len(self.settings):
|
|
raise ValueError
|
|
return request
|
|
except ValueError:
|
|
self.message(f"'{request}' is not a valid input, try again!", self.display_settings)
|
|
|
|
def display_setting(self, setting_name):
|
|
self.clear_screen()
|
|
print(f"{REVERSE}{setting_name.ljust(Settings.max_len())} CURRENT VALUE: {getattr(Settings, setting_name)} {RESET}\n")
|
|
print(f"INFO: {getattr(SettingsHelp, setting_name)}\n")
|
|
|
|
def validate_new_setting(self, setting_name, value):
|
|
try:
|
|
if SETTINGS_RULES[setting_name]['type'] == int:
|
|
value = int(value)
|
|
if value >= SETTINGS_RULES[setting_name]['min'] and value <= SETTINGS_RULES[setting_name]['max']:
|
|
pass
|
|
else:
|
|
raise ValueError
|
|
return value
|
|
else:
|
|
return value
|
|
except ValueError:
|
|
self.message(f"'{value}' is not a valid input, try again!", self.display_settings)
|
|
|
|
def handle_load_matrix(self):
|
|
file_name = " ".join(_SAVED_MATRIX_LIST[int(Settings.LOAD_MATRIX)].split("] ")[1:])
|
|
with open(f"assets/saves/{file_name}", 'r') as f:
|
|
seed = f.readline()
|
|
|
|
self.current_seed = int(seed)
|
|
self.regenerate()
|
|
self.use_current_seed = True
|
|
|
|
def handle_save_matrix(self):
|
|
Settings.SAVE_MATRIX = Settings.SAVE_MATRIX.strip().replace(".", " ").replace(" ", "_")
|
|
with open(f"assets/saves/{Settings.SAVE_MATRIX}.sav", 'w+') as f:
|
|
f.write(str(self.current_seed))
|