---
code: "INT-24"
id: "c-audio"
tab: "dynamic-2"
kind: "interactive"
title: "Audio Wave · 사운드 웨이브"
library: "Canvas"
deps: ["canvas"]
load: "high"
pasteKind: "custom-mount"
family: "signal"
dataShape: "animated waveform line"
tasks: ["explore data", "filter subsets", "monitor change", "show trend over time", "show sound intensity"]
tags: ["interactive", "exploration", "linked view", "time-series", "audio", "waveform", "wave", "사운드", "웨이브"]
gallery: "https://graph.heal7.com/#INT-24"
js: "/sku/INT-24.js"
---

# [INT-24] Audio Wave · 사운드 웨이브

> AI 붙여넣기 카드. 갤러리 런타임(`main.js`)이 아니라 이 파일을 가져와라.

## 언제 가져가나
다중 주파수 사인파가 합성되어 흐르는 살아있는 파형 — 색 그라디언트 + 트레일.

**응용** 음성 인터페이스 비주얼라이저, 라이브 스트림 인디케이터, 명상/사운드 앱.

## 데이터 모양
- family: `signal`
- dataShape: animated waveform line
- tasks: explore data, filter subsets, monitor change, show trend over time, show sound intensity
- tags: interactive, exploration, linked view, time-series, audio, waveform, wave, 사운드, 웨이브


## 의존
canvas · load `high` · library `Canvas`

## 붙여넣는 법
1. 라이브러리 없이 host 엘리먼트에 직접 그린다
2. 함수 전체를 가져간다
3. `getElementById('원본id')` 만 대상 노드로 바꾼다

```js
const T = {
  "bg": "transparent",
  "fg": "#ece8f5",
  "sub": "#a59bbd",
  "grid": "rgba(255,255,255,.08)",
  "gold": "#e8c069",
  "royal": "#6b8cff",
  "rose": "#e07a9f",
  "teal": "#6dd2c4",
  "plum": "#b487e8"
};
const isDark = () => document.documentElement.getAttribute('data-theme') !== 'light';
const host = document.querySelector('#chart');
const factory = function(){
  const dom = document.getElementById('c-audio'); if(!dom) return;
  const cv = document.createElement('canvas');
  cv.style.cssText = 'width:100%;height:100%;display:block;border-radius:8px';
  dom.appendChild(cv);
  const ctx = cv.getContext('2d');
  let phase = 0;
  function resize(){
    const r = cv.getBoundingClientRect();
    cv.width = Math.max(1, Math.floor(r.width*devicePixelRatio));
    cv.height = Math.max(1, Math.floor(r.height*devicePixelRatio));
  }
  resize();
  window.addEventListener('resize', resize);
  const dark = isDark();
  const layers = [
    {c1:T.gold,  c2:T.rose,  freq:0.012, ampF:0.22},
    {c1:T.royal, c2:T.gold,  freq:0.018, ampF:0.16},
    {c1:T.rose,  c2:T.royal, freq:0.025, ampF:0.10}
  ];
  function step(){
    const W = cv.width, H = cv.height;
    ctx.fillStyle = dark ? 'rgba(0,0,0,.18)' : 'rgba(255,255,255,.22)';
    ctx.fillRect(0,0,W,H);
    phase += 0.032;
    layers.forEach((L, i) => {
      const grad = ctx.createLinearGradient(0,0,W,0);
      grad.addColorStop(0, L.c1); grad.addColorStop(1, L.c2);
      ctx.strokeStyle = grad;
      ctx.lineWidth = (3 - i*0.7)*devicePixelRatio;
      ctx.shadowBlur = 10*devicePixelRatio;
      ctx.shadowColor = L.c1;
      ctx.beginPath();
      const amp = H * L.ampF;
      for(let x=0; x<=W; x+=4){
        const y = H/2 + Math.sin(x*L.freq + phase + i*1.2)*amp + Math.sin(x*L.freq*2 + phase*1.4)*amp*0.3;
        if(x===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
      }
      ctx.stroke();
    });
    ctx.shadowBlur = 0;
    requestAnimationFrame(step);
  }
  step();
  charts.push({resize});
};
factory();
```

## 원본 factory (갤러리 정본 발췌)
- file: `frontend/js/graph-gallery/main.js`
- lines: 2334-2379
- gallery: https://graph.heal7.com/#INT-24
- raw js: https://graph.heal7.com/sku/INT-24.js

```js
function(){
  const dom = document.getElementById('c-audio'); if(!dom) return;
  const cv = document.createElement('canvas');
  cv.style.cssText = 'width:100%;height:100%;display:block;border-radius:8px';
  dom.appendChild(cv);
  const ctx = cv.getContext('2d');
  let phase = 0;
  function resize(){
    const r = cv.getBoundingClientRect();
    cv.width = Math.max(1, Math.floor(r.width*devicePixelRatio));
    cv.height = Math.max(1, Math.floor(r.height*devicePixelRatio));
  }
  resize();
  window.addEventListener('resize', resize);
  const dark = isDark();
  const layers = [
    {c1:T.gold,  c2:T.rose,  freq:0.012, ampF:0.22},
    {c1:T.royal, c2:T.gold,  freq:0.018, ampF:0.16},
    {c1:T.rose,  c2:T.royal, freq:0.025, ampF:0.10}
  ];
  function step(){
    const W = cv.width, H = cv.height;
    ctx.fillStyle = dark ? 'rgba(0,0,0,.18)' : 'rgba(255,255,255,.22)';
    ctx.fillRect(0,0,W,H);
    phase += 0.032;
    layers.forEach((L, i) => {
      const grad = ctx.createLinearGradient(0,0,W,0);
      grad.addColorStop(0, L.c1); grad.addColorStop(1, L.c2);
      ctx.strokeStyle = grad;
      ctx.lineWidth = (3 - i*0.7)*devicePixelRatio;
      ctx.shadowBlur = 10*devicePixelRatio;
      ctx.shadowColor = L.c1;
      ctx.beginPath();
      const amp = H * L.ampF;
      for(let x=0; x<=W; x+=4){
        const y = H/2 + Math.sin(x*L.freq + phase + i*1.2)*amp + Math.sin(x*L.freq*2 + phase*1.4)*amp*0.3;
        if(x===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
      }
      ctx.stroke();
    });
    ctx.shadowBlur = 0;
    requestAnimationFrame(step);
  }
  step();
  charts.push({resize});
}
```

