# AI 꾸러미 — 연속 정답 음 높이 오름 — n/12
> 콤보마다 반음씩 위로 — 재생 속도 2^(n/12)  
> 견본: https://ai-techstudio.web.app/#t/i142

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

## 주문서

### 만들어 줘: 연속 정답 음 높이 오름 — n/12

#### 1. 목표
연속 정답에 「연속 정답 음 높이 오름」 기술을 깔끔한 기본 느낌으로 넣어 줘. — 콤보마다 반음씩 위로 — 재생 속도 2^(n/12)

#### 2. 핵심 기술 용어
- **n/12**

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

#### 4. 조건
- 사용자가 처음 누를 때 AudioContext 를 만들거나 resume() — 그 전엔 소리가 안 난다
- 소리 파일 없이 코드로 합성
- 마지막에 GainNode 로 크기를 제한해 찢어지는 소리(클리핑)가 없게

#### 5. 완성 기준 (이게 보이면 성공)
- 견본처럼 「콤보마다 반음씩 위로 — 재생 속도 2^(n/12)」 이(가) 첫 화면에서 바로 보인다
- 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

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

## 원리
- 콤보마다 반음씩 위로 — 재생 속도 2^(n/12)


## 흔한 실수 · 확인 목록
- (아직 정리된 것이 없어요)

## 완성 기준 체크리스트
- [ ] 견본처럼 「콤보마다 반음씩 위로 — 재생 속도 2^(n/12)」 이(가) 첫 화면에서 바로 보인다
- [ ] 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- [ ] 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- [ ] 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

## 이 기술 정보
- id: `i142` · 분류: 소리 › 효과음 (SFX) · 공통 · 난이도 쉬움 · 폰 부담 보통 (폰 주의)
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/i142
- 쓰면 좋을 때: 쓰면 좋을 곳 — 연속 정답 · 콤보

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### comboDing — `src/demos/demosSfx.ts:1207`
```ts
const comboDing = (c: BA, out: AudioNode, t0: number, n: number): void => {
  const f = 784 * st2(n);
  tone(c, out, t0, f, 0.28, 0.25, 'triangle', 0.002);
  tone(c, out, t0, f * 2, 0.12, 0.08, 'sine', 0.002);
  tone(c, out, t0 + 0.05, f * 1.5, 0.22, 0.1, 'sine', 0.002);
};
```

### i142 견본 항목 — `src/demos/demosSfx.ts:2142`
```ts
  i142: {
    caption: '연속 정답마다 반음씩 위로 — 재생 속도 2^(n/12), 계단처럼 오르는 「딩」',
    title: '콤보!',
    color: '#ffb84d',
    dur: 8 * 0.22 + 0.35,
    rec: recComboRun,
    playLabel: '▶ 8콤보 들어 보기',
    extra: (api) => [
      {
        type: 'button',
        label: '정답! (콤보 +1)',
        on: () => {
          const n = Math.min(12, (api.st['n'] ?? -1) + 1);
          api.st['n'] = n;
          api.st['at'] = performance.now();
          api.play((c, o, t0) => comboDing(c, o, t0, n), 0.35);
        },
      },
      {
        type: 'button',
        label: '콤보 끊기',
        on: () => {
          api.st['n'] = -1;
          api.play((c, o, t0) => {
            const x = osc(c, 'triangle', 220, t0, 0.3);
            x.frequency.exponentialRampToValueAtTime(110, t0 + 0.25);
            x.connect(envG(c, o, t0, 0.005, 0, 0.25, 0.3));
          }, 0.3);
        },
      },
    ],
    icon(g, k, _t, _p, st) {
      const manual = st['at'] !== undefined && performance.now() - (st['at'] ?? 0) < 4000;
      const n = manual ? (st['n'] ?? -1) : on01(k) ? Math.min(7, Math.floor((k * (8 * 0.22 + 0.35)) / 0.22)) : -1;
      for (let i = 0; i < 8; i++) {
        const lit = i <= n;
        const x = -42 + i * 11;
        const hgt = 8 + i * 5;
        rr(g, x, 28 - hgt, 8, hgt, 2);
        g.fillStyle = lit ? `hsl(${40 - i * 4},100%,${60 + i * 2}%)` : 'rgba(255,255,255,0.08)';
        g.fill();
      }
      if (n >= 0) {
        const sc = 1 + (manual ? 0.2 : 0) + n * 0.04;
        g.save();
        g.translate(-14, -26);
        g.scale(sc, sc);
        txt(g, `${n + 1} 콤보`, 0, 0, 14, '#ffd27a', 'center', 900);
        g.restore();
        txt(g, `× 2^(${n}/12) = ${Math.pow(2, n / 12).toFixed(2)}`, -14, -10, 8, 'rgba(255,255,255,0.6)', 'center', 700, MONO);
      }
    },
  }
```

## 관련 기술
- 이 효과를 만든 기술: [정답 아르페지오 (조성 맞춤)](https://ai-techstudio.web.app/ai/t/i141.md) `i141`
