Browse Source

Migrate to WebAudio API

Thomas Dy 1 year ago
parent
commit
f7b7d8f9c8
7 changed files with 132 additions and 5779 deletions
  1. 1 1
      index.html
  2. 84 0
      scripts/audio.js
  3. 14 25
      scripts/game.js
  4. 33 16
      scripts/games/safari.js
  5. 0 5724
      scripts/soundmanager2.js
  6. 0 13
      scripts/util.js
  7. BIN
      swf/soundmanager2_flash9_debug.swf

+ 1 - 1
index.html

@@ -32,7 +32,7 @@
 </div>
 <script type="text/javascript" src="scripts/q.js"></script>
 <script type="text/javascript" src="scripts/state-machine.js"></script>
-<script type="text/javascript" src="scripts/soundmanager2.js"></script>
+<script type="text/javascript" src="scripts/audio.js"></script>
 <script type="text/javascript" src="scripts/util.js"></script>
 <script type="text/javascript" src="scripts/game.js"></script>
 <script type="text/javascript" src="scripts/games/safari.js"></script>

+ 84 - 0
scripts/audio.js

@@ -0,0 +1,84 @@
+var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
+
+function loadSound(url) {
+  return fetch(url)
+    .then(response => response.arrayBuffer())
+    .then(buffer => audioCtx.decodeAudioData(buffer))
+    .then(audio => new Sound(audio, url));
+}
+
+function SFXManager() {
+  this.soundMap = {}
+}
+
+SFXManager.prototype.loadSound = function(name, url) {
+  var self = this;
+  return loadSound(url).then(sound => {
+    self.soundMap[name] = sound;
+  })
+}
+
+SFXManager.prototype.play = function(name) {
+  this.soundMap[name].play();
+}
+
+SFXManager.prototype.unloadSound = function(name) {
+  delete this.soundMap[name];
+}
+
+function Sound(buffer, url) {
+  this.buffer = buffer;
+  this.url = url;
+  this._target = audioCtx.destination;
+  this._source = null;
+  this._playStartTime = 0;
+  this._timeToStartFrom = 0;
+  this._playing = false;
+}
+
+Sound.prototype.setTarget = function(target) {
+  this._target = target;
+  if(this._playing) {
+    this.pause();
+    this.resume();
+  }
+}
+
+Sound.prototype._start = function(offset) {
+  this._source = audioCtx.createBufferSource();
+  this._source.buffer = this.buffer;
+  this._source.connect(this._target);
+  this._source.onended = function() {
+    this._playing = false;
+    this._timeToStartFrom = 0;
+  }.bind(this);
+  this._playStartTime = audioCtx.currentTime;
+  this._timeToStartFrom = offset;
+  this._playing = true;
+  this._source.start(0, this._timeToStartFrom);
+}
+
+Sound.prototype.play = function() {
+  if(this._playing) {
+    this.pause();
+  }
+  this._start(0);
+}
+
+Sound.prototype.resume = function() {
+  this._start(this._timeToStartFrom);
+}
+
+Sound.prototype.pause = function() {
+  // in case the sound has not been played yet
+  if(this._source == null) return;
+
+  this._playing = false;
+  this._source.onended = null;
+  this._source.stop();
+  this._source.disconnect();
+  var elapsedTime = this._timeToStartFrom + audioCtx.currentTime - this._playStartTime;
+  this._timeToStartFrom = elapsedTime;
+}
+
+var sfxManager = new SFXManager();

+ 14 - 25
scripts/game.js

@@ -16,15 +16,6 @@ WebFontConfig = {
   s.parentNode.insertBefore(wf, s);
 })();
 
-var soundDeferred = Q.defer();
-soundManager.setup({
-  url: 'swf/',
-  flashVersion: 9,
-  onready: function() {
-    soundDeferred.resolve(this);
-  }
-});
-
 var Game = {
   canvas: document.getElementById('game'),
   context: document.getElementById('game').getContext('2d'),
@@ -46,18 +37,16 @@ var Game = {
       delete Game.keysDown[e.keyCode];
     }, false);
 
-    var fontPromise = fontDeferred.promise.timeout(5000);
-    var soundPromise = soundDeferred.promise.then(function() {
-      return [
-        createSound('confirm', 'sound/confirm.mp3'),
-        createSound('back', 'sound/back.mp3'),
-        createSound('pause', 'sound/pause.mp3'),
-        createSound('cursor', 'sound/cursor.mp3')
-      ]
-    });
+    var resourcePromises = [
+      fontDeferred.promise.timeout(5000),
+      sfxManager.loadSound('confirm', 'sound/confirm.mp3'),
+      sfxManager.loadSound('back', 'sound/back.mp3'),
+      sfxManager.loadSound('pause', 'sound/pause.mp3'),
+      sfxManager.loadSound('cursor', 'sound/cursor.mp3')
+    ];
 
     Game.sceneManager.push(SplashScreen);
-    Q.allResolved([fontPromise, soundPromise]).done(function() {
+    Q.allResolved(resourcePromises).done(function() {
       Game.sceneManager.pop();
       Game.sceneManager.push(MainMenu);
     });
@@ -242,16 +231,16 @@ var MainMenu = new Scene();
         document.title = alternate ? 'SoundVoyager' : 'audventure';
         break;
       case 38:
-        soundManager.play('cursor');
+        sfxManager.play('cursor');
         selected = (selected + games.length -1) % games.length;
         break;
       case 40:
-        soundManager.play('cursor');
+        sfxManager.play('cursor');
         selected = (selected + 1) % games.length;
         break;
       case 32:
       case 13:
-        soundManager.play('confirm');
+        sfxManager.play('confirm');
         games[selected].start();
     }
   }
@@ -294,13 +283,13 @@ var PauseScreen = new Scene();
       case 113:
       case 27:
         Game.sceneManager.pop(2);
-        soundManager.play('back');
+        sfxManager.play('back');
         break;
       case 80:
       case 112:
       case 32:
         Game.sceneManager.pop(1, 0);
-        soundManager.play('back');
+        sfxManager.play('back');
     }
   };
   PauseScreen.load = PauseScreen.resume = function() {
@@ -324,4 +313,4 @@ var PauseScreen = new Scene();
   };
 })();
 
-Game.start();
+Game.start();

+ 33 - 16
scripts/games/safari.js

@@ -47,7 +47,7 @@ function SoundSafari(beatInfo, endless) {
       },
       onpoint: function() {
         ++points;
-        soundManager.play('get');
+        sfxManager.play('get');
         if(!endless && points >= beats.length) {
           state.win();
         }
@@ -62,6 +62,25 @@ function SoundSafari(beatInfo, endless) {
   var beats = [];
   var currentBeats = [];
 
+  // Setup sound pipeline
+  var analyser = audioCtx.createAnalyser();
+  analyser.fftSize = 32;
+  var spectrum = new Uint8Array(analyser.frequencyBinCount);
+
+  var panner = audioCtx.createPanner();
+  panner.panningModel = 'HRTF';
+  panner.distanceModel = 'exponential';
+  panner.refDistance = 50;
+  panner.rolloffFactor = 1.2;
+  panner.setOrientation(0, 0, 1);
+  panner.coneInnerAngle = 60;
+  panner.coneOuterAngle = 60;
+  panner.coneOuterGain = 0.7;
+  audioCtx.listener.setOrientation(0,0,-1,0,1,0);
+
+  analyser.connect(panner);
+  panner.connect(audioCtx.destination);
+
   // Drawing functions
   {
     function drawLeafV(point, h, w) {
@@ -91,12 +110,12 @@ function SoundSafari(beatInfo, endless) {
       ctx.rect(
         margin - ctx.lineWidth,
         canvas.height - barHeight - ctx.lineWidth - margin,
-        activeTarget.spectrum.length * (barWidth+barSpacing) + 2 * ctx.lineWidth - barSpacing,
+        spectrum.length * (barWidth+barSpacing) + 2 * ctx.lineWidth - barSpacing,
         barHeight + 2 * ctx.lineWidth
       );
       ctx.stroke();
-      for(var i = 0; i < activeTarget.spectrum.length; ++i) {
-        var height = activeTarget.spectrum[i]*20;
+      for(var i = 0; i < spectrum.length; ++i) {
+        var height = spectrum[i] / 255 * 20;
         ctx.fillStyle = starColors[i];
         ctx.fillRect(i * (barWidth + barSpacing) + margin, canvas.height - height - margin, barWidth, height);
       }
@@ -223,7 +242,7 @@ function SoundSafari(beatInfo, endless) {
       }
 
       if(state.current == 'playing') {
-        var newAlpha = 1.5 * activeTarget.spectrum[this.type];
+        var newAlpha = spectrum[this.type] / 255 * 1.5;
         var a = 0.9;
         this.alpha = Math.min(1, activeTarget.shimmerFactor * (this.alpha * (1-a) + newAlpha * a));
       }
@@ -277,8 +296,7 @@ function SoundSafari(beatInfo, endless) {
 
       if(colliding(player, this)) {
         state.point();
-        this.beat.sound.setPan(0);
-        this.beat.sound.setVolume(100);
+        this.beat.sound.setTarget(audioCtx.destination);
         this.shouldDelete = true;
         return;
       }
@@ -299,7 +317,7 @@ function SoundSafari(beatInfo, endless) {
       else {
         angle = s * 100;
       }
-      this.beat.sound.setPan(Math.max(xDistAbs, angle) * s);
+      panner.setPosition(xDist, 0, yDist);
 
       var rDist = xDist*xDist + yDist*yDist;
       if(rDist >= 250000) {
@@ -307,14 +325,12 @@ function SoundSafari(beatInfo, endless) {
       }
       rDist = 250000 - rDist;
       rDist /= 2500;
-      this.beat.sound.setVolume(rDist);
 
       this.shimmerFactor = easeOutExpo(Math.abs(xDist), 1, -1, LEVEL_SIZE/2) * rDist / 100;
       if(isNaN(this.shimmerFactor) || this.shimmerFactor < 0) {
         this.shimmerFactor = 0;
       }
-      var pos = gameTime % this.beat.info.duration;
-      if(pos < this.beat.sound.duration) this.spectrum = this.beat.sound.getSpectrum(pos);
+      analyser.getByteFrequencyData(spectrum);
     };
   }
 
@@ -336,7 +352,9 @@ function SoundSafari(beatInfo, endless) {
   var activeTarget;
 
   function switchBeat(index) {
+    currentBeats.forEach(beat => beat.sound.setTarget(audioCtx.destination));
     var currentBeat = beats[index];
+    currentBeat.sound.setTarget(analyser);
     currentBeats.push(currentBeat);
     if(currentBeats.length > Math.min(4, beats.length)) {
       currentBeats.splice(0, 1);
@@ -472,7 +490,7 @@ function SoundSafari(beatInfo, endless) {
       case 112: // p
       case 32:  // space
         Game.pause();
-        soundManager.play('pause');
+        sfxManager.play('pause');
         break;
       case 68:  // D
         debug = !debug;
@@ -485,13 +503,13 @@ function SoundSafari(beatInfo, endless) {
     beats = [];
     var soundPromises = beatInfo.map(function(elem, index) {
       var beat = { info: elem, rounds: 0 };
-      var promise = createSound('beat'+index, elem.url).then(function(sound) {
+      var promise = loadSound(elem.url).then(function(sound) {
         beat.sound = sound;
       });
       beats.push(beat);
       return promise;
     });
-    soundPromises.push(createSound('get', 'sound/get.mp3'));
+    soundPromises.push(sfxManager.loadSound('get', 'sound/get.mp3'));
 
     this.textAlphaTween = this.tween(0)
       .wait(0.5)
@@ -511,8 +529,7 @@ function SoundSafari(beatInfo, endless) {
 
   this.unload = function() {
     removeEventListener('keypress', onKeyUp);
-    soundManager.destroySound('get');
-    beats.forEach(function(beat) {beat.sound.destruct()});
+    sfxManager.unloadSound('get');
   };
 
   this.pause = function() {

File diff suppressed because it is too large
+ 0 - 5724
scripts/soundmanager2.js


+ 0 - 13
scripts/util.js

@@ -1,16 +1,3 @@
-function createSound(name, url) {
-  var deferred = Q.defer();
-  soundManager.createSound({
-    id: name,
-    url: url,
-    autoLoad: true,
-    onload: function() {
-      deferred.resolve(this);
-    }
-  });
-  return deferred.promise;
-}
-
 function drawTriangle(x, y, h, w) {
   var ctx = Game.context;
   ctx.beginPath();

BIN
swf/soundmanager2_flash9_debug.swf