You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
11 KiB
335 lines
11 KiB
"""
|
|
Configuration Module
|
|
|
|
Manages application configuration, settings persistence, and defaults.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from typing import Dict, Any, Optional
|
|
|
|
class Configuration:
|
|
"""
|
|
Application configuration manager.
|
|
Handles loading, saving, and managing application settings.
|
|
"""
|
|
|
|
DEFAULT_CONFIG = {
|
|
"version": "1.0",
|
|
"window": {
|
|
"width": 1200,
|
|
"height": 800,
|
|
"maximize": False,
|
|
"remember_position": True,
|
|
"x": 100,
|
|
"y": 100
|
|
},
|
|
"audio": {
|
|
"sample_rate": 22050,
|
|
"buffer_size": 512,
|
|
"master_volume": 0.8,
|
|
"enabled": True
|
|
},
|
|
"midi": {
|
|
"default_output": "",
|
|
"default_mode": "simulator",
|
|
"auto_connect": True
|
|
},
|
|
"arpeggiator": {
|
|
"default_root_note": 60, # Middle C
|
|
"default_scale": "major",
|
|
"default_pattern": "up",
|
|
"default_tempo": 120.0,
|
|
"default_octave_range": 1,
|
|
"default_note_speed": "1/8",
|
|
"default_gate": 1.0,
|
|
"default_swing": 0.0,
|
|
"default_velocity": 80
|
|
},
|
|
"channels": {
|
|
"default_synth_count": 8,
|
|
"default_instrument": 0, # Piano
|
|
"max_voices_per_synth": 3
|
|
},
|
|
"volume_patterns": {
|
|
"default_pattern": "static",
|
|
"default_speed": 1.0,
|
|
"default_intensity": 1.0,
|
|
"default_min_volume": 0.2,
|
|
"default_max_volume": 1.0,
|
|
"default_min_velocity": 40,
|
|
"default_max_velocity": 127
|
|
},
|
|
"simulator": {
|
|
"default_brightness": 1.0,
|
|
"lighting_enabled": True,
|
|
"animation_speed": 16 # ms between updates
|
|
},
|
|
"maschine": {
|
|
"enabled": False,
|
|
"device_name": "",
|
|
"auto_connect": True,
|
|
"pad_sensitivity": 1.0,
|
|
"encoder_sensitivity": 1.0
|
|
},
|
|
"presets": {
|
|
"auto_save_current": True,
|
|
"remember_last_preset": True,
|
|
"last_preset": ""
|
|
},
|
|
"interface": {
|
|
"theme": "dark",
|
|
"font_size": 10,
|
|
"show_tooltips": True,
|
|
"confirm_destructive_actions": True
|
|
}
|
|
}
|
|
|
|
def __init__(self, config_file: str = "config.json"):
|
|
self.config_file = config_file
|
|
self.config = self.DEFAULT_CONFIG.copy()
|
|
self.load_config()
|
|
|
|
def load_config(self) -> bool:
|
|
"""Load configuration from file"""
|
|
try:
|
|
if os.path.exists(self.config_file):
|
|
with open(self.config_file, 'r') as f:
|
|
loaded_config = json.load(f)
|
|
|
|
# Merge with defaults (preserves new default settings)
|
|
self._merge_config(self.config, loaded_config)
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error loading configuration: {e}")
|
|
print("Using default configuration")
|
|
|
|
return False
|
|
|
|
def save_config(self) -> bool:
|
|
"""Save current configuration to file"""
|
|
try:
|
|
with open(self.config_file, 'w') as f:
|
|
json.dump(self.config, f, indent=2)
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error saving configuration: {e}")
|
|
return False
|
|
|
|
def _merge_config(self, base: dict, update: dict):
|
|
"""Recursively merge configuration dictionaries"""
|
|
for key, value in update.items():
|
|
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
|
self._merge_config(base[key], value)
|
|
else:
|
|
base[key] = value
|
|
|
|
def get(self, path: str, default: Any = None) -> Any:
|
|
"""
|
|
Get configuration value using dot notation.
|
|
Example: get("window.width") returns config["window"]["width"]
|
|
"""
|
|
keys = path.split('.')
|
|
value = self.config
|
|
|
|
try:
|
|
for key in keys:
|
|
value = value[key]
|
|
return value
|
|
except (KeyError, TypeError):
|
|
return default
|
|
|
|
def set(self, path: str, value: Any) -> bool:
|
|
"""
|
|
Set configuration value using dot notation.
|
|
Example: set("window.width", 1024)
|
|
"""
|
|
keys = path.split('.')
|
|
config_ref = self.config
|
|
|
|
try:
|
|
# Navigate to parent dictionary
|
|
for key in keys[:-1]:
|
|
if key not in config_ref:
|
|
config_ref[key] = {}
|
|
config_ref = config_ref[key]
|
|
|
|
# Set the value
|
|
config_ref[keys[-1]] = value
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error setting config value {path}: {e}")
|
|
return False
|
|
|
|
def get_section(self, section: str) -> Dict[str, Any]:
|
|
"""Get entire configuration section"""
|
|
return self.config.get(section, {})
|
|
|
|
def set_section(self, section: str, values: Dict[str, Any]):
|
|
"""Set entire configuration section"""
|
|
if section not in self.config:
|
|
self.config[section] = {}
|
|
self.config[section].update(values)
|
|
|
|
def reset_to_defaults(self, section: Optional[str] = None):
|
|
"""Reset configuration to defaults"""
|
|
if section:
|
|
if section in self.DEFAULT_CONFIG:
|
|
self.config[section] = self.DEFAULT_CONFIG[section].copy()
|
|
else:
|
|
self.config = self.DEFAULT_CONFIG.copy()
|
|
|
|
def get_window_settings(self) -> Dict[str, Any]:
|
|
"""Get window-related settings"""
|
|
return self.get_section("window")
|
|
|
|
def save_window_settings(self, width: int, height: int, x: int, y: int, maximized: bool = False):
|
|
"""Save window position and size"""
|
|
window_settings = {
|
|
"width": width,
|
|
"height": height,
|
|
"x": x,
|
|
"y": y,
|
|
"maximize": maximized
|
|
}
|
|
self.set_section("window", window_settings)
|
|
self.save_config()
|
|
|
|
def get_audio_settings(self) -> Dict[str, Any]:
|
|
"""Get audio-related settings"""
|
|
return self.get_section("audio")
|
|
|
|
def get_midi_settings(self) -> Dict[str, Any]:
|
|
"""Get MIDI-related settings"""
|
|
return self.get_section("midi")
|
|
|
|
def get_arpeggiator_defaults(self) -> Dict[str, Any]:
|
|
"""Get arpeggiator default settings"""
|
|
return self.get_section("arpeggiator")
|
|
|
|
def get_channel_defaults(self) -> Dict[str, Any]:
|
|
"""Get channel default settings"""
|
|
return self.get_section("channels")
|
|
|
|
def get_volume_pattern_defaults(self) -> Dict[str, Any]:
|
|
"""Get volume pattern default settings"""
|
|
return self.get_section("volume_patterns")
|
|
|
|
def get_simulator_settings(self) -> Dict[str, Any]:
|
|
"""Get simulator settings"""
|
|
return self.get_section("simulator")
|
|
|
|
def get_maschine_settings(self) -> Dict[str, Any]:
|
|
"""Get Maschine integration settings"""
|
|
return self.get_section("maschine")
|
|
|
|
def get_preset_settings(self) -> Dict[str, Any]:
|
|
"""Get preset management settings"""
|
|
return self.get_section("presets")
|
|
|
|
def get_interface_settings(self) -> Dict[str, Any]:
|
|
"""Get interface settings"""
|
|
return self.get_section("interface")
|
|
|
|
def should_auto_save_preset(self) -> bool:
|
|
"""Check if presets should be auto-saved"""
|
|
return self.get("presets.auto_save_current", True)
|
|
|
|
def should_remember_last_preset(self) -> bool:
|
|
"""Check if last preset should be remembered"""
|
|
return self.get("presets.remember_last_preset", True)
|
|
|
|
def get_last_preset(self) -> str:
|
|
"""Get name of last used preset"""
|
|
return self.get("presets.last_preset", "")
|
|
|
|
def set_last_preset(self, preset_name: str):
|
|
"""Set name of last used preset"""
|
|
self.set("presets.last_preset", preset_name)
|
|
self.save_config()
|
|
|
|
def should_confirm_destructive_actions(self) -> bool:
|
|
"""Check if destructive actions should be confirmed"""
|
|
return self.get("interface.confirm_destructive_actions", True)
|
|
|
|
def get_theme(self) -> str:
|
|
"""Get current theme"""
|
|
return self.get("interface.theme", "dark")
|
|
|
|
def export_config(self, file_path: str) -> bool:
|
|
"""Export configuration to a different file"""
|
|
try:
|
|
with open(file_path, 'w') as f:
|
|
json.dump(self.config, f, indent=2)
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error exporting configuration: {e}")
|
|
return False
|
|
|
|
def import_config(self, file_path: str) -> bool:
|
|
"""Import configuration from a file"""
|
|
try:
|
|
if os.path.exists(file_path):
|
|
with open(file_path, 'r') as f:
|
|
imported_config = json.load(f)
|
|
|
|
# Validate and merge
|
|
self._merge_config(self.config, imported_config)
|
|
self.save_config()
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error importing configuration: {e}")
|
|
|
|
return False
|
|
|
|
def validate_config(self) -> bool:
|
|
"""Validate configuration values"""
|
|
valid = True
|
|
|
|
# Validate window settings
|
|
window = self.get_section("window")
|
|
if window.get("width", 0) < 800:
|
|
self.set("window.width", 1200)
|
|
valid = False
|
|
|
|
if window.get("height", 0) < 600:
|
|
self.set("window.height", 800)
|
|
valid = False
|
|
|
|
# Validate audio settings
|
|
audio = self.get_section("audio")
|
|
if audio.get("sample_rate", 0) not in [22050, 44100, 48000]:
|
|
self.set("audio.sample_rate", 22050)
|
|
valid = False
|
|
|
|
if not (0.0 <= audio.get("master_volume", 1.0) <= 1.0):
|
|
self.set("audio.master_volume", 0.8)
|
|
valid = False
|
|
|
|
# Validate arpeggiator settings
|
|
arp = self.get_section("arpeggiator")
|
|
if not (0 <= arp.get("default_root_note", 60) <= 127):
|
|
self.set("arpeggiator.default_root_note", 60)
|
|
valid = False
|
|
|
|
if not (40 <= arp.get("default_tempo", 120) <= 200):
|
|
self.set("arpeggiator.default_tempo", 120.0)
|
|
valid = False
|
|
|
|
# Validate channel settings
|
|
channels = self.get_section("channels")
|
|
if not (1 <= channels.get("default_synth_count", 8) <= 16):
|
|
self.set("channels.default_synth_count", 8)
|
|
valid = False
|
|
|
|
return valid
|
|
|
|
def get_config_info(self) -> Dict[str, Any]:
|
|
"""Get configuration metadata"""
|
|
return {
|
|
"version": self.config.get("version", "Unknown"),
|
|
"file_path": self.config_file,
|
|
"file_exists": os.path.exists(self.config_file),
|
|
"file_size": os.path.getsize(self.config_file) if os.path.exists(self.config_file) else 0,
|
|
"sections": list(self.config.keys())
|
|
}
|