# ARCHITECTURE ## 1. 기술 스택 (확정) | 영역 | 선택 | 이유 | |---|---|---| | 런타임 | **.NET 8** | 최신 LTS, 네이티브 Windows | | UI | **WPF + WPF-UI (lepoco)** | Fluent Design System / Mica·다크모드 / WinUI 감성. 텍스트 UI 아님 | | 차트 | **LiveCharts2** | 애니메이션되는 아름다운 EQ 커브. (대안: ScottPlot=정적·경량) | | MVVM | **CommunityToolkit.Mvvm** | 소스제너레이터 기반, 보일러플레이트 최소 | | DB | **SQLite (Microsoft.Data.Sqlite)** | 로컬 프로파일 인덱스 | | AI | **Anthropic REST (HttpClient)** 또는 `Anthropic.SDK` NuGet | 구조화 출력(tool use) | | 패키징 | **MSIX 또는 velopack/single-file** | 배포. 라이선스 제외 옵션과 연동 | > WPF-UI가 Mica 배경 + Fluent 컨트롤 + 다크테마를 제공해 목업(`mockups/main_window.html`)의 > 룩앤필을 네이티브로 재현할 수 있다. 그래프는 LiveCharts2의 `CartesianChart`로 로그 X축(주파수) > + 선형 Y축(dB), 영역 그라데이션 채우기. ## 2. 컴포넌트 구성 ``` DansoriEQ.sln ├─ DansoriEQ.App (WPF, WPF-UI) ── Views/ViewModels, 그래프, 프롬프트 독 ├─ DansoriEQ.Core (class lib) │ ├─ Profiles/ ProfileRepository, AutoEqParser, DbUpdater │ ├─ Eq/ EqState, Filter(model), ApoRenderer, PreampCalculator │ ├─ Ai/ ClaudeClient, EqPromptBuilder, EqDeltaSchema │ └─ Apo/ ApoLocator, ApoConfigInstaller(승격 1회), IncludeWriter └─ DansoriEQ.Setup (elevated helper, 최초 1회 UAC — 아래 4장) ``` ## 3. 데이터 흐름 ``` 프로파일 선택 → ProfileRepository.Load(id) → EqState(base) → ApoRenderer → IncludeWriter.Write(ai_eq.txt) → APO 라이브 리로드 → 그래프 갱신 프롬프트 입력 → EqPromptBuilder(현재 EqState + 헤드폰 메타 + 요청) → ClaudeClient(tool use, EqDeltaSchema) → EqDelta(JSON) → EqState.Apply(delta) → PreampCalculator → ApoRenderer → IncludeWriter → APO → AI 설명 텍스트를 응답 패널에 표시 / 히스토리 스택 push ``` ## 4. APO 연동 & 저권한 전략 (중요) 문제: APO의 `config.txt`는 `C:\Program Files\EqualizerAPO\config\` 아래라 쓰기에 승격 필요. 매번 승격은 "저권한" 원칙 위반. **채택 방식 — "1회 승격 설정, 이후 무승격":** 1. **최초 실행 설정 마법사**(DansoriEQ.Setup, UAC 1회): - APO 설치 경로 탐지: 레지스트리 `HKLM\SOFTWARE\EqualizerAPO` → `InstallPath`. - 사용자 쓰기 가능한 EQ 파일 준비. **권장:** `config` 폴더 내 `ai_eq.txt`를 만들고 **그 파일(또는 폴더)에 현재 사용자 쓰기 ACL 부여**(icacls). 그리고 `config.txt`에 `Include: ai_eq.txt` 한 줄 추가(중복 방지 검사). - 완료 플래그 저장(`%LOCALAPPDATA%\DansoriEQ\setup.json`). 2. **이후 실행**: 앱은 `ai_eq.txt`만 갱신 → **승격 불필요**. APO가 저장 즉시 리로드. > ⚠️ **M1에서 검증할 스파이크:** APO `Include:`가 절대경로를 허용하는지 확인. > - 허용 O → `ai_eq.txt`를 `%APPDATA%\DansoriEQ\`에 두고 절대경로 Include(ACL 불필요, 가장 깔끔). > - 허용 X(상대경로만) → 위 4-1의 config 폴더 내 파일 + ACL 부여 방식으로. > 두 경로 모두 "1회 승격, 이후 무승격"을 만족. 스파이크 결과로 하나 확정. APO 필터 문법(렌더 타깃): ``` Preamp: -6.0 dB Filter 1: ON PK Fc 3000 Hz Gain 3.0 dB Q 1.20 Filter 2: ON LSC Fc 90 Hz Gain 4.0 dB Q 0.70 Filter 3: ON HSC Fc 10000 Hz Gain -1.5 dB Q 0.70 ``` - 타입 매핑: PK=peaking, LSC=low-shelf, HSC=high-shelf, (LP/HP 확장 가능). - **Preamp = -(양의 게인 총합) 또는 최소 -(최대 양의 게인) − 여유 1dB.** 클리핑 방지 필수. ## 5. 프로파일 DB & 라이선스 분리 ### 폴더 레이아웃 ``` data/ ├─ profiles/ │ ├─ open/ ← 재배포 허용(퍼미시브) 소스. 배포 빌드에 포함 │ └─ restricted/ ← 재배포 불가 소스. 배포 빌드에서 제외 ├─ manifest.json ← 소스별 {name, license, distributable, url, commit} └─ dansorieq.db ← SQLite 인덱스(open+restricted 모두 인덱싱) ``` ### 원칙 - **수집(개발 중 1회):** AutoEQ repo를 zip/clone로 확보 → 파싱 → 소스 라이선스에 따라 `open/` 또는 `restricted/`로 분류 저장 → SQLite에 upsert. - **UI:** DB를 조회할 때 **라이선스 구분 없이 병합**해서 보여준다(IEM/헤드폰만 구분). - **배포:** 패키징 시 `--exclude-restricted` 옵션 → `restricted/` 폴더와 해당 DB 행 제외. (개인 사용 로컬 빌드는 전체 포함.) - DB 스키마: ```sql CREATE TABLE headphone( id INTEGER PRIMARY KEY, name TEXT NOT NULL, brand TEXT, type TEXT CHECK(type IN ('iem','headphone')) NOT NULL, source TEXT, -- 측정/소스명 license TEXT, -- 예: 'CC-BY', 'restricted' distributable INTEGER, -- 0/1 (UI에선 필터하지 않음) target TEXT, -- 예: 'Harman IE 2019' preamp_db REAL, filters_json TEXT, -- [{type,fc,gain,q}] raw_text TEXT, -- 원본 ParametricEQ.txt updated_at TEXT ); CREATE INDEX ix_hp_type ON headphone(type); CREATE INDEX ix_hp_name ON headphone(name); ``` ### 앱 내 업데이트 - "DB 업데이트" → 최신 소스(zip) 재확보 → 파싱 → **upsert(변경분만)** → manifest commit/날짜 갱신. - UI 하단에 `N profiles · 최신: YYYY-MM-DD` 표시. ## 6. AI 레이어 스키마 (구조화 출력) `EqDelta` (Claude가 tool use로 반환): ```json { "preamp_db": -6.0, "filters": [ { "type": "PK", "fc": 3000, "gain": 3.0, "q": 1.2, "enabled": true }, { "type": "LSC", "fc": 90, "gain": 4.0, "q": 0.7, "enabled": true } ], "explanation": "보컬 존재감을 위해 3kHz +3dB…", "replace": false // false=현재 상태에 병합, true=전체 교체 } ``` 시스템 프롬프트 요지: - 역할: 파라메트릭 EQ 전문가. APO 제약(타입 PK/LSC/HSC, Fc 20–20000Hz, Gain ±12dB 권장, Q 0.3–6) 안에서만 출력. - 입력으로 **현재 EqState + 헤드폰 모델/타입 + 사용자 요청** 제공 → 요청을 delta로 반영. - 오디오 상식 힌트(보컬≈2–5kHz, 저음 단단히≈40–80Hz 무게+150–250Hz 뭉침 컷, 치찰음≈6–8kHz). - **Preamp 안전값 계산 규칙 명시.** - 런타임 모델: 기본 **Claude Sonnet**(요청당 2–5k 토큰), 옵션으로 Opus. ## 7. 오픈 이슈 → DEV_PLAN 참조 - APO Include 절대경로 허용 여부(스파이크). - AutoEQ repo 구조/소스 라이선스 최신 상태 확인(수집 스크립트에서). - 다중 출력 장치별 APO config 타깃팅(고급 옵션, MVP 이후). ## 8. EQ 프리셋 공유 포맷 (.tweq) 사용자가 만든 EQ를 다른 사용자와 공유하기 위한 파일. **JSON**, 확장자 `.tweq`. ```json { "format": "dansorieq.eq", "version": 1, "app_version": "0.1.0", "created_utc": "2026-07-01T12:00:00Z", "author_note": "밝고 보컬 중심 튜닝", "target": { "name": "Truthear x Crinacle Zero", "brand": "Truthear", "type": "iem", "source": "AutoEQ", "target_curve": "Harman IE 2019" }, "eq": { "preamp_db": -6.0, "filters": [ { "type": "PK", "fc": 3000, "gain": 3.0, "q": 1.2, "enabled": true } ] }, "history": [ { "role": "user", "text": "보컬 가까이, 저음 단단히", "ts": "..." }, { "role": "ai", "text": "3kHz +3dB, 180Hz -2.5dB…", "ts": "..." } ] } ``` - **내보내기**: 현재 EqState + 대상 기기 메타 + 대화 히스토리(간략)를 직렬화해 저장. - **가져오기**: 검증(format/version, 값 범위 클램프, 크기 상한) → **정보 패널(대상 기기 / 히스토리 / 작성자 노트) 표시** → EQ 적용. - **라이선스 안전**: 파일엔 측정 데이터가 아니라 **기기 이름/메타만** 포함 → 재배포 문제 없음. - **보안**: history 텍스트는 **표시 전용**(실행/링크 처리 안 함), 항목 수·길이 상한, API 키 미포함. ## 9. 설정 & Claude API 키 관리 - **별도 설정 화면**(WPF-UI NavigationView: Home/EQ ↔ Settings). - 섹션: [AI · API 키] / [테마] / [APO] / [데이터베이스]. - **API 키**: 등록 / 삭제 / 교체, "연결 테스트", **DPAPI 암호화 저장**(`%LOCALAPPDATA%\DansoriEQ`). - **모델 선택**: 기본 Claude Sonnet / 옵션 Opus. - 키 미등록 시 프롬프트 독 비활성 + 등록 안내.