package backend import ( "encoding/json" "fmt" "os" "path/filepath" "sort" ) const eqPresetsDir = "eq_presets" // EQPreset represents an equalizer preset that can be saved/loaded type EQPreset struct { Name string `json:"name"` Type string `json:"type"` // "ISO10Band" or "ISO15Band" Preamp float64 `json:"preamp"` Bands []float64 `json:"bands"` IsBuiltin bool `json:"-"` // not saved to file, determined at load time } // EQPresetManager handles loading and saving EQ presets type EQPresetManager struct { presetsDir string builtinPresets []EQPreset } // NewEQPresetManager creates a new preset manager func NewEQPresetManager(configDir string) *EQPresetManager { return &EQPresetManager{ presetsDir: filepath.Join(configDir, eqPresetsDir), builtinPresets: getBuiltinPresets(), } } // getBuiltinPresets returns the built-in equalizer presets func getBuiltinPresets() []EQPreset { return []EQPreset{ {Name: "Flat", Type: "ISO15Band", Preamp: 0, Bands: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, IsBuiltin: true}, {Name: "Rock", Type: "ISO15Band", Preamp: 0, Bands: []float64{5, 4, 3, 1, -1, -1, 0, 2, 3, 4, 4, 4, 3, 2, 2}, IsBuiltin: true}, {Name: "Pop", Type: "ISO15Band", Preamp: 0, Bands: []float64{-1, -1, 0, 2, 4, 4, 2, 0, -1, -1, 0, 1, 2, 3, 3}, IsBuiltin: true}, {Name: "Jazz", Type: "ISO15Band", Preamp: 0, Bands: []float64{4, 3, 1, 2, -2, -2, 0, 2, 3, 3, 3, 4, 4, 4, 4}, IsBuiltin: true}, {Name: "Classical", Type: "ISO15Band", Preamp: 0, Bands: []float64{5, 4, 3, 2, -1, -1, 0, 2, 3, 3, 3, 2, 2, 2, -1}, IsBuiltin: true}, {Name: "Bass Boost", Type: "ISO15Band", Preamp: 0, Bands: []float64{6, 5, 4, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, IsBuiltin: true}, {Name: "Treble Boost", Type: "ISO15Band", Preamp: 0, Bands: []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 5, 6, 6}, IsBuiltin: true}, {Name: "Vocal", Type: "ISO15Band", Preamp: 0, Bands: []float64{-2, -3, -3, 1, 4, 4, 4, 3, 2, 1, 0, -1, -2, -2, -3}, IsBuiltin: true}, {Name: "Electronic", Type: "ISO15Band", Preamp: 0, Bands: []float64{5, 4, 2, 0, -2, -2, 0, 2, 3, 4, 4, 3, 4, 4, 3}, IsBuiltin: true}, {Name: "Acoustic", Type: "ISO15Band", Preamp: 0, Bands: []float64{5, 4, 3, 1, 2, 1, 1, 2, 2, 2, 1, 2, 2, 3, 2}, IsBuiltin: true}, {Name: "R&B", Type: "ISO15Band", Preamp: 0, Bands: []float64{3, 6, 5, 2, -2, -2, 2, 3, 2, 2, 3, 3, 3, 3, 4}, IsBuiltin: true}, {Name: "Loudness", Type: "ISO15Band", Preamp: 0, Bands: []float64{6, 5, 3, 0, -1, -1, -1, -1, 0, 1, 2, 4, 5, 5, 3}, IsBuiltin: true}, } } // LoadPresets loads all presets (builtin + user-defined) func (m *EQPresetManager) LoadPresets() ([]EQPreset, error) { // Start with builtin presets presets := make([]EQPreset, len(m.builtinPresets)) copy(presets, m.builtinPresets) // Create presets directory if it doesn't exist if err := os.MkdirAll(m.presetsDir, 0o755); err != nil { return presets, err } // Load user presets entries, err := os.ReadDir(m.presetsDir) if err != nil { return presets, nil // Return builtins if can't read dir } for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { continue } presetPath := filepath.Join(m.presetsDir, entry.Name()) data, err := os.ReadFile(presetPath) if err != nil { continue // Skip invalid files } var preset EQPreset if err := json.Unmarshal(data, &preset); err != nil { continue // Skip invalid JSON } preset.IsBuiltin = false presets = append(presets, preset) } // Sort: builtins first, then user presets alphabetically sort.Slice(presets, func(i, j int) bool { if presets[i].IsBuiltin != presets[j].IsBuiltin { return presets[i].IsBuiltin } return presets[i].Name < presets[j].Name }) return presets, nil } // SavePreset saves a preset to disk func (m *EQPresetManager) SavePreset(preset EQPreset) error { if preset.IsBuiltin { return fmt.Errorf("cannot overwrite builtin preset") } // Create presets directory if it doesn't exist if err := os.MkdirAll(m.presetsDir, 0o755); err != nil { return err } // Sanitize filename filename := sanitizeFilename(preset.Name) + ".json" presetPath := filepath.Join(m.presetsDir, filename) data, err := json.MarshalIndent(preset, "", " ") if err != nil { return err } return os.WriteFile(presetPath, data, 0o644) } // DeletePreset deletes a user preset func (m *EQPresetManager) DeletePreset(name string) error { // Check if it's a builtin preset for _, bp := range m.builtinPresets { if bp.Name == name { return fmt.Errorf("cannot delete builtin preset") } } filename := sanitizeFilename(name) + ".json" presetPath := filepath.Join(m.presetsDir, filename) return os.Remove(presetPath) } // sanitizeFilename removes characters that aren't safe for filenames func sanitizeFilename(name string) string { // Simple sanitization - could be enhanced var result []rune for _, r := range name { if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_' || r == ' ' { result = append(result, r) } } return string(result) }