Web Audio APIãçšããWebXRã®3D空éãªãŒãã£ãªå®è£ ã¬ã€ããéçºè åãã«ã鳿ºäœçœ®èšç®ã®åºæ¬ããå¿çšãŸã§ãç¶²çŸ çã«è§£èª¬ããŸãã
ååšæã®ãµãŠã³ãïŒWebXR空éãªãŒãã£ãªãš3Däœçœ®èšç®ã®åŸ¹åºè§£èª¬
æ¥éã«é²åããã€ããŒã·ãæè¡ã®äžçã§ã¯ããã°ãã°èŠèŠçãªå¿ å®åºŠã泚ç®ãéããŸããç§ãã¡ã¯é«è§£å床ã®ãã£ã¹ãã¬ã€ããªã¢ã«ãªã·ã§ãŒããŒãè€éãª3Dã¢ãã«ã«é©åããŸããããããä»®æ³äžçãæ¡åŒµäžçã§çã®ååšæãšãªã¢ãªãã£ãçã¿åºãããã®æã匷åãªããŒã«ã®äžã€ãèŠéããããã¡ã§ããããã¯ãªãŒãã£ãªã§ãããã ã®ãªãŒãã£ãªã§ã¯ãªããå®å šã«ç©ºéåããã3次å ã®ãµãŠã³ãã§ãããç§ãã¡ã®è³ã«ãæ¬åœã«ããã«ãããšç¢ºä¿¡ããããã®ã§ãã
WebXR空éãªãŒãã£ãªã®äžçãžãããããããã¯ãé³ããå·Šè³ã§èããããã®ãšã空éã®ç¹å®ã®ç¹âé äžãå£ã®åããããŸãã¯é ãããããŠé£ãã§ããâããèãããã®ãšã®éãã§ãããã®æè¡ã¯ã次ã®ã¬ãã«ã®æ²¡å ¥æãžã®æãéãéµã§ãããååçãªäœéšãããŠã§ããã©ãŠã¶ããçŽæ¥ã¢ã¯ã»ã¹ã§ããæ·±ãé åçãªã€ã³ã¿ã©ã¯ãã£ããªäžçãžãšå€è²ãããŸãã
ãã®å æ¬çãªã¬ã€ãã¯ãäžçäžã®éçºè ããªãŒãã£ãªãšã³ãžãã¢ããããŠæè¡æå¥œå®¶ã®ããã«äœãããŸãããWebXRã«ããã3DãµãŠã³ãããžã·ã§ãã³ã°ã®èåŸã«ããäžå¿çãªæŠå¿µãšèšç®ãè§£ãæãããŸããåºç€ãšãªãWeb Audio APIãæ¢æ±ããããžã·ã§ãã³ã°ã®æ°åŠãåè§£ããçãããèªèº«ã®ãããžã§ã¯ãã«å¿ å®åºŠã®é«ã空éãªãŒãã£ãªãçµ±åããããã®å®è·µçãªæŽå¯ãæäŸããŸããã¹ãã¬ãªãè¶ ããèŠãç®ã ãã§ãªãé³ããªã¢ã«ãªäžçãæ§ç¯ããæ¹æ³ãåŠã¶æºåãããŠãã ããã
ãªã空éãªãŒãã£ãªã¯WebXRã«ãšã£ãŠã²ãŒã ãã§ã³ãžã£ãŒãªã®ã
æè¡çãªè©³çްã«é£ã³èŸŒãåã«ããªã空éãªãŒãã£ãªãXRäœéšã«ãšã£ãŠããã»ã©éèŠãªã®ããçè§£ããããšãäžå¯æ¬ ã§ããç§ãã¡ã®è³ã¯ãç°å¢ãçè§£ããããã«é³ãè§£éããããã«ã§ããŠããŸãããã®æ ¹æºçãªã·ã¹ãã ã¯ãèŠéã®å€ã«ãããã®ã§ããããåšå²ã®ç¶æ³ã«é¢ããæ å ±ãçµ¶ããæäŸããŠãããŸãããããä»®æ³ç°å¢ã§åçŸããããšã§ãããçŽæçã§ä¿¡ææ§ã®ããäœéšãåµé ããã®ã§ãã
ã¹ãã¬ãªãè¶ ããŠïŒã€ããŒã·ããªãµãŠã³ãã¹ã±ãŒããžã®é£èº
äœå幎ãã®éãããžã¿ã«ãªãŒãã£ãªã¯ã¹ãã¬ãªãµãŠã³ãã«æ¯é ãããŠããŸãããã¹ãã¬ãªã¯å·Šå³ã®æèŠãäœãåºãã®ã«å¹æçã§ãããåºæ¬çã«ã¯2ã€ã®ã¹ããŒã«ãŒãŸãã¯ããããã©ã³ã®éã«åºãã2次å ã®é³ã®å¹³é¢ã§ããé«ãã奥è¡ãããŸãã¯3D空éã«ããã鳿ºã®æ£ç¢ºãªäœçœ®ãæ£ç¢ºã«è¡šçŸããããšã¯ã§ããŸããã
äžæ¹ã空éãªãŒãã£ãªã¯ã3次å ç°å¢ã§é³ãã©ã®ããã«æ¯ãèãããèšç®ããã¢ãã«ã§ãã鳿³¢ã鳿ºããäŒããããªã¹ããŒã®é ãè³ãšçžäºäœçšããéŒèã«å°éãããŸã§ãã·ãã¥ã¬ãŒãããŸãããã®çµæããŠãŒã¶ãŒãé ãäœãåãããšããã¹ãŠã®é³ã空éå ã®æç¢ºãªçºçç¹ãæã¡ããªã¢ã«ã«ç§»åãå€åãããµãŠã³ãã¹ã±ãŒããçãŸããŸãã
XRã¢ããªã±ãŒã·ã§ã³ã«ãããäž»ãªå©ç¹
é©åã«å®è£ ããã空éãªãŒãã£ãªã®åœ±é¿ã¯çµ¶å€§ã§ãããããããã¿ã€ãã®XRã¢ããªã±ãŒã·ã§ã³ã«åã³ãŸãïŒ
- ãªã¢ãªãºã ãšååšæã®åäžïŒ ä»®æ³ã®é³¥ãé äžã®æšã®æã§ãããã£ãããç¹å®ã®å»äžããè¶³é³ãè¿ã¥ããŠããããããšãäžçã¯ããå åºã§ãªã¢ã«ã«æããããŸãããã®èŠèŠãšèŽèŠã®æãããã®äžèŽã¯ãããã¬ãŒã³ã¹ãâä»®æ³ç°å¢ã«ãããšããå¿ççæèŠâãçã¿åºãããã®ç€ã§ãã
- ãŠãŒã¶ãŒã¬ã€ãã³ã¹ãšèªç¥åºŠã®åäžïŒ ãªãŒãã£ãªã¯ããŠãŒã¶ãŒã®æ³šæãåŒãããã®åŒ·åã§éªéã«ãªããªãæ¹æ³ã«ãªãåŸãŸããéèŠãªãªããžã§ã¯ãã®æ¹åããã®åŸ®ããªé³ã®åå³ã¯ãç¹æ» ããç¢å°ãããèªç¶ã«ãŠãŒã¶ãŒã®èŠç·ãå°ãããšãã§ããŸãããŸããç¶æ³èªèãé«ãããŠãŒã¶ãŒã«å³æã®èŠéã®å€ã§èµ·ãã£ãŠããã€ãã³ããèŠåããŸãã
- ã¢ã¯ã»ã·ããªãã£ã®åäžïŒ èŠèŠé害ã®ãããŠãŒã¶ãŒã«ãšã£ãŠã空éãªãŒãã£ãªã¯é©æ°çãªããŒã«ãšãªãåŸãŸããä»®æ³ç©ºéã®ã¬ã€ã¢ãŠãããªããžã§ã¯ãã®äœçœ®ãä»ã®ãŠãŒã¶ãŒã®ååšã«é¢ããè±å¯ãªæ å ±å±€ãæäŸããããèªä¿¡ãæã£ãããã²ãŒã·ã§ã³ãšã€ã³ã¿ã©ã¯ã·ã§ã³ãå¯èœã«ããŸãã
- ããæ·±ãææ çã€ã³ãã¯ãïŒ ã²ãŒã ããã¬ãŒãã³ã°ãã¹ããŒãªãŒããªã³ã°ã«ãããŠããµãŠã³ããã¶ã€ã³ã¯ã ãŒããèšå®ããããã«äžå¯æ¬ ã§ããé ãã§é¿ãé³ã¯ã¹ã±ãŒã«æãå€ç¬æãçã¿åºããçªç¶ã®è¿ãé³ã¯é©ããå±éºãåŒã³èµ·ããããšãã§ããŸãã空éåã¯ããã®ææ çãªããŒã«ããããèšãç¥ããªãã»ã©å¢å¹ ãããŸãã
ã³ã¢ã³ã³ããŒãã³ãïŒWeb Audio APIã®çè§£
ãã©ãŠã¶å ã§ã®ç©ºéãªãŒãã£ãªã®éæ³ã¯ãWeb Audio APIã«ãã£ãŠå¯èœã«ãªããŸãããã®åŒ·åã§é«ã¬ãã«ãªJavaScript APIã¯ãçŸä»£ã®ãã©ãŠã¶ã«çŽæ¥çµã¿èŸŒãŸããŠããããªãŒãã£ãªã®å¶åŸ¡ãšåæã®ããã®å æ¬çãªã·ã¹ãã ãæäŸããŸããããã¯åã«ãµãŠã³ããã¡ã€ã«ãåçããããã ãã®ãã®ã§ã¯ãªããè€éãªãªãŒãã£ãªåŠçã°ã©ããäœæããããã®ã¢ãžã¥ã©ãŒãã¬ãŒã ã¯ãŒã¯ã§ãã
AudioContextïŒããªãã®ãµãŠã³ããŠãããŒã¹
Web Audio APIã®ãã¹ãŠã¯AudioContext
å
ã§èµ·ãããŸããããã¯ããªãŒãã£ãªã·ãŒã³å
šäœã®ããã®ã³ã³ãããã¯ãŒã¯ã¹ããŒã¹ãšèããããšãã§ããŸãããªãŒãã£ãªããŒããŠã§ã¢ãã¿ã€ãã³ã°ããããŠãã¹ãŠã®ãµãŠã³ãã³ã³ããŒãã³ãéã®æ¥ç¶ã管çããŸãã
ãããäœæããããšããããããWeb Audioã¢ããªã±ãŒã·ã§ã³ã®æåã®ã¹ãããã§ãïŒ
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
ãªãŒãã£ãªããŒãïŒãµãŠã³ãã®æ§æèŠçŽ
Web Audio APIã¯ã«ãŒãã£ã³ã°ã®æŠå¿µã§åäœããŸããããŸããŸãªãªãŒãã£ãªããŒããäœæããããããæ¥ç¶ããŠåŠçã°ã©ãã圢æããŸãããµãŠã³ãã¯ãœãŒã¹ããŒãããæµãã1ã€ä»¥äžã®åŠçããŒããééããæçµçã«ãã¹ãã£ããŒã·ã§ã³ããŒãïŒéåžžã¯ãŠãŒã¶ãŒã®ã¹ããŒã«ãŒïŒã«å°éããŸãã
- ãœãŒã¹ããŒãïŒ ãããã®ããŒãã¯é³ãçæããŸããäžè¬çãªãã®ã«
AudioBufferSourceNode
ããããããã¯ã¡ã¢ãªå ã®ãªãŒãã£ãªã¢ã»ããïŒãã³ãŒããããMP3ãWAVãã¡ã€ã«ãªã©ïŒãåçããŸãã - åŠçããŒãïŒ ãããã®ããŒãã¯é³ãä¿®æ£ããŸãã
GainNode
ã¯é³éã倿ŽããBiquadFilterNode
ã¯ã€ã³ã©ã€ã¶ãŒãšããŠæ©èœãããããŠâç§ãã¡ã®ç®çã«ãšã£ãŠæãéèŠãªâPannerNode
ã¯é³ã3D空éã«é 眮ããŸãã - ãã¹ãã£ããŒã·ã§ã³ããŒãïŒ ããã¯æçµçãªåºåã§ããã
audioContext.destination
ã§è¡šãããŸãããã¹ãŠã®ã¢ã¯ãã£ããªãªãŒãã£ãªã°ã©ãã¯ãèãããããã«ããããã«ã¯æçµçã«ãã®ããŒãã«æ¥ç¶ãããªããã°ãªããŸããã
PannerNodeïŒç©ºéåã®å¿èéš
PannerNode
ã¯ãWeb Audio APIã«ããã3D空éãªãŒãã£ãªã®äžå¿çãªã³ã³ããŒãã³ãã§ãããµãŠã³ããœãŒã¹ã`PannerNode`ãéããŠã«ãŒãã£ã³ã°ãããšããªã¹ããŒã«å¯Ÿãã3D空éå
ã§ã®ç¥èŠçãªäœçœ®ãå¶åŸ¡ã§ããããã«ãªããŸããããã¯åäžãã£ã³ãã«ïŒã¢ãã©ã«ïŒã®å
¥åãåãåããèšç®ãããäœçœ®ã«åºã¥ããŠããã®é³ããªã¹ããŒã®äž¡è³ã§ã©ã®ããã«èãããããã·ãã¥ã¬ãŒãããã¹ãã¬ãªä¿¡å·ãåºåããŸãã
PannerNode
ã«ã¯ããã®äœçœ®ïŒpositionX
ãpositionY
ãpositionZ
ïŒãšåãïŒorientationX
ãorientationY
ãorientationZ
ïŒãå¶åŸ¡ããããããã£ãããããããã«ã€ããŠã¯åŸã»ã©è©³ããæ¢ããŸãã
3DãµãŠã³ãã®æ°åŠïŒäœçœ®ãšåãã®èšç®
ä»®æ³ç°å¢ã«é³ãæ£ç¢ºã«é 眮ããããã«ã¯ãå ±æã®åç §ãã¬ãŒã ãå¿ èŠã§ããããã§åº§æšç³»ãšå°ãã®ãã¯ãã«æŒç®ãç»å ŽããŸãã幞ããªããšã«ããããã®æŠå¿µã¯éåžžã«çŽæçã§ãããWebGLãTHREE.jsãBabylon.jsã®ãããªäººæ°ã®ãããã¬ãŒã ã¯ãŒã¯ã§3Dã°ã©ãã£ãã¯ã¹ãæ±ãããæ¹æ³ãšå®å šã«äžèŽããŠããŸãã
座æšç³»ã®ç¢ºç«
WebXRãšWeb Audio APIã¯å³æç³»ãã«ã«ã座æšç³»ã䜿çšããŸããç©ç空éã®äžå¿ã«ç«ã£ãŠããèªåãæ³åããŠãã ããïŒ
- Xè»žã¯æ°Žå¹³ã«èµ°ããŸãïŒå³ãæ£ãå·Šãè² ïŒã
- Y軞ã¯åçŽã«èµ°ããŸãïŒäžãæ£ãäžãè² ïŒã
- Z軞ã¯å¥¥è¡ãæ¹åã«èµ°ããŸãïŒåŸããæ£ãåãè² ïŒã
ããã¯éèŠãªèŠçŽã§ãããªã¹ããŒãå鳿ºãå«ãã·ãŒã³å ã®ãã¹ãŠã®ãªããžã§ã¯ãã¯ããã®ã·ã¹ãã å ã®(x, y, z)座æšã«ãã£ãŠãã®äœçœ®ãå®çŸ©ãããŸãã
ãªã¹ããŒïŒä»®æ³äžçã®ããªãã®è³
Web Audio APIã¯ããŠãŒã¶ãŒã®ãè³ããã©ãã«ãããã©ã¡ããåããŠããããç¥ãå¿
èŠããããŸããããã¯AudioContext
äžã®listener
ãšåŒã°ããç¹å¥ãªãªããžã§ã¯ãã«ãã£ãŠç®¡çãããŸãã
const listener = audioContext.listener;
listener
ã«ã¯ã3D空éå
ã§ã®ç¶æ
ãå®çŸ©ããããã€ãã®ããããã£ããããŸãïŒ
- äœçœ®ïŒ
listener.positionX
ãlistener.positionY
ãlistener.positionZ
ããããã¯ãªã¹ããŒã®äž¡è³ã®äžå¿ç¹ã®(x, y, z)座æšã衚ããŸãã - åãïŒ ãªã¹ããŒãåããŠããæ¹åã¯ããåæ¹ããã¯ãã«ãšãäžæ¹ããã¯ãã«ã®2ã€ã®ãã¯ãã«ã«ãã£ãŠå®çŸ©ãããŸãããããã¯
listener.forwardX/Y/Z
ãšlistener.upX/Y/Z
ããããã£ã«ãã£ãŠå¶åŸ¡ãããŸãã
ãŠãŒã¶ãŒãZ軞ã®è² ã®æ¹åã«ãŸã£ããåããŠããå Žåãããã©ã«ãã®åãã¯æ¬¡ã®ããã«ãªããŸãïŒ
- åæ¹ïŒ (0, 0, -1)
- äžæ¹ïŒ (0, 1, 0)
éèŠãªããšã«ãWebXRã»ãã·ã§ã³ã§ã¯ããããã®å€ãæåã§èšå®ããŸããã ãã©ãŠã¶ã¯ãVR/ARãããã»ããããã®ç©ççãªãã©ããã³ã°ããŒã¿ã«åºã¥ããŠãæ¯ãã¬ãŒã ããªã¹ããŒã®äœçœ®ãšåããèªåçã«æŽæ°ããŸããããªãã®ä»äºã¯é³æºãé 眮ããããšã§ãã
鳿ºïŒPannerNodeã®é 眮
空éåãããåãµãŠã³ãã¯ãããããç¬èªã®PannerNode
ãéããŠã«ãŒãã£ã³ã°ãããŸãããããŒã®äœçœ®ã¯ããªã¹ããŒãšåãã¯ãŒã«ã座æšç³»ã§èšå®ãããŸãã
const panner = audioContext.createPanner();
é³ãé 眮ããã«ã¯ããã®äœçœ®ããããã£ã®å€ãèšå®ããŸããäŸãã°ãåç¹(0,0,0)ã®çæ£é¢5ã¡ãŒãã«ã®äœçœ®ã«é³ãé 眮ããã«ã¯ïŒ
panner.positionX.value = 0;
panner.positionY.value = 0;
panner.positionZ.value = -5;
ãã®åŸãWeb Audio APIã®å éšãšã³ãžã³ãå¿ èŠãªèšç®ãå®è¡ããŸãããªã¹ããŒã®äœçœ®ãããããŒã®äœçœ®ãžã®ãã¯ãã«ã決å®ãããªã¹ããŒã®åããèæ ®ããé³ããã®å Žæããæ¥ãŠããããã«èãããããã«é©åãªãªãŒãã£ãªåŠçïŒé³éãé å»¶ããã£ã«ã¿ãªã³ã°ïŒãèšç®ããŸãã
å®è·µäŸïŒãªããžã§ã¯ãã®äœçœ®ãšPannerNodeã®é£æº
åçãªXRã·ãŒã³ã§ã¯ããªããžã§ã¯ãïŒãããã£ãŠé³æºïŒã¯ç§»åããŸããã¢ããªã±ãŒã·ã§ã³ã®ã¬ã³ããªã³ã°ã«ãŒãïŒ`requestAnimationFrame`ã«ãã£ãŠåŒã³åºããã颿°ïŒå
ã§ãPannerNode
ã®äœçœ®ãç¶ç¶çã«æŽæ°ããå¿
èŠããããŸãã
THREE.jsã®ãããª3Dã©ã€ãã©ãªã䜿çšããŠãããšæ³åããŠãã ãããã·ãŒã³ã«3Dãªããžã§ã¯ãããããããã«é¢é£ä»ãããããµãŠã³ãã远åŸããããã«ããããšããŸãã
// 'audioContext'ãš'panner'ã¯æ¢ã«äœææžã¿ãšä»®å®ããŸãã // 'virtualObject'ã¯3Dã·ãŒã³ã®ãªããžã§ã¯ãïŒäŸïŒa THREE.MeshïŒãšä»®å®ããŸãã // ãã®é¢æ°ã¯æ¯ãã¬ãŒã åŒã³åºãããŸãã function renderLoop() { // 1. ä»®æ³ãªããžã§ã¯ãã®ã¯ãŒã«ã座æšãååŸããŸãã // ã»ãšãã©ã®3Dã©ã€ãã©ãªã«ã¯ãã®ããã®ã¡ãœããããããŸãã const objectWorldPosition = new THREE.Vector3(); virtualObject.getWorldPosition(objectWorldPosition); // 2. æ£ç¢ºãªã¹ã±ãžã¥ãŒãªã³ã°ã®ããã«AudioContextããçŸåšæå»ãååŸããŸãã const now = audioContext.currentTime; // 3. ãããŒã®äœçœ®ããªããžã§ã¯ãã®äœçœ®ã«åãããŠæŽæ°ããŸãã // ã¹ã ãŒãºãªé·ç§»ã®ããã«ã¯setValueAtTimeã®äœ¿çšãæšå¥šãããŸãã panner.positionX.setValueAtTime(objectWorldPosition.x, now); panner.positionY.setValueAtTime(objectWorldPosition.y, now); panner.positionZ.setValueAtTime(objectWorldPosition.z, now); // 4. 次ã®ãã¬ãŒã ããªã¯ãšã¹ãããŠã«ãŒããç¶ç¶ããŸãã requestAnimationFrame(renderLoop); }
ãããæ¯ãã¬ãŒã è¡ãããšã§ããªãŒãã£ãªãšã³ãžã³ã¯åžžã«ç©ºéåãåèšç®ãããµãŠã³ãã¯åãä»®æ³ãªããžã§ã¯ãã«å®å šã«åºå®ãããŠããããã«èãããŸãã
äœçœ®ãè¶ ããŠïŒé«åºŠãªç©ºéåæè¡
ãªã¹ããŒãšé³æºã®äœçœ®ãç¥ãã ãã§ã¯å§ãŸãã«ãããŸãããæ¬åœã«èª¬åŸåã®ãããªãŒãã£ãªãäœæããããã«ãWeb Audio APIã¯ä»ã®ããã€ãã®çŸå®äžçã®é³é¿çŸè±¡ãã·ãã¥ã¬ãŒãããŸãã
é éšäŒé颿° (HRTF)ïŒãªã¢ã«ãª3DãªãŒãã£ãªãžã®éµ
ããªãã®è³ã¯ãé³ãæ£é¢ã«ããã®ããåŸãã«ããã®ãããããšãäžã«ããã®ããã©ã®ããã«ããŠç¥ãã®ã§ããããïŒããã¯ã鳿³¢ãããªãã®é ãèŽäœããããŠå€è³ïŒè³ä»ïŒã®ç©ççãªåœ¢ç¶ã«ãã£ãŠåŸ®åŠã«å€åããããã§ãããããã®å€åâ埮å°ãªé å»¶ãåå°ãåšæ³¢æ°ã®æžè¡°âã¯ãé³ãæ¥ãæ¹åã«ãã£ãŠãŠããŒã¯ã§ãããã®è€éãªãã£ã«ã¿ãªã³ã°ã¯ãé éšäŒé颿° (HRTF)ãšããŠç¥ãããŠããŸãã
PannerNode
ã¯ãã®å¹æãã·ãã¥ã¬ãŒãã§ããŸãããããæå¹ã«ããã«ã¯ããã®panningModel
ããããã£ã`'HRTF'`ã«èšå®ããå¿
èŠããããŸããããã¯ãç¹ã«ããããã©ã³ã§ã®æ²¡å
¥åã§é«å質ãªç©ºéåã®ããã®ãŽãŒã«ãã¹ã¿ã³ããŒãã§ãã
panner.panningModel = 'HRTF';
ä»£æ¿æ¡ã§ãã`'equalpower'`ã¯ãã¹ãã¬ãªã¹ããŒã«ãŒã«é©ããåçŽãªå·Šå³ã®ãã³ãã³ã°ãæäŸããŸãããHRTFãæã€åçŽæ§ãååŸæ¹åã®åºå¥ãæ¬ ããŠããŸããWebXRã§ã¯ãäœçœ®ãªãŒãã£ãªã«ã¯ã»ãšãã©ã®å ŽåHRTFãæ£ããéžæã§ãã
è·é¢æžè¡°ïŒé³ãè·é¢ã«å¿ããŠã©ã®ããã«æžè¡°ããã
çŸå®äžçã§ã¯ãé³ã¯é ãããã«ã€ããŠéãã«ãªããŸããPannerNode
ã¯ããã®distanceModel
ããããã£ãšããã€ãã®é¢é£ãã©ã¡ãŒã¿ã§ãã®æ¯ãèããã¢ãã«åããŸãã
distanceModel
: ããã¯ãè·é¢ã«å¿ããŠé³ã®é³éãæžå°ãããããã«äœ¿çšãããã¢ã«ãŽãªãºã ãå®çŸ©ããŸããæãç©ççã«æ£ç¢ºãªã¢ãã«ã¯'inverse'
ïŒéäºä¹ã®æ³åã«åºã¥ãïŒã§ãããããèžè¡çãªå¶åŸ¡ã®ããã«'linear'
ã'exponential'
ã¢ãã«ãå©çšã§ããŸããrefDistance
: ããã¯ãé³ã®é³éã100%ã«ãªãåºæºè·é¢ïŒã¡ãŒãã«åäœïŒãèšå®ããŸãããã®è·é¢ããæåã§ã¯é³éã¯å¢å ããŸããããã®è·é¢ãéãããšãéžæãããã¢ãã«ã«åŸã£ãŠæžè¡°ãå§ããŸããããã©ã«ãã¯1ã§ããrolloffFactor
: ããã¯ãé³éãã©ãã ãéãæžå°ããããå¶åŸ¡ããŸããå€ãé«ãã»ã©ããªã¹ããŒãé¢ããã«ã€ããŠé³ã¯ããæ¥éã«æ¶ããŠãããŸããããã©ã«ãã¯1ã§ããmaxDistance
: ãã®è·é¢ãè¶ ãããšé³ã®é³éã¯ããä»¥äžæžè¡°ããªããªããŸããããã©ã«ãã¯10000ã§ãã
ãããã®ãã©ã¡ãŒã¿ã調æŽããããšã§ãé³ãè·é¢ã«å¿ããŠã©ã®ããã«æ¯ãèãããæ£ç¢ºã«å¶åŸ¡ã§ããŸããé ãã®é³¥ã¯é«ãrefDistance
ãšç·©ãããªrolloffFactor
ãæã€ãããããŸããããéããªããããã¯ãè¿ãã§ã®ã¿èãããããã«éåžžã«çãrefDistance
ãšæ¥ãªrolloffFactor
ãæã€ãããããŸããã
ãµãŠã³ãã³ãŒã³ïŒæåæ§ã®ãã鳿º
ãã¹ãŠã®é³ãå
šæ¹åã«åçã«æŸå°ãããããã§ã¯ãããŸããã話ããŠãã人ããã¬ãããŸãã¯ã¡ã¬ãã³ãèããŠã¿ãŠãã ããâé³ã¯çæ£é¢ãæã倧ãããåŽé¢ãèé¢ã¯éãã§ããPannerNode
ã¯ãµãŠã³ãã³ãŒã³ã¢ãã«ã§ãããã·ãã¥ã¬ãŒãã§ããŸãã
ããã䜿çšããã«ã¯ããŸãorientationX/Y/Z
ããããã£ã䜿çšããŠãããŒã®åããå®çŸ©ããå¿
èŠããããŸããããã¯ãé³ããåããŠãããæ¹åãæããã¯ãã«ã§ããæ¬¡ã«ãã³ãŒã³ã®åœ¢ç¶ãå®çŸ©ã§ããŸãïŒ
coneInnerAngle
: 鳿ºããåºããã³ãŒã³ã®è§åºŠïŒ0ãã360床ã®ç¯å²ïŒããã®ã³ãŒã³ã®å åŽã§ã¯ãé³éã¯æå€§ã§ãïŒã³ãŒã³èšå®ã®åœ±é¿ãåããŸããïŒãããã©ã«ãã¯360ïŒç¡æåæ§ïŒã§ããconeOuterAngle
: ãã倧ããªå€åŽã³ãŒã³ã®è§åºŠãå åŽãšå€åŽã®ã³ãŒã³ã®éã§ãé³éã¯éåžžã¬ãã«ããconeOuterGain
ãžãšæ»ããã«ç§»è¡ããŸããããã©ã«ãã¯360ã§ããconeOuterGain
: ãªã¹ããŒãconeOuterAngle
ã®å€åŽã«ãããšãã«é³ã«é©çšãããé³é乿°ãå€ã0ã®å Žåã¯ç¡é³ãã0.5ã®å Žåã¯ååã®é³éãæå³ããŸããããã©ã«ãã¯0ã§ãã
ããã¯ä¿¡ããããªãã»ã©åŒ·åãªããŒã«ã§ããä»®æ³ãã¬ãã®é³ãã¹ããŒã«ãŒãããªã¢ã«ã«çºããããã«ãããããã£ã©ã¯ã¿ãŒã®å£°ãåããŠããæ¹åã«æåœ±ããããããŠãã·ãŒã³ã«ãããªãåçãªãªã¢ãªãºã ã®å±€ã远å ã§ããŸãã
WebXRãšã®çµ±åïŒãã¹ãŠããŸãšãã
ããŠããŠãŒã¶ãŒã®é ã®å§¿å¢ãæäŸããWebXR Device APIãšããã®æ å ±ãå¿ èŠãšããWeb Audio APIã®ãªã¹ããŒãšã®éã®ç¹ãšç¹ãçµã³ã€ããŸãããã
WebXR Device APIãšã¬ã³ããŒãŒã«ãŒã
WebXRã»ãã·ã§ã³ãéå§ãããšãç¹å¥ãª`requestAnimationFrame`ã³ãŒã«ããã¯ã«ã¢ã¯ã»ã¹ã§ããŸãããã®é¢æ°ã¯ãããã»ããã®ãã£ã¹ãã¬ã€ãªãã¬ãã·ã¥ã¬ãŒããšåæããŠãããæ¯ãã¬ãŒã ã`timestamp`ãš`xrFrame`ãªããžã§ã¯ãã®2ã€ã®åŒæ°ãåãåããŸãã
`xrFrame`ãªããžã§ã¯ãã¯ããŠãŒã¶ãŒã®äœçœ®ãšåãã«é¢ããä¿¡é Œã§ããæ å ±æºã§ãã`xrFrame.getViewerPose(referenceSpace)`ãåŒã³åºãããšã§`XRViewerPose`ãªããžã§ã¯ããååŸã§ããããã«ã¯ç§ãã¡ã®`AudioListener`ãæŽæ°ããããã«å¿ èŠãªæ å ±ãå«ãŸããŠããŸãã
XR Poseãã`AudioListener`ãæŽæ°ãã
`XRViewerPose`ãªããžã§ã¯ãã«ã¯`transform`ããããã£ãå«ãŸããŠãããããã¯`XRRigidTransform`ã§ãããã®ãã©ã³ã¹ãã©ãŒã ã¯ãä»®æ³äžçã«ããããŠãŒã¶ãŒã®é ã®äœçœ®ãšåãã®äž¡æ¹ãä¿æããŠããŸãã以äžã¯ãæ¯ãã¬ãŒã ãããã䜿çšããŠãªã¹ããŒãæŽæ°ããæ¹æ³ã§ãã
// 泚ïŒãã®äŸã¯ã'audioContext'ãš'referenceSpace'ãååšããåºæ¬çãªã»ããã¢ãããåæãšããŠããŸãã // çã®æ°åŠã§è¡ããšåé·ã«ãªããããæç¢ºã«ããããã«THREE.jsã®ãããªã©ã€ãã©ãªããã¯ãã«/ã¯ã©ãŒã¿ããªã³ã®èšç®ã«äœ¿çšããããšãå€ãã§ãã function onXRFrame(time, frame) { const session = frame.session; session.requestAnimationFrame(onXRFrame); const pose = frame.getViewerPose(referenceSpace); if (pose) { // ãã¥ãŒã¢ã®ããŒãºãããã©ã³ã¹ãã©ãŒã ãååŸ const transform = pose.transform; const position = transform.position; const orientation = transform.orientation; // ããã¯ã¯ã©ãŒã¿ããªã³ã§ã const listener = audioContext.listener; const now = audioContext.currentTime; // 1. ãªã¹ããŒã®äœçœ®ãæŽæ° // äœçœ®ã¯DOMPointReadOnlyãšããŠçŽæ¥å©çšå¯èœã§ãïŒx, y, zããããã£ãæã€ïŒ listener.positionX.setValueAtTime(position.x, now); listener.positionY.setValueAtTime(position.y, now); listener.positionZ.setValueAtTime(position.z, now); // 2. ãªã¹ããŒã®åããæŽæ° // ã¯ã©ãŒã¿ããªã³ã®åããã'forward'ãš'up'ãã¯ãã«ãå°åºããå¿ èŠããããŸãã // 3Dæ°åŠã©ã€ãã©ãªã䜿çšããã®ãæãç°¡åãªæ¹æ³ã§ãã // åæ¹ãã¯ãã«ïŒ0, 0, -1ïŒãäœæãããããã»ããã®åãã§å転ãããŸãã const forwardVector = new THREE.Vector3(0, 0, -1); forwardVector.applyQuaternion(new THREE.Quaternion(orientation.x, orientation.y, orientation.z, orientation.w)); // äžæ¹ãã¯ãã«ïŒ0, 1, 0ïŒãäœæããåãåãã§å転ãããŸãã const upVector = new THREE.Vector3(0, 1, 0); upVector.applyQuaternion(new THREE.Quaternion(orientation.x, orientation.y, orientation.z, orientation.w)); // ãªã¹ããŒã®åããã¯ãã«ãèšå®ããŸãã listener.forwardX.setValueAtTime(forwardVector.x, now); listener.forwardY.setValueAtTime(forwardVector.y, now); listener.forwardZ.setValueAtTime(forwardVector.z, now); listener.upX.setValueAtTime(upVector.x, now); listener.upY.setValueAtTime(upVector.y, now); listener.upZ.setValueAtTime(upVector.z, now); } // ... æ®ãã®ã¬ã³ããªã³ã°ã³ãŒã ... }
ãã®ã³ãŒããããã¯ã¯ããŠãŒã¶ãŒã®ç©ççãªé ã®åããšä»®æ³ãªãŒãã£ãªãšã³ãžã³ãšã®éã®æ¬è³ªçãªãªã³ã¯ã§ãããããå®è¡ããããšããŠãŒã¶ãŒãé ãåãã«ã€ããŠã3DãµãŠã³ãã¹ã±ãŒãå šäœãçŸå®äžçãšåãããã«å®å®ããŠæ£ããä¿ãããŸãã
ããã©ãŒãã³ã¹ã«é¢ããèæ ®äºé ãšãã¹ããã©ã¯ãã£ã¹
è±ããªç©ºéãªãŒãã£ãªäœéšãå®è£ ããã«ã¯ãã¹ã ãŒãºã§é«æ§èœãªã¢ããªã±ãŒã·ã§ã³ã確ä¿ããããã«ãªãœãŒã¹ãæ éã«ç®¡çããå¿ èŠããããŸãã
ãªãŒãã£ãªã¢ã»ããã®ç®¡ç
ãªãŒãã£ãªã®èªã¿èŸŒã¿ãšãã³ãŒãã¯ãªãœãŒã¹ã倧éã«æ¶è²»ããå¯èœæ§ããããŸããXRäœéšãå§ãŸãåã«ãå¿ ããªãŒãã£ãªã¢ã»ãããããªããŒãããŠãã³ãŒãããŠãã ãããããŠã³ããŒãæéãšã¡ã¢ãªäœ¿çšéãåæžããããã«ãéå§çž®ã®WAVãã¡ã€ã«ã§ã¯ãªããOpusãAACã®ãããªçŸä»£çã§å§çž®ããããªãŒãã£ãªãã©ãŒãããã䜿çšããŠãã ããã`fetch` APIãš`audioContext.decodeAudioData`ãçµã¿åãããã®ããããã«å¯Ÿããæšæºçã§çŸä»£çãªã¢ãããŒãã§ãã
空éåã®ã³ã¹ã
HRTFããŒã¹ã®ç©ºéåã¯åŒ·åã§ããã`PannerNode`ã®æãèšç®ã³ã¹ããé«ãéšåã§ããã·ãŒã³å ã®ãã¹ãŠã®ãµãŠã³ãã空éåããå¿ èŠã¯ãããŸããããªãŒãã£ãªæŠç¥ãç«ãŠãŸãããïŒ
- HRTFä»ãã®`PannerNode`ã䜿çšããå ŽåïŒ ã²ãŒã ãã¬ã€ãæ²¡å ¥æã«ãšã£ãŠäœçœ®ãéèŠãªäž»èŠãªé³æºïŒäŸïŒãã£ã©ã¯ã¿ãŒãã€ã³ã¿ã©ã¯ãã£ããªãªããžã§ã¯ããéèŠãªãµãŠã³ããã¥ãŒïŒã
- åçŽãªã¹ãã¬ãªãŸãã¯ã¢ãã©ã«ã䜿çšããå ŽåïŒ ãŠãŒã¶ãŒã€ã³ã¿ãŒãã§ãŒã¹ã®ãã£ãŒãããã¯ãBGMããŸãã¯ç¹å®ã®çºçæºãæããªãç°å¢é³ã®ãããªéãã€ãžã§ãã£ãã¯ãµãŠã³ãããããã¯`PannerNode`ã®ä»£ããã«åçŽãª`GainNode`ãéããŠåçã§ããŸãã
ã¬ã³ããŒã«ãŒãã§ã®æŽæ°ã®æé©å
äœçœ®ã®ãããªãªãŒãã£ãªãã©ã¡ãŒã¿ã®`.value`ããããã£ãçŽæ¥èšå®ãã代ããã«ãåžžã«`setValueAtTime()`ãŸãã¯ãã®ä»ã®ã¹ã±ãžã¥ãŒã«ããããã©ã¡ãŒã¿å€æŽïŒ`linearRampToValueAtTime`ãªã©ïŒã䜿çšããŠãã ãããçŽæ¥èšå®ãããšãå¯èŽãªã¯ãªãã¯é³ããããé³ãçºçããå¯èœæ§ããããŸãããã¹ã±ãžã¥ãŒã«ããã倿Žã¯ã¹ã ãŒãºã§ãµã³ãã«ç²ŸåºŠã®é«ãé·ç§»ãä¿èšŒããŸãã
éåžžã«é ãã«ããé³ã«ã€ããŠã¯ããã®äœçœ®æŽæ°ãéåŒãããšãæ€èšãããããããŸããã100ã¡ãŒãã«é¢ããé³ã®äœçœ®ã¯ãããããæ¯ç§90åæŽæ°ããå¿ èŠã¯ãããŸããã5ãã¬ãŒã ããšãŸãã¯10ãã¬ãŒã ããšã«æŽæ°ããããšã§ãã¡ã€ã³ã¹ã¬ããã§ã®CPUæéããããã«ç¯çŽã§ããŸãã
ã¬ããŒãžã³ã¬ã¯ã·ã§ã³ãšãªãœãŒã¹ç®¡ç
`AudioContext`ãšãã®ããŒãã¯ãæ¥ç¶ãããŠå®è¡ãããŠããéãããã©ãŠã¶ã«ãã£ãŠèªåçã«ã¬ããŒãžã³ã¬ã¯ã·ã§ã³ãããŸããããµãŠã³ãã®åçãçµäºãããããªããžã§ã¯ããã·ãŒã³ããåé€ããããããå Žåã¯ããœãŒã¹ããŒããæç€ºçã«åæ¢ãïŒ`source.stop()`ïŒãæ¥ç¶ãè§£é€ããŠãã ããïŒ`source.disconnect()`ïŒãããã«ããããã©ãŠã¶ãåå©çšããããã®ãªãœãŒã¹ãè§£æŸãããé·æéå®è¡ãããã¢ããªã±ãŒã·ã§ã³ã§ã®ã¡ã¢ãªãªãŒã¯ãé²ããŸãã
WebXRãªãŒãã£ãªã®æªæ¥
çŸåšã®Web Audio APIã¯å ç¢ãªåºç€ãæäŸããŠããŸããããªã¢ã«ã¿ã€ã ãªãŒãã£ãªã®äžçã¯çµ¶ããé²åããŠããŸããæªæ¥ã¯ããããªããªã¢ãªãºã ãšããç°¡åãªå®è£ ãçŽæããŠããŸãã
ãªã¢ã«ã¿ã€ã ç°å¢ãšãã§ã¯ãïŒãªããŒããšãªã¯ã«ãŒãžã§ã³
次ã®ããã³ãã£ã¢ã¯ãé³ãç°å¢ãšã©ã®ããã«çžäºäœçšããããã·ãã¥ã¬ãŒãããããšã§ããããã«ã¯ä»¥äžãå«ãŸããŸãïŒ
- ãªããŒãïŒæ®é¿ïŒïŒ 空éå ã§ã®é³ã®åé¿ãåå°ãã·ãã¥ã¬ãŒãããŸãã倧ããªå€§èå ã§ã®é³ã¯ãã«ãŒããããæ·ãããå°ããªéšå±ã§ã®é³ãšã¯ç°ãªãã¯ãã§ãã`ConvolverNode`ã䜿çšããŠã€ã³ãã«ã¹å¿çãçšããŠãªããŒããé©çšã§ããŸãããåçãªãªã¢ã«ã¿ã€ã ç°å¢ã¢ããªã³ã°ã¯æŽ»çºãªç ç©¶åéã§ãã
- ãªã¯ã«ãŒãžã§ã³ãšãªãã¹ãã©ã¯ã·ã§ã³ïŒ é³ãåºäœãªããžã§ã¯ããééãããšãã«ã©ã®ããã«ããããïŒãªã¯ã«ãŒãžã§ã³ïŒããŸãã¯ãã®åšããåã蟌ããšãã«ã©ã®ããã«æ²ãããïŒãªãã¹ãã©ã¯ã·ã§ã³ïŒãã·ãã¥ã¬ãŒãããŸããããã¯ãæšæºåå£äœãã©ã€ãã©ãªäœæè ããŠã§ãã§ããã©ãŒãã³ã¹è¯ã解決ããããšåãçµãã§ããè€éãªèšç®åé¡ã§ãã
æé·ãããšã³ã·ã¹ãã
`PannerNode`ãæåã§ç®¡çããäœçœ®ãæŽæ°ããã®ã¯è€éã«ãªãããšããããŸãã幞ããªããšã«ãWebXRããŒã«ã®ãšã³ã·ã¹ãã ã¯æçãã€ã€ãããŸããTHREE.jsïŒãã®`PositionalAudio`ãã«ããŒãšå ±ã«ïŒãBabylon.jsããããŠA-Frameã®ãããªå®£èšçãªãã¬ãŒã ã¯ãŒã¯ãªã©ã®äž»èŠãª3Dãã¬ãŒã ã¯ãŒã¯ã¯ãåºç€ãšãªãWeb Audio APIããã¯ãã«æŒç®ã®å€ããåŠçãããããé«ã¬ãã«ã®æœè±¡åãæäŸããŸãããããã®ããŒã«ã掻çšããããšã§ãéçºãå€§å¹ ã«å éããå®åçãªã³ãŒããåæžã§ããŸãã
çµè«ïŒãµãŠã³ãã§ä¿¡ææ§ã®ããäžçãåµé ãã
空éãªãŒãã£ãªã¯WebXRã«ãããèŽ æ²¢ãªæ©èœã§ã¯ãªããæ²¡å ¥æã®åºæ¬çãªæ±ã§ããWeb Audio APIã®åãçè§£ãæŽ»çšããããšã§ãéãã§ç¡èã®3Dã·ãŒã³ããç¡æèã®ã¬ãã«ã§ãŠãŒã¶ãŒãé äºãçŽåŸããããçããŠåŒåžããäžçã«å€ããããšãã§ããŸãã
ç§ãã¡ã¯3DãµãŠã³ãã®åºæ¬æŠå¿µããããããå®çŸããããã«å¿ èŠãªç¹å®ã®èšç®ãAPIåŒã³åºããŸã§æ ãããŠããŸããã`PannerNode`ãä»®æ³é³æºãšããŠæ©èœãã`AudioListener`ããŠãŒã¶ãŒã®è³ã衚ããWebXR Device APIãããããçµã³ã€ããããã®éèŠãªãã©ããã³ã°ããŒã¿ãæäŸããæ¹æ³ãèŠãŠããŸããããããã®ããŒã«ããã¹ã¿ãŒããããã©ãŒãã³ã¹ãšãã¶ã€ã³ã®ãã¹ããã©ã¯ãã£ã¹ãé©çšããããšã§ãããªãã¯æ¬¡äžä»£ã®ã€ããŒã·ããªãŠã§ãäœéšâãã èŠãããã ãã§ãªããçã«èãããäœéšâãæ§ç¯ããæºåãã§ããŠããŸãã