com.badlogic.gdx.backends.gwt.webaudio.WebAudioAPISound Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gdx-backend-gwt Show documentation
Show all versions of gdx-backend-gwt Show documentation
Android/Desktop/iOS/HTML5 game development framework.
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.backends.gwt.webaudio;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.IntMap.Keys;
import com.badlogic.gdx.utils.Pool;
import com.google.gwt.core.client.JavaScriptObject;
/** Implementation of the {@link Sound} interface for GWT, using the Web Audio API (
* https://www.w3.org/TR/webaudio/).
*
* @author barkholt */
public class WebAudioAPISound implements Sound {
// JavaScript object that is the base object of the Web Audio API
private final JavaScriptObject audioContext;
// JavaScript AudioNode representing the final destination of the sound. Typically the speakers of whatever device we are
// running on.
private final JavaScriptObject destinationNode;
// Maps from integer keys to active sounds. Both the AudioBufferSourceNode and the associated AudioControlGraph are stored for
// quick access
private final IntMap activeSounds;
private final IntMap activeAudioControlGraphs;
// The raw sound data of this sound, which will be fed into the audio nodes
private JavaScriptObject audioBuffer;
// Key generator for sound objects.
private int nextKey = 0;
// We use a pool of AudioControlGraphs in order to minimize object creation
private AudioControlGraphPool audioGraphPool;
/** @param audioContext The JavaScript AudioContext object that servers as the base object of the Web Audio API
* @param destinationNode The JavaScript AudioNode to route all the sound output to
* @param audioGraphPool A Pool that allows us to create AudioControlGraphs efficiently */
public WebAudioAPISound (JavaScriptObject audioContext, JavaScriptObject destinationNode,
AudioControlGraphPool audioGraphPool) {
this.audioContext = audioContext;
this.destinationNode = destinationNode;
this.audioGraphPool = audioGraphPool;
this.activeSounds = new IntMap();
this.activeAudioControlGraphs = new IntMap();
}
/** Set the buffer containing the actual sound data
* @param audioBuffer */
public void setAudioBuffer (JavaScriptObject audioBuffer) {
this.audioBuffer = audioBuffer;
// If play-back of sounds have been requested before we were ready, do a pause/resume to get sound flowing
Keys keys = activeSounds.keys();
while (keys.hasNext) {
int key = keys.next();
pause(key);
resume(key, 0f);
}
}
protected long play (float volume, float pitch, float pan, boolean loop) {
// if the sound system is not yet unlocked, skip playing the sound.
// otherwise, it is played when the user makes his first input
if (!WebAudioAPIManager.isSoundUnlocked() && WebAudioAPIManager.isAudioContextLocked(audioContext))
return -1;
// Get ourselves a fresh audio graph
AudioControlGraph audioControlGraph = audioGraphPool.obtain();
// Create the source node that will be feeding the audio graph
JavaScriptObject audioBufferSourceNode = createBufferSourceNode(loop, pitch);
// Configure the audio graph
audioControlGraph.setSource(audioBufferSourceNode);
audioControlGraph.setPan(pan);
audioControlGraph.setVolume(volume);
int myKey = nextKey++;
// Start the playback
playJSNI(audioBufferSourceNode, myKey, 0f);
// Remember that we are playing
activeSounds.put(myKey, audioBufferSourceNode);
activeAudioControlGraphs.put(myKey, audioControlGraph);
return myKey;
}
private void soundDone (int key) {
// The sound might have been removed by an explicit stop, before the sound reached its end
if (activeSounds.containsKey(key)) {
activeSounds.remove(key);
audioGraphPool.free(activeAudioControlGraphs.remove(key));
}
}
public native JavaScriptObject createBufferSourceNode (boolean loop, float pitch) /*-{
// Get the Java values here, for readability
var audioContext = [email protected]::audioContext;
var audioBuffer = [email protected]::audioBuffer;
if (audioBuffer == null) {
// If there is no audio buffer yet, we presume it is still loading, and instead play a short silent clip.
audioBuffer = audioContext.createBuffer(2, 22050, 44100);
}
// Setup the basic audio source
var source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.loop = loop;
// Pitch change can cause resampling, so only do it if necessary
if (pitch !== 1.0) {
source.playbackRate.value = pitch;
}
return source;
}-*/;
public native JavaScriptObject playJSNI (JavaScriptObject source, int key, float startOffset) /*-{
// Get the Java values here, for readability
var audioContext = [email protected]::audioContext;
// Remember when we started playing this. This is needed for pause/resume.
source.startTime = audioContext.currentTime;
var self = this;
// Listen for the end, in order to clean up
source.onended = function() {
[email protected]::soundDone(I)(key);
};
// First try the standards defined version of starting the sound
if (typeof (source.start) !== "undefined")
source.start(audioContext.currentTime, startOffset);
else
// Then try the older webkit (iOS) way
source.noteOn(audioContext.currentTime, startOffset);
return source;
}-*/;
public static native JavaScriptObject stopJSNI (JavaScriptObject audioBufferSourceNode) /*-{
// First try the standards defined version of stopping the sound
if (typeof (audioBufferSourceNode.stop) !== "undefined")
audioBufferSourceNode.stop();
else
// Then try the older webkit (iOS) way
audioBufferSourceNode.noteOff();
}-*/;
@Override
public long play () {
return play(1f);
}
@Override
public long play (float volume) {
return play(volume, 1f, 0f);
}
@Override
public long play (float volume, float pitch, float pan) {
return play(volume, pitch, pan, false);
}
@Override
public long loop () {
return loop(1f);
}
@Override
public long loop (float volume) {
return loop(volume, 1f, 0f);
}
@Override
public long loop (float volume, float pitch, float pan) {
return play(volume, pitch, pan, true);
}
@Override
public void stop () {
Keys keys = activeSounds.keys();
while (keys.hasNext) {
int next = keys.next();
stop(next);
}
}
@Override
public void pause () {
Keys keys = activeSounds.keys();
while (keys.hasNext) {
pause(keys.next());
}
}
@Override
public void resume () {
Keys keys = activeSounds.keys();
while (keys.hasNext) {
resume(keys.next());
}
}
@Override
public void dispose () {
stop();
activeSounds.clear();
}
@Override
public void stop (long soundId) {
int soundKey = (int)soundId;
if (activeSounds.containsKey(soundKey)) {
JavaScriptObject audioBufferSourceNode = activeSounds.remove(soundKey);
stopJSNI(audioBufferSourceNode);
audioGraphPool.free(activeAudioControlGraphs.remove(soundKey));
}
}
@Override
public void pause (long soundId) {
// Record our current position, and then stop
int soundKey = (int)soundId;
if (activeSounds.containsKey(soundKey)) {
JavaScriptObject audioBufferSourceNode = activeSounds.get(soundKey);
// The API has no concept of pause/resume, so we do it by recording a pause time stamp, and then stopping the sound. On
// resume we play the
// sound again, starting from a calculated offset.
pauseJSNI(audioBufferSourceNode);
stopJSNI(audioBufferSourceNode);
}
}
@Override
public void resume (long soundId) {
resume(soundId, null);
}
private void resume (long soundId, Float from) {
// Start from previous paused position
int soundKey = (int)soundId;
if (activeSounds.containsKey(soundKey)) {
JavaScriptObject audioBufferSourceNode = activeSounds.remove(soundKey);
AudioControlGraph audioControlGraph = activeAudioControlGraphs.get(soundKey);
boolean loop = getLoopingJSNI(audioBufferSourceNode);
float pitch = getPitchJSNI(audioBufferSourceNode);
float resumeOffset = getResumeOffsetJSNI(audioBufferSourceNode);
if (from != null) resumeOffset = from;
// These things can not be re-used. One play only, as dictated by the Web Audio API
JavaScriptObject newAudioBufferSourceNode = createBufferSourceNode(loop, pitch);
audioControlGraph.setSource(newAudioBufferSourceNode);
activeSounds.put(soundKey, newAudioBufferSourceNode);
playJSNI(newAudioBufferSourceNode, soundKey, resumeOffset);
}
}
@Override
public void setLooping (long soundId, boolean looping) {
int soundKey = (int)soundId;
if (activeSounds.containsKey(soundKey)) {
JavaScriptObject sound = activeSounds.get(soundKey);
setLoopingJSNI(sound, looping);
}
}
@Override
public void setPitch (long soundId, float pitch) {
int soundKey = (int)soundId;
if (activeSounds.containsKey(soundKey)) {
JavaScriptObject sound = activeSounds.get(soundKey);
setPitchJSNI(sound, pitch);
}
}
@Override
public void setVolume (long soundId, float volume) {
int soundKey = (int)soundId;
if (activeSounds.containsKey(soundKey)) {
AudioControlGraph audioControlGraph = activeAudioControlGraphs.get(soundKey);
audioControlGraph.setVolume(volume);
}
}
@Override
public void setPan (long soundId, float pan, float volume) {
int soundKey = (int)soundId;
if (activeSounds.containsKey(soundKey)) {
AudioControlGraph audioControlGraph = activeAudioControlGraphs.get(soundKey);
audioControlGraph.setPan(pan);
audioControlGraph.setVolume(volume);
}
}
public static native JavaScriptObject setPitchJSNI (JavaScriptObject audioBufferSourceNode, float pitch) /*-{
audioBufferSourceNode.playbackRate.value = pitch;
}-*/;
public static native float getPitchJSNI (JavaScriptObject audioBufferSourceNode) /*-{
return audioBufferSourceNode.playbackRate.value;
}-*/;
public static native JavaScriptObject setLoopingJSNI (JavaScriptObject audioBufferSourceNode, boolean looping) /*-{
audioBufferSourceNode.loop = looping;
}-*/;
public static native boolean getLoopingJSNI (JavaScriptObject audioBufferSourceNode) /*-{
return audioBufferSourceNode.loop;
}-*/;
public native JavaScriptObject pauseJSNI (JavaScriptObject audioBufferSourceNode) /*-{
var audioContext = [email protected]::audioContext;
audioBufferSourceNode.pauseTime = audioContext.currentTime;
}-*/;
public static native float getResumeOffsetJSNI (JavaScriptObject audioBufferSourceNode) /*-{
return audioBufferSourceNode.pauseTime
- audioBufferSourceNode.startTime;
}-*/;
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy