# AI 꾸러미 — 음계 양자화 (값 → 음) — Pitch quantization (scale snapping)
> 0 ~ 100 같은 어떤 값이든 세 옥타브의 5음계 음 15개 중 하나로 맞춰 울려, 큰 수는 높은 음이 되고 아무렇게나 쳐도 음이 어긋나지 않는다.  
> 견본: https://ai-techstudio.web.app/#t/i143

이 문서를 코드 도우미(Claude Code · Cursor · ChatGPT 등)에 그대로 주면 돼요. 「## 주문서」 가 할 일, 나머지는 참고 자료예요.

## 주문서

### 만들어 줘: 음계 양자화 (값 → 음) — Pitch quantization (scale snapping)

#### 1. 목표
블록 · 숫자를 누를 때마다 나는 음 (수의 크기 듣기)을(를) 소리로 들려줘 — 값을 5음계 음으로 맞춰서 큰 값은 높은 음, 어떤 순서로 쳐도 어울리게. 느낌은 실로폰처럼 맑은.

#### 2. 핵심 기술 용어
- **Pitch quantization (scale snapping)** — 음계 양자화 — 값을 가장 가까운 음계 음으로
- **Major pentatonic scale (0 2 4 7 9)** — 5음계 — 도 레 미 솔 라, 어떻게 섞어도 안 부딪힘
- **Sonification** — 값 · 자료를 소리로 들려주기

#### 3. 환경
- 플랫폼: Web Audio API (TypeScript) — 소리 파일 없이 OscillatorNode · GainNode · BiquadFilterNode 로 합성
- 화면: 브라우저 — PC · 폰 모두 (폰은 첫 터치 뒤에만 소리가 난다)

#### 4. 조건
- 값 범위를 먼저 0 ~ 1 로 자르고(clamp) 칸으로 반올림
- 음계 표는 배열 하나 (PENTA = [0, 2, 4, 7, 9]) — 음악 조가 바뀌면 기준음만 바꾼다
- 짧은 소리(0.3초 · 어택 4ms)로 연달아 쳐도 뭉개지지 않게
- 켬/끔 비교 단추로 양자화 효과를 들려준다

#### 5. 완성 기준 (이게 보이면 성공)
- 값 8개를 차례로 치면 막대가 높을수록 음이 높고, 가락처럼 어울린다
- 「5음계로 맞추기」를 끄면 같은 값들이 어긋난 음으로 들린다
- 0 ~ 100 손잡이를 끌면 계단처럼 15개 음 중 하나로 딱딱 맞춰진다

#### 6. 진행 방식
- 핵심 코드 위주로, 설명은 짧게. 내 프로젝트에 끼워 넣기 쉬운 함수 · 클래스로 나눠 줘.
- 처음 화면에 바로 결과가 보이게, 그리고 켬/끔(또는 전/후) 비교를 할 수 있게 만들어 줘.
- 그림 · 소리 · 모델 파일이 필요하면 코드로 만든 임시 대체물로 먼저 돌아가게 하고, 진짜 파일로 바꿀 자리를 표시해 줘.
- 마지막에 「확인 방법」(무엇을 보면 성공인지)과 「조절할 값」 목록을 짧게 정리해 줘.
- 답변과 코드 주석은 한국어로 해 줘.

## 원리
- 값을 0 ~ 1 로 줄인 뒤 15칸(5음계 × 세 옥타브) 중 가장 가까운 칸으로 반올림한다: i = round(v/100 × 14).
- 칸 번호 → 반음 = 12 × floor(i / 5) + [0, 2, 4, 7, 9][i % 5]. 진동수 = 261.63Hz(가운데 도) × 2^(반음/12).
- 5음계에는 반음 차이 나는 음(미-파, 시-도)이 없어서 어떤 음끼리 겹쳐도 거칠게 부딪히지 않는다.
- 맞추지 않고 값을 그대로 진동수로 바꾸면(261.63 × 2^(v/100 × 3)) 음계 사이 음이 나와 「틀린 음」처럼 들린다 — 견본의 끄기로 비교.

## 핵심 코드 — 값 → 5음계 음
(발췌: demos/demosSfx.ts pentaNote · recPenta 를 정리)
```ts
const PENTA = [0, 2, 4, 7, 9];                       // 도 레 미 솔 라
const st2 = (n: number) => Math.pow(2, n / 12);

/** 0~100 값 → 가운데 도에서 몇 반음 위인가 (세 옥타브 15칸) */
function pentaNote(v: number): number {
  const steps = 15;
  const i = Math.round(Math.max(0, Math.min(1, v / 100)) * (steps - 1));
  return 12 * Math.floor(i / 5) + PENTA[i % 5];
}
function playValue(c: AudioContext, out: AudioNode, t0: number, v: number, quant = true) {
  const f = quant ? 261.63 * st2(pentaNote(v))         // 음계 음으로 맞춤
                  : 261.63 * Math.pow(2, (v / 100) * 3); // 맞추지 않음 — 비교용
  const o = c.createOscillator(), g = c.createGain();
  o.type = 'triangle';
  o.frequency.setValueAtTime(f, t0);
  g.gain.setValueAtTime(0.0001, t0);
  g.gain.exponentialRampToValueAtTime(0.22, t0 + 0.004);
  g.gain.exponentialRampToValueAtTime(0.0001, t0 + 0.004 + 0.3);
  o.connect(g).connect(out);
  o.start(t0);
  o.stop(t0 + 0.36);
}
// 값 여러 개를 0.2초 간격으로: [12, 47, 33, 78, 91, 60, 25, 100].forEach((v, i) => playValue(c, out, t0 + i * 0.2, v));
```

## 흔한 실수 · 확인 목록
- [ ] **값을 진동수에 곧게 비례시키면(100Hz + v) 높은 쪽 차이가 안 들린다** — 귀는 비율로 듣는다 — 반음(2^(n/12)) 단위로 바꾼다.
- [ ] **칠음계(도레미파솔라시) 전부를 쓰면 마구 칠 때 미-파 · 시-도가 부딪힌다** — 5음계는 반음 간격이 없어 어떤 조합도 거칠지 않다.
- [ ] **값 범위 밖(−5, 130)이 들어오면 엉뚱하게 높거나 낮은 음이 난다** — 반올림 전에 0 ~ 1 로 자른다.

## 완성 기준 체크리스트
- [ ] 값 8개를 차례로 치면 막대가 높을수록 음이 높고, 가락처럼 어울린다
- [ ] 「5음계로 맞추기」를 끄면 같은 값들이 어긋난 음으로 들린다
- [ ] 0 ~ 100 손잡이를 끌면 계단처럼 15개 음 중 하나로 딱딱 맞춰진다

## 이 기술 정보
- id: `i143` · 분류: 소리 › 효과음 (SFX) · 공통 · 난이도 쉬움 · 폰 부담 가벼움 (폰 OK) — 음 하나 = 발진기 1 + 게인 1.
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/i143
- 쓰면 좋을 때: 아이가 아무렇게나 눌러도 음악처럼 들려야 하는 장난감 · 블록 소리 / 수의 크기 · 그래프 모양을 귀로 느끼게 할 때
- 쓰지 말 때: 정확한 값 차이를 들려줘야 할 때 (51 과 52 의 차이) — 양자화하면 같은 음이 된다 / 배경음악이 단조 · 다른 조일 때 — 그 조의 음계 표로 바꿔야 한다

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### pentaNote — `src/demos/demosSfx.ts:1218`
```ts
function pentaNote(v: number): number {
  const steps = 15; // 세 옥타브
  const i = Math.round((clamp(v / 100) * (steps - 1)));
  return 12 * Math.floor(i / 5) + PENTA[i % 5]!;
}
```

### recPenta — `src/demos/demosSfx.ts:1223`
```ts
const recPenta: Rec = (c, out, t0, p) => {
  const q = p['quant'] ?? 1;
  SAMPLE_VALUES.forEach((v, i) => {
    const f = q ? 261.63 * st2(pentaNote(v)) : 261.63 * Math.pow(2, (v / 100) * 3);
    tone(c, out, t0 + i * 0.2, f, 0.3, 0.22, 'triangle', 0.004);
  });
};
```

## 관련 기술
- 먼저 알면 좋은 기술: [코드로 효과음 합성 (견본판 70가지)](https://ai-techstudio.web.app/ai/t/i499.md) `i499`
- 다음에 해 볼 기술: [정답 아르페지오 (조성 맞춤)](https://ai-techstudio.web.app/ai/t/i141.md) `i141`
- 참고 문서: [Wikipedia — Pentatonic scale](https://en.wikipedia.org/wiki/Pentatonic_scale)
