Web Audio API(Webkit) 기본 사용법



Web Audio API(Webkit) 기본 사용법



지난 7월 7일 NHN 개발자 블로그인 "Hello World"에서 열린 "제2회 Front-End 개발자들을 위한 오픈 세미나" 가 있었습니다.

   

참석은 하였지만, 개인적인 사정으로 두 번째 섹션까지밖에 들을 수 없어 많이 아쉬웠던 세미나 이기도 합니다.;;ㅠㅠ

    

아래 작성된 코드는 이날 세미나의 세 번째 섹션인 "Web Audio API"에 대한 소스를 이리저리 찾아보며(구글링을 통해) 작성된 예제 코드입니다.


또한, 코드 작성 시 기술(음향)에 대한 전문적인 지식이 많이 부족하여 소스에 대한 응용이 전무합니다.;;;(API에 대한 한글 문서가 없다는 점과 기반 지식이 부족하여 몇몇 소스는 이해하기도 어려었습니다.;;)





1. 사운드 데이타 요청(Ajax) 시 응답 HTTP Header는 아래와 같습니다.










2. 작성된 소스는 아래와 같으며, 기본적인 API 사용에 관해 관심 있으신 분은 아래 소스를 테스트해보시기 바랍니다.


  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml">
  3. <head id="Head1" runat="server">
  4. <script type="text/javascript" src="/sabo/contents/script/jquery.js"></script>
  5. <title></title>
  6. <script type="text/javascript">
  7. //<![CDATA[
  8.        
  9.     // BufferLoader
  10.     // Ajax 방식을 통해 사운드 데이터를 바이너리로 저장한다.
  11.     var BufferLoader = (function () {
  12.  
  13.         var BufferLoader = function (opt) {
  14.  
  15.             return new BufferLoader.fn.init(opt);
  16.         }
  17.  
  18.         BufferLoader.fn = BufferLoader.prototype = {
  19.             init: function (opt) {
  20.  
  21.                 this.ctx = new webkitAudioContext();
  22.                 this.urls = [];
  23.                 this.buffers = [];
  24.                 this.callback = function () { ; };
  25.                 this.errcallback = function () { ; };
  26.                 this.loaderCount = 0;
  27.  
  28.                 marge.call(this, opt);
  29.  
  30.                 return this;
  31.  
  32.             },
  33.             loader: function () {
  34.                 for (var i = 0, length = this.urls.length; i < length; i++) {
  35.                     load.apply(this, [this.urls[i], this.callback, this.errcallback]);
  36.                 }
  37.             }
  38.         }
  39.  
  40.         function load(url, callback) {
  41.  
  42.             var xhr = new XMLHttpRequest();
  43.             xhr.open("get", url, true);
  44.             // 전송받을 데이터 타입 설정 arraybuffer(바이너리)
  45.             xhr.responseType = "arraybuffer";
  46.  
  47.             var that = this;
  48.  
  49.             xhr.onload = function () {
  50.  
  51.                 that.ctx.decodeAudioData(
  52.                 xhr.response,
  53.                 function (buffer) {
  54.  
  55.                     if (!buffer) return;
  56.  
  57.                     that.buffers.push(buffer);
  58.  
  59.                     if (++that.loaderCount === that.urls.length) {
  60.                         typeof that.callback === 'function' && that.callback.call(that.ctx, that.buffers);
  61.                     }
  62.                 });
  63.  
  64.             };
  65.  
  66.             xhr.onerror = function (e) {
  67.                 if (that.loaderCount === that.urls.length) {
  68.                     typeof that.errcallback === 'function' && that.errcallback.call(that.ctx, e);
  69.                 }
  70.             };
  71.  
  72.             xhr.send();
  73.         }
  74.  
  75.         function marge() {
  76.  
  77.             var target = this
  78.                 , opts = []
  79.                 , src = null
  80.                 , copy = null;
  81.  
  82.             for (var i = 0, length = arguments.length; i < length; i++) {
  83.  
  84.                 opts = arguments[i];
  85.  
  86.                 for (var n in opts) {
  87.  
  88.                     src = target[n];
  89.                     copy = opts[n];
  90.                     target[n] = copy;
  91.                 }
  92.             }
  93.  
  94.             return target;
  95.         }
  96.  
  97.  
  98.         BufferLoader.fn.init.prototype = BufferLoader.prototype;
  99.  
  100.         return BufferLoader;
  101.  
  102.     })();
  103.  
  104.  
  105.     // WebAudio
  106.     var WebAudio = (function () {
  107.  
  108.         var that = null;
  109.  
  110.         var WebAudio = function (opt) {
  111.             return new WebAudio.fn.init(opt);
  112.         }
  113.  
  114.         WebAudio.fn = WebAudio.prototype = {
  115.             init: function (opt) {
  116.  
  117.                 this.ctx = null;
  118.                 this.buffers = [];
  119.  
  120.  
  121.                 marge.call(this, opt);
  122.  
  123.                 if (that) return that;
  124.  
  125.                 that = this;
  126.  
  127.                 return this;
  128.  
  129.             },
  130.             currentSource: null,
  131.             play: function (type, callback) {
  132.  
  133.                 type = type || '';
  134.                 callback = callback || function () { ; };
  135.  
  136.                 // 현재 플레이 되고 있는 소스 노드를 off 시키고 새로운 소스 노드를 on 시킨다.
  137.                 if (this.currentSource) this.currentSource.noteOff(0);
  138.  
  139.                 context = this.ctx;
  140.  
  141.                 this.currentSource = this.getTypeToSource(context, type);
  142.  
  143.                 var gainNode = context.createGainNode();
  144.                 gainNode.connect(context.destination);
  145.  
  146.                 this.currentSource.connect(gainNode);
  147.                 this.currentSource.noteOn(0);
  148.  
  149.                 callback.call(context);
  150.  
  151.                 return this;
  152.  
  153.             },
  154.             currentGainNode: null,
  155.             crossFadePlay: function (type, volume, callback) {
  156.  
  157.                 type = type || '';
  158.                 volume = volume || 0;
  159.                 callback = callback || function () { ; };
  160.  
  161.                 // 현재 플레이 되고 있는 소스 노드의 사운드 볼륨을 0.5로 설정하여 새롭게 적용되는 소스 노드의 사운드와 겹치게 만든다.
  162.                 if (this.currentGainNode) this.currentGainNode.gain.value = volume;
  163.  
  164.                 context = this.ctx;
  165.  
  166.                 var source = this.getTypeToSource(context, type);
  167.  
  168.                 this.currentGainNode = context.createGainNode();
  169.                 this.currentGainNode.connect(context.destination);
  170.  
  171.                 source.connect(this.currentGainNode);
  172.                 source.noteOn(0);
  173.  
  174.                 callback.call(context);
  175.  
  176.                 return this;
  177.  
  178.             },
  179.             getTypeToSource: function (context, type) {
  180.  
  181.                 var source = context.createBufferSource();
  182.  
  183.                 switch (type) {
  184.                     case 'bg1':
  185.                         {
  186.                             source.buffer = this.buffers[0];
  187.                             break;
  188.                         }
  189.                     case 'bg2':
  190.                         {
  191.                             source.buffer = this.buffers[1];
  192.                             break;
  193.                         }
  194.                     case 'bg3':
  195.                         {
  196.                             source.buffer = this.buffers[2];
  197.                             break;
  198.                         }
  199.                 }
  200.  
  201.                 return source;
  202.             },
  203.             sourceNormalEach: function (callback) {
  204.  
  205.                 context = this.ctx;
  206.                 buffers = this.buffers || [];
  207.                 callback = callback || function () { ; };
  208.  
  209.                 for (var i = 0, length = buffers.length; i < length; i++) {
  210.  
  211.                     // 사운드 소스 생성
  212.                     var source = context.createBufferSource();
  213.                     source.buffer = buffers[i];
  214.  
  215.                     // 소스를 스피커와 연결
  216.                     source.connect(context.destination);
  217.                     //callback
  218.                     callback.apply(context, [source, i + 1]);
  219.                 }
  220.  
  221.                 return this;
  222.             },
  223.             sourceGainNodeEach: function (callback) {
  224.  
  225.                 context = this.ctx;
  226.                 buffers = this.buffers || [];
  227.                 callback = callback || function () { ; };
  228.  
  229.                 for (var i = 0, length = buffers.length; i < length; i++) {
  230.  
  231.  
  232.                     var gainNode = context.createGainNode();
  233.                     // gainNode를 스피커와 연결
  234.                     gainNode.connect(context.destination);
  235.  
  236.                     var source = context.createBufferSource();
  237.                     source.buffer = buffers[i];
  238.                     // 소스를 생성된 gainNode 노드와 연결
  239.                     source.connect(gainNode);
  240.  
  241.                     //callback
  242.                     callback.apply(context, [source, gainNode, i + 1]);
  243.                 }
  244.  
  245.                 return this;
  246.             },
  247.             sourceCompressorEach: function (callback) {
  248.  
  249.                 context = this.ctx;
  250.                 buffers = this.buffers || [];
  251.                 callback = callback || function () { ; };
  252.  
  253.                 for (var i = 0, length = buffers.length; i < length; i++) {
  254.  
  255.                     var compressor = context.createDynamicsCompressor();
  256.                     compressor.connect(context.destination);
  257.  
  258.                     var gainNode = context.createGainNode();
  259.                     // gainNode를 Compressor와 연결
  260.                     gainNode.connect(compressor);
  261.  
  262.                     var source = context.createBufferSource();
  263.                     source.buffer = buffers[i];
  264.                     source.connect(gainNode);
  265.                     //callback
  266.                     callback.apply(context, [source, gainNode, compressor, i + 1]);
  267.                 }
  268.  
  269.                 return this;
  270.             },
  271.             sourceDelayNodeEach: function (callback) {
  272.  
  273.                 context = this.ctx;
  274.                 buffers = this.buffers || [];
  275.                 callback = callback || function () { ; };
  276.  
  277.                 for (var i = 0, length = buffers.length; i < length; i++) {
  278.  
  279.                     var delayNode = context.createDelayNode();
  280.                     delayNode.connect(context.destination);
  281.  
  282.                     var source = context.createBufferSource();
  283.                     source.buffer = buffers[i];
  284.                     source.connect(delayNode);
  285.                     //callback
  286.                     callback.apply(context, [source, delayNode, i + 1]);
  287.                 }
  288.  
  289.                 return this;
  290.             },
  291.             sourceFilterEach: function (callback) {
  292.  
  293.                 context = this.ctx;
  294.                 buffers = this.buffers || [];
  295.                 callback = callback || function () { ; };
  296.  
  297.                 for (var i = 0, length = buffers.length; i < length; i++) {
  298.  
  299.                     // createBiquadFilter를 사용해, 사운드 시그널의 주파수를 필터링해 주파수의 응답을 변경한다.
  300.                     var filter = context.createBiquadFilter();
  301.                     filter.connect(context.destination);
  302.  
  303.                     var source = context.createBufferSource();
  304.                     source.buffer = buffers[i];
  305.                     source.connect(filter);
  306.                     //callback
  307.                     callback.apply(context, [source, filter, i + 1]);
  308.                 }
  309.  
  310.                 return this;
  311.             },
  312.             sourcePannerEach: function (callback) {
  313.  
  314.                 context = this.ctx;
  315.                 buffers = this.buffers || [];
  316.                 callback = callback || function () { ; };
  317.  
  318.                 for (var i = 0, length = buffers.length; i < length; i++) {
  319.  
  320.                     // AudioPannerNode를 활용해 위치와 공간을 프로세싱 한다.
  321.                     var panner = context.createPanner();
  322.                     panner.connect(context.destination);
  323.  
  324.                     var source = context.createBufferSource();
  325.                     source.buffer = buffers[i];
  326.                     source.connect(panner);
  327.                     //callback
  328.                     callback.apply(context, [source, panner, i + 1]);
  329.                 }
  330.  
  331.                 return this;
  332.             },
  333.             sourceConvolverEach: function (callback) {
  334.  
  335.                 context = this.ctx;
  336.                 buffers = this.buffers || [];
  337.                 callback = callback || function () { ; };
  338.  
  339.                 for (var i = 0, length = buffers.length; i < length; i++) {
  340.  
  341.                     // 동일한 소리를 장소에 따라 다르게 녹음된 데이터를 생성한다.
  342.                     var convolver = context.createConvolver();
  343.                     // 임펄스 데이터
  344.                     convolver.buffer = buffers[i];
  345.                     convolver.connect(context.destination);
  346.  
  347.                     var source = context.createBufferSource();
  348.                     source.buffer = buffers[i];
  349.                     source.connect(convolver);
  350.                     //callback
  351.                     callback.apply(context, [source, convolver, i + 1]);
  352.                 }
  353.  
  354.                 return this;
  355.             }
  356.  
  357.         }
  358.  
  359.         WebAudio.fn.init.prototype = WebAudio.prototype;
  360.  
  361.  
  362.         function marge() {
  363.  
  364.             var target = this
  365.                 , opts = []
  366.                 , src = null
  367.                 , copy = null;
  368.  
  369.             for (var i = 0, length = arguments.length; i < length; i++) {
  370.  
  371.                 opts = arguments[i];
  372.  
  373.                 for (var n in opts) {
  374.  
  375.                     src = target[n];
  376.                     copy = opts[n];
  377.                     target[n] = copy;
  378.                 }
  379.             }
  380.  
  381.             return target;
  382.         }
  383.  
  384.         return WebAudio;
  385.  
  386.     })();
  387.  
  388.  
  389.     function init() {
  390.         BufferLoader({
  391.             urls: [
  392.                 '/sabo/bg1.mp3',
  393.                 '/sabo/bg2.mp3',
  394.                 '/sabo/bg3.mp3'
  395.             ],
  396.             callback: function (buffers) {
  397.  
  398.                 var ctx = this;
  399.                 var Audio = WebAudio({
  400.                     ctx: ctx,
  401.                     buffers: buffers
  402.                 });
  403.  
  404.  
  405.                 $('#BackgroundSoundPlay1').click(function (e) {
  406.                     Audio.play('bg1');
  407.                     return false;
  408.                 });
  409.  
  410.                 $('#BackgroundSoundPlay2').click(function (e) {
  411.                     Audio.play('bg2');
  412.                     return false;
  413.                 });
  414.  
  415.                 $('#BackgroundSoundPlay3').click(function (e) {
  416.                     Audio.play('bg3');
  417.                     return false;
  418.                 });
  419.  
  420.                 $('#BackgroundSoundCrossFadePlay1').click(function (e) {
  421.                     Audio.crossFadePlay('bg1', 0.5);
  422.                     return false;
  423.                 });
  424.  
  425.                 $('#BackgroundSoundCrossFadePlay2').click(function (e) {
  426.                     Audio.crossFadePlay('bg2', 0.5);
  427.                     return false;
  428.                 });
  429.  
  430.                 $('#BackgroundSoundCrossFadePlay3').click(function (e) {
  431.                     Audio.crossFadePlay('bg3', 0.5);
  432.                     return false;
  433.                 });
  434.  
  435.                 $('#sourceNormalEach').click(function (e) {
  436.                     Audio.sourceNormalEach(function (source, i) {
  437.  
  438.                         // 소스 반복 설정    
  439.                         //source.loop = true;
  440.                         source.noteOn(0);
  441.                     });
  442.  
  443.                     return false;
  444.                 });
  445.  
  446.                 $('#sourceGainNodeEach').click(function (e) {
  447.                     Audio.sourceGainNodeEach(function (source, gainNode, i) {
  448.  
  449.                         // 재생 볼륨 설정
  450.                         gainNode.gain.value = 0.2 * i;
  451.                         // 소스 반복 설정
  452.                         //source.loop = true;
  453.                         // 재생 시점이 다른 반복 재생
  454.                         source.noteOn(this.currentTime + i);
  455.                     });
  456.  
  457.                     return false;
  458.                 });
  459.  
  460.                 $('#sourceGainNodeScheduleEach').click(function (e) {
  461.                     Audio.sourceGainNodeEach(function (source, gainNode, i) {
  462.  
  463.                         //gainNode.gain.cancelScheduledValues(this.currentTime);
  464.  
  465.                         // 특정 시간까지 값을 리니어하게 변경하도록 스케줄링 한다.
  466.                         // Ramp up and down.
  467.                         gainNode.gain.linearRampToValueAtTime(1.0, (this.currentTime + 0.5));
  468.                         gainNode.gain.linearRampToValueAtTime(0.0, (this.currentTime + 1.5));
  469.                         gainNode.gain.linearRampToValueAtTime(1.0, (this.currentTime + 1.5));
  470.                         gainNode.gain.linearRampToValueAtTime(0.0, (this.currentTime + 2.0));
  471.                         gainNode.gain.linearRampToValueAtTime(1.0, (this.currentTime + 2.5));
  472.  
  473.                         source.noteOn(0);
  474.                     });
  475.  
  476.  
  477.  
  478.                     return false;
  479.                 });
  480.  
  481.                 $('#sourceDelayNodeEach').click(function (e) {
  482.                     Audio.sourceDelayNodeEach(function (source, delayNode, i) {
  483.                         // DelayNode를 사용하면 오디오 시그널의 통과를 지연시킨다.
  484.                         delayNode.delayTime.setValueAtTime(0, this.currentTime);
  485.  
  486.                         source.noteOn(0);
  487.                     });
  488.  
  489.                     return false;
  490.                 });
  491.  
  492.  
  493.                 $('#sourceFilterEach').click(function (e) {
  494.                     Audio.sourceFilterEach(function (source, filter, i) {
  495.  
  496.                         // LowPass
  497.                         filter.type = 0;
  498.                         filter.Q.vlaue = 12.06 + i;
  499.                         filter.frequency.value = 400;
  500.  
  501.                         source.noteOn(0);
  502.                     });
  503.  
  504.                     return false;
  505.                 });
  506.  
  507.  
  508.                 $('#sourcePannerEach').click(function (e) {
  509.                     Audio.sourcePannerEach(function (source, panner, i) {
  510.  
  511.                         panner.coneOuterGain = 0.5;
  512.                         panner.coneOuterAngle = 180;
  513.                         panner.coneinnerAngle = 0;
  514.  
  515.                         panner.setPosition(1, 10, 0);
  516.                         panner.setOrientation(20, 10, 5);
  517.                         panner.setVelocity(0, 1, 0);
  518.  
  519.                         source.noteOn(0);
  520.                     });
  521.  
  522.                     return false;
  523.                 });
  524.  
  525.                 $('#sourceConvolverEach').click(function (e) {
  526.                     Audio.sourceConvolverEach(function (source, convolver, i) {
  527.                         source.noteOn(0);
  528.                     });
  529.  
  530.                     return false;
  531.                 });
  532.             },
  533.             errcallback: function (e) {
  534.             }
  535.         }).loader();
  536.            
  537.     }
  538.        
  539.     window.addEventListener('load', init, false);
  540.  
  541. //]]>
  542. </script>
  543. </head>
  544. <body>
  545.  
  546. <div>Play</div>
  547. <a id="BackgroundSoundPlay1" href="#">BackgroundSoundPlay1</a>
  548. <a id="BackgroundSoundPlay2" href="#">BackgroundSoundPlay2</a>
  549. <a id="BackgroundSoundPlay3" href="#">BackgroundSoundPlay3</a><br /><br />
  550.  
  551. <div>Cross Fade Play</div>
  552. <a id="BackgroundSoundCrossFadePlay1" href="#">BackgroundSoundCrossFadePlay1</a>
  553. <a id="BackgroundSoundCrossFadePlay2" href="#">BackgroundSoundCrossFadePlay2</a>
  554. <a id="BackgroundSoundCrossFadePlay3" href="#">BackgroundSoundCrossFadePlay3</a><br /><br />
  555.  
  556. <div>여러 가지 동작</div>
  557. <a id="sourceNormalEach" href="#">sourceNormalEach</a><br /><br />
  558. <a id="sourceGainNodeEach" href="#">sourceGainNodeEach</a><br /><br />
  559. <a id="sourceGainNodeScheduleEach" href="#">sourceGainNodeScheduleEach</a><br /><br />
  560. <a id="sourceDelayNodeEach" href="#">sourceDelayNodeEach</a><br /><br />
  561. <a id="sourceFilterEach" href="#">sourceFilterEach</a><br /><br />
  562. <a id="sourcePannerEach" href="#">sourcePannerEach</a><br /><br />
  563. <a id="sourceConvolverEach" href="#">sourceConvolverEach</a><br /><br />
  564. </body>
  565. </html>




참고 사이트:


제2회 hello world 오픈세미나 Web Audio API-가능성엿보기:

http://www.slideshare.net/deview/2-hello-world-web-aaudioapi


SoftSynth:

http://www.softsynth.com/webaudio/gainramp.php


GETTING STARTED WITH WEB AUDIO API:

http://www.html5rocks.com/en/tutorials/webaudio/intro/