/* Copyright (C) 2010 Copyright 2010 Google Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package com.googlecode.gwtquake.client; import com.google.gwt.core.client.JavaScriptObject; import com.googlecode.gwtquake.shared.sound.ALAdapter; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.HashMap; import java.util.Map; /** * Absolutely minimal implementation of AL API using HTML5 audio. */ public class WebALAdapter extends ALAdapter { private float alListenerGain; private float alListenerX; private float alListenerY; private float alListenerZ; private int alDistanceModel; static class HTML5AudioElement extends JavaScriptObject { public static native HTML5AudioElement create(String name) /*-{ var x = new $wnd.Audio(name); x.setAttribute("preload", "auto"); return x; }-*/; protected HTML5AudioElement() { } public final native HTML5AudioElement play() /*-{ if(this.currentTime > 0) { if(this.seekable && this.seekable.length && this.seekable.start) { this.currentTime = 0; return this; } else { var x = new $wnd.Audio(this.src); x.setAttribute("preload", "auto"); x.play(); return x; } } this.play(); return this; }-*/; public final native void stop() /*-{ this.pause(); }-*/; public final native void setVolume(float gain) /*-{ this.volume = gain; }-*/; public final native boolean ended() /*-{ return this.ended; }-*/; public final native boolean paused() /*-{ return this.paused; }-*/; } class SourceData { public float alGain = 1.0f; public float alPitch = 1.0f; public boolean alLooping = false; public float alRefDist = 1.0f; public float alMinGain = 0.0f; public float alMaxGain = 1.0f; public BufferData buffer; public boolean alSrcRelative = false; public boolean alSrcAbsolute; public float alRolloffFactor = 1.0f; public boolean started = false; public float sourceX; public float sourceY; public float sourceZ; private float alMaxDist = Float.MAX_VALUE; public void updateState() { if (buffer != null && buffer.element != null) { if (alDistanceModel != ALAdapter.AL_INVERSE_DISTANCE_CLAMPED) { buffer.element.setVolume(alListenerGain * alGain); } else { float gain = alRefDist / (alRefDist * alRolloffFactor * (Math.min(Math.max(distance(), alRefDist), alMaxDist) - alRefDist)); buffer.element.setVolume(alListenerGain * Math.max(alMinGain, Math.min(alGain * gain, alMaxGain))); } } } private float distance() { float dx = sourceX - alListenerX; float dy = sourceY - alListenerY; float dz = sourceZ - alListenerZ; return (float) Math.sqrt(dx * dx + dy * dy + dz * dz); } } static class BufferData { Buffer data; int freq; int format; String location; private HTML5AudioElement element; BufferData(String location) { this.location = location; element = HTML5AudioElement.create(location); } } int nextBufferId = 1; int nextSourceId = 1; private Map<Integer, BufferData> bufferData = new HashMap<Integer, BufferData>(); private Map<Integer, SourceData> sourceData = new HashMap<Integer, SourceData>(); @Override public void alBufferData(int buffer, String soundUrl) { bufferData.put(buffer, new BufferData(soundUrl)); } @Override public void alBufferData(int buffer, int format, ByteBuffer data, int freq) { // TODO(cromwellian) } @Override public void alBufferData(int buffer, int format, IntBuffer data, int freq) { // TODO(cromwellian) } @Override public void alBufferData(int buffer, int format, ShortBuffer data, int freq) { // TODO(cromwellian) } @Override public void alDeleteBuffers(IntBuffer buffers) { for (int bid : buffers.array()) { bufferData.remove(bid); } } @Override public void alDeleteSources(IntBuffer sources) { // TODO(cromwellian) } @Override public void alDisable(int capability) { // TODO(cromwellian) } @Override public void alDistanceModel(int value) { alDistanceModel = value; } @Override public void alDopplerFactor(float value) { // TODO(cromwellian) } @Override public void alDopplerVelocity(float value) { // TODO(cromwellian) } @Override public void alEnable(int capability) { // TODO(cromwellian) } @Override public void alGenBuffers(IntBuffer buffers) { int len = buffers.capacity(); for (int i = 0; i < buffers.capacity(); i++) { buffers.put(nextBufferId++); } } @Override public void alGenSources(IntBuffer sources) { int len = sources.capacity(); for (int i = 0; i < sources.capacity(); i++) { int sid = nextSourceId++; sources.put(sid); sourceData.put(sid, new SourceData()); } } @Override public boolean alGetBoolean(int pname) { return false; // TODO(cromwellian) } @Override public float alGetBufferf(int buffer, int pname) { return 0; // TODO(cromwellian) } @Override public int alGetBufferi(int buffer, int pname) { return 0; // TODO(cromwellian) } @Override public double alGetDouble(int pname) { return 0; // TODO(cromwellian) } @Override public void alGetDouble(int pname, DoubleBuffer data) { // TODO(cromwellian) } @Override public int alGetEnumValue(String ename) { return 0; // TODO(cromwellian) } @Override public int alGetError() { return AL_NO_ERROR; } @Override public float alGetFloat(int pname) { return 0; // TODO(cromwellian) } @Override public void alGetFloat(int pname, FloatBuffer data) { // TODO(cromwellian) } @Override public int alGetInteger(int pname) { return 0; // TODO(cromwellian) } @Override public void alGetInteger(int pname, IntBuffer data) { // TODO(cromwellian) } @Override public void alGetListener(int pname, FloatBuffer floatdata) { // TODO(cromwellian) } @Override public float alGetListenerf(int pname) { return 0; // TODO(cromwellian) } @Override public int alGetListeneri(int pname) { return 0; // TODO(cromwellian) } @Override public void alGetSource(int source, int pname, FloatBuffer floatdata) { // TODO(cromwellian) } @Override public float alGetSourcef(int source, int pname) { return 0; // TODO(cromwellian) } @Override public int alGetSourcei(int source, int pname) { SourceData sd = sourceData.get(source); if (sd != null) { switch (pname) { case AL_SOURCE_STATE: if (sd.started) { if (sd.buffer != null) { if (!sd.buffer.element.ended() && !sd.buffer.element.paused()) { return AL_PLAYING; } } } } } else { return 0; } return 0; } @Override public String alGetString(int i) { return null; // TODO(cromwellian) } @Override public boolean alIsBuffer(int buffer) { return false; // TODO(cromwellian) } @Override public boolean alIsEnabled(int capability) { return false; // TODO(cromwellian) } @Override public boolean alIsExtensionPresent(String fname) { return false; // TODO(cromwellian) } @Override public boolean alIsSource(int id) { return false; // TODO(cromwellian) } @Override public void alListener(int pname, FloatBuffer value) { // TODO(cromwellian) } @Override public void alListener3f(int pname, float v1, float v2, float v3) { switch (pname) { case AL_POSITION: alListenerX = v1; alListenerY = v2; alListenerZ = v3; break; default: } } @Override public void alListenerf(int pname, float value) { switch (pname) { case AL_GAIN: alListenerGain = value; break; default: } } @Override public void alListeneri(int pname, int value) { // TODO(cromwellian) } @Override public void alSource(int source, int pname, FloatBuffer value) { SourceData sd = sourceData.get(source); if (sd != null) { switch (pname) { case AL_POSITION: sd.sourceX = value.get(0); sd.sourceY = value.get(1); sd.sourceZ = value.get(2); sd.updateState(); break; } } } @Override public void alSource3f(int source, int pname, float v1, float v2, float v3) { // TODO(cromwellian) } @Override public void alSourcef(int source, int pname, float value) { SourceData sd = sourceData.get(source); if (sd != null) { switch (pname) { case AL_GAIN: sd.alGain = value; break; case AL_PITCH: sd.alPitch = value; break; case AL_LOOPING: sd.alLooping = value == AL_TRUE; break; case AL_REFERENCE_DISTANCE: sd.alRefDist = value; break; case AL_MIN_GAIN: sd.alMinGain = value; break; case AL_MAX_GAIN: sd.alMaxGain = value; break; case AL_ROLLOFF_FACTOR: sd.alRolloffFactor = value; break; } sd.updateState(); } } @Override public void alSourcei(int source, int pname, int value) { SourceData sd = sourceData.get(source); if (sd != null) { switch (pname) { case AL_LOOPING: sd.alLooping = value == AL_TRUE; break; case AL_BUFFER: sd.buffer = bufferData.get(value); break; case AL_SOURCE_ABSOLUTE: sd.alSrcAbsolute = value == AL_TRUE; break; case AL_SOURCE_RELATIVE: sd.alSrcRelative = value == AL_TRUE; break; } } } @Override public void alSourcePause(IntBuffer sources) { // TODO(cromwellian) } @Override public void alSourcePause(int source) { // TODO(cromwellian) } @Override public void alSourcePlay(IntBuffer sources) { for (int i = sources.position(); i < sources.limit(); i++) { alSourcePlay(sources.get(i)); } } @Override public void alSourcePlay(int source) { SourceData sd = sourceData.get(source); if (sd != null) { if (sd.buffer != null && sd.buffer.element != null) { sd.buffer.element = sd.buffer.element.play(); } } } @Override public void alSourceQueueBuffers(int source, IntBuffer buffers) { // TODO(cromwellian) } @Override public void alSourceRewind(IntBuffer sources) { // TODO(cromwellian) } @Override public void alSourceRewind(int source) { // TODO(cromwellian) } @Override public void alSourceStop(IntBuffer sources) { for (int i = sources.position(); i < sources.limit(); i++) { alSourceStop(sources.get(i)); } } @Override public void alSourceStop(int source) { SourceData sd = sourceData.get(source); if (sd != null) { if (sd.buffer != null && sd.buffer.element != null) { sd.buffer.element.stop(); } } } @Override public void alSourceUnqueueBuffers(int source, IntBuffer buffers) { // TODO(cromwellian) } @Override public void create(String deviceArguments, int contextFrequency, int contextRefresh, boolean contextSynchronized) { // TODO(cromwellian) } @Override public void create(String deviceArguments, int contextFrequency, int contextRefresh, boolean contextSynchronized, boolean openDevice) { // TODO(cromwellian) } }