File size: 4,206 Bytes
c1e08a0
 
 
 
 
 
 
 
 
 
 
 
29e2c21
c1e08a0
 
 
 
 
 
 
 
 
 
 
 
 
29e2c21
 
c1e08a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5e84ffc
 
 
 
 
b9df2e0
5e84ffc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c1e08a0
 
 
 
 
5e84ffc
 
c1e08a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5e84ffc
 
 
 
 
c1e08a0
 
 
5e84ffc
 
 
c1e08a0
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""Configuration module for audio settings and chord progressions."""

from dataclasses import dataclass, field
from pathlib import Path

import yaml


@dataclass
class PitchDetectorConfig:
    """Configuration settings for pitch detection."""

    sample_rate: int = 16000
    hop_length: int = 512
    decoder_mode: str = "local_argmax"
    threshold: float = 0.006
    f0_min: int = 80
    f0_max: int = 880
    interp_uv: bool = False
    device: str = "cpu"


@dataclass
class AudioConfig:
    """Configuration class for audio-related settings."""

    sample_rate: int = 16000
    buffer_duration: float = 0.3
    note_duration: float = 1.0
    pitch_detector: PitchDetectorConfig = field(default_factory=PitchDetectorConfig)

    @classmethod
    def from_yaml(cls, yaml_data: dict) -> "AudioConfig":
        """Create AudioConfig instance from YAML data."""
        config = cls(
            sample_rate=yaml_data.get("sample_rate", cls.sample_rate),
            buffer_duration=yaml_data.get("buffer_duration", cls.buffer_duration),
            note_duration=yaml_data.get("note_duration", cls.note_duration),
        )

        if "pitch_detector" in yaml_data:
            pitch_detector_data = yaml_data["pitch_detector"]
            # The sample rate must be set explicitly
            # Use the sample rate specified in the audio config
            pitch_detector_data["sample_rate"] = config.sample_rate
            config.pitch_detector = PitchDetectorConfig(**pitch_detector_data)

        return config


@dataclass
class IntervalPracticeConfig:
    """Configuration settings for interval practice."""

    num_problems: int = 10
    interval: int = 0

    @classmethod
    def from_yaml(cls, yaml_data: dict) -> "IntervalPracticeConfig":
        """Create IntervalPracticeConfig instance from YAML data."""
        return cls(
            num_problems=yaml_data.get("num_problems", cls.num_problems),
            interval=yaml_data.get("interval", cls.interval),
        )


@dataclass
class PiecePracticeConfig:
    """Configuration settings for piece practice."""

    selected_song: str = "fly_me_to_the_moon"
    chord_progressions: dict = field(default_factory=dict)

    @classmethod
    def from_yaml(cls, yaml_data: dict) -> "PiecePracticeConfig":
        """Create PiecePracticeConfig instance from YAML data."""
        return cls(
            selected_song=yaml_data.get("selected_song", cls.selected_song),
            chord_progressions=yaml_data.get(
                "chord_progressions", {cls.selected_song: []}
            ),
        )


@dataclass
class Config:
    """Application configuration handler."""

    audio: AudioConfig
    interval_practice: IntervalPracticeConfig
    piece_practice: PiecePracticeConfig

    def __init__(self, config_path: str | Path = "config.yml"):
        """Initialize Config instance.

        Args:
            config_path: Path to YAML configuration file (default: 'config.yml').
        """
        self.config_path = Path(config_path)
        self._load_config()

    def _load_config(self):
        if self.config_path.exists():
            with open(self.config_path, "r") as f:
                yaml_data = yaml.safe_load(f)
                self.audio = AudioConfig.from_yaml(yaml_data.get("audio", {}))
                self.interval_practice = IntervalPracticeConfig.from_yaml(
                    yaml_data.get("interval_practice", {})
                )
                self.piece_practice = PiecePracticeConfig.from_yaml(
                    yaml_data.get("piece_practice", {})
                )
        else:
            self.audio = AudioConfig()
            self.interval_practice = IntervalPracticeConfig()
            self.piece_practice = PiecePracticeConfig()
            self.piece_practice.chord_progressions = {
                # opening 4 bars of Fly Me to the Moon
                "fly_me_to_the_moon": [
                    ("A", "natural_minor", "A", "min7", 8),
                    ("A", "natural_minor", "D", "min7", 8),
                    ("C", "major", "G", "dom7", 8),
                    ("C", "major", "C", "maj7", 4),
                    ("F", "major", "C", "dom7", 4),
                ]
            }