CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-badlogicgames-gdx--gdx-backend-gwt

GWT backend for libGDX enabling Java game development for web browsers through JavaScript compilation

Pending
Overview
Eval results
Files

webaudio.mddocs/

Web Audio API

The GWT backend provides advanced audio capabilities through Web Audio API integration, offering superior audio performance and features compared to basic HTML5 Audio. This system provides low-latency audio, advanced audio processing, and better mobile device support.

Core Web Audio Classes

WebAudioAPIManager { .api }

public class WebAudioAPIManager implements LifecycleListener {
    // Web Audio API availability and setup
    public static boolean isSupported();
    public static WebAudioAPIManager getInstance();
    
    // Audio context management
    public void createContext();
    public void resumeContext();
    public void suspendContext();
    public boolean isContextRunning();
    
    // Lifecycle management
    public void pause();
    public void resume();
    public void dispose();
    
    // Audio graph management
    public AudioControlGraph createAudioGraph();
    public void releaseAudioGraph(AudioControlGraph graph);
    
    // Volume and settings
    public void setMasterVolume(float volume);
    public float getMasterVolume();
}

WebAudioAPISound { .api }

public class WebAudioAPISound implements Sound {
    // Sound playback control
    public long play();
    public long play(float volume);
    public long play(float volume, float pitch, float pan);
    
    // Instance control
    public void stop();
    public void stop(long soundId);
    public void pause();
    public void pause(long soundId);
    public void resume();
    public void resume(long soundId);
    
    // Sound properties
    public void setVolume(long soundId, float volume);
    public void setPitch(long soundId, float pitch);
    public void setPan(long soundId, float pan, float volume);
    
    // Looping control
    public void setLooping(long soundId, boolean looping);
    
    // Resource management
    public void dispose();
}

WebAudioAPIMusic { .api }

public class WebAudioAPIMusic implements Music {
    // Playback control
    public void play();
    public void pause();
    public void stop();
    
    // State queries
    public boolean isPlaying();
    public boolean isLooping();
    
    // Music properties
    public void setLooping(boolean isLooping);
    public void setVolume(float volume);
    public float getVolume();
    public void setPan(float pan, float volume);
    
    // Position control
    public void setPosition(float position);
    public float getPosition();
    
    // Event handling
    public void setOnCompletionListener(OnCompletionListener listener);
    
    // Resource management
    public void dispose();
}

AudioControlGraph { .api }

public class AudioControlGraph {
    // Audio node management
    public void connect(AudioNode source, AudioNode destination);
    public void disconnect(AudioNode source);
    public void disconnect(AudioNode source, AudioNode destination);
    
    // Volume and gain control
    public void setVolume(float volume);
    public float getVolume();
    public void setMasterGain(float gain);
    
    // Audio effects
    public void applyLowPassFilter(float frequency);
    public void applyHighPassFilter(float frequency);
    public void applyReverb(float roomSize, float damping);
    public void applyDelay(float delayTime, float feedback);
    
    // Spatial audio
    public void setListenerPosition(float x, float y, float z);
    public void setListenerOrientation(float forwardX, float forwardY, float forwardZ, 
                                     float upX, float upY, float upZ);
    public void setSourcePosition(float x, float y, float z);
    
    // Audio analysis
    public float[] getFrequencyData();
    public float[] getTimeData();
    public float getAverageVolume();
    
    // Resource management
    public void dispose();
}

AudioControlGraphPool { .api }

public class AudioControlGraphPool extends Pool<AudioControlGraph> {
    // Object pooling for audio graphs
    public AudioControlGraphPool(int initialCapacity, int max);
    
    // Pool management
    protected AudioControlGraph newObject();
    protected void reset(AudioControlGraph object);
    
    // Pool operations
    public AudioControlGraph obtain();
    public void free(AudioControlGraph object);
    public void clear();
}

Usage Examples

Basic Web Audio Setup

public class WebAudioGame implements ApplicationListener {
    private WebAudioAPIManager audioManager;
    private WebAudioAPISound jumpSound;
    private WebAudioAPIMusic backgroundMusic;
    
    @Override
    public void create() {
        // Check Web Audio API support
        if (WebAudioAPIManager.isSupported()) {
            audioManager = WebAudioAPIManager.getInstance();
            audioManager.createContext();
            
            // Load audio assets
            jumpSound = (WebAudioAPISound) Gdx.audio.newSound(Gdx.files.internal("audio/jump.ogg"));
            backgroundMusic = (WebAudioAPIMusic) Gdx.audio.newMusic(Gdx.files.internal("music/background.ogg"));
            
            // Configure background music
            backgroundMusic.setLooping(true);
            backgroundMusic.setVolume(0.7f);
            
            System.out.println("Web Audio API initialized successfully");
        } else {
            System.out.println("Web Audio API not supported, using fallback");
            // Fall back to regular HTML5 Audio
        }
    }
    
    @Override
    public void render() {
        // Resume audio context on user interaction (required by browsers)
        if (Gdx.input.justTouched() && audioManager != null) {
            audioManager.resumeContext();
            
            if (!backgroundMusic.isPlaying()) {
                backgroundMusic.play();
            }
        }
    }
    
    public void playJumpSound() {
        if (jumpSound != null) {
            // Play with random pitch variation
            float pitch = 0.8f + (float)Math.random() * 0.4f; // 0.8 to 1.2
            jumpSound.play(1.0f, pitch, 0.0f);
        }
    }
    
    @Override
    public void dispose() {
        if (jumpSound != null) jumpSound.dispose();
        if (backgroundMusic != null) backgroundMusic.dispose();
        if (audioManager != null) audioManager.dispose();
    }
}

Advanced Audio Effects

public class AudioEffectsDemo implements ApplicationListener {
    private AudioControlGraph audioGraph;
    private WebAudioAPISound laserSound;
    private WebAudioAPIMusic ambienceMusic;
    
    @Override
    public void create() {
        if (WebAudioAPIManager.isSupported()) {
            WebAudioAPIManager manager = WebAudioAPIManager.getInstance();
            manager.createContext();
            
            // Create audio processing graph
            audioGraph = manager.createAudioGraph();
            
            // Load sounds
            laserSound = (WebAudioAPISound) Gdx.audio.newSound(Gdx.files.internal("sfx/laser.ogg"));
            ambienceMusic = (WebAudioAPIMusic) Gdx.audio.newMusic(Gdx.files.internal("music/ambience.ogg"));
            
            // Apply audio effects
            setupAudioEffects();
        }
    }
    
    private void setupAudioEffects() {
        // Apply low-pass filter for underwater effect
        audioGraph.applyLowPassFilter(800.0f);
        
        // Add reverb for spacious environment
        audioGraph.applyReverb(0.8f, 0.2f);
        
        // Set up spatial audio
        audioGraph.setListenerPosition(0, 0, 0);
        audioGraph.setListenerOrientation(0, 0, -1, 0, 1, 0); // Looking forward, up is up
    }
    
    public void playLaserAt(float x, float y) {
        if (laserSound != null) {
            // Calculate pan based on position
            float screenWidth = Gdx.graphics.getWidth();
            float pan = (x - screenWidth / 2) / (screenWidth / 2); // -1 to 1
            pan = Math.max(-1, Math.min(1, pan)); // Clamp to valid range
            
            // Play with spatial positioning
            audioGraph.setSourcePosition(x, y, 0);
            laserSound.play(1.0f, 1.0f, pan);
        }
    }
    
    public void startUnderwater​Mode() {
        // Apply underwater audio effect
        audioGraph.applyLowPassFilter(400.0f);
        audioGraph.setMasterGain(0.6f);
        
        // Reduce music volume
        if (ambienceMusic != null) {
            ambienceMusic.setVolume(0.3f);
        }
    }
    
    public void endUnderwaterMode() {
        // Remove underwater effect
        audioGraph.applyLowPassFilter(20000.0f); // Remove filter
        audioGraph.setMasterGain(1.0f);
        
        // Restore music volume
        if (ambienceMusic != null) {
            ambienceMusic.setVolume(0.8f);
        }
    }
    
    @Override
    public void dispose() {
        if (audioGraph != null) audioGraph.dispose();
        if (laserSound != null) laserSound.dispose();
        if (ambienceMusic != null) ambienceMusic.dispose();
    }
}

Audio Visualization

public class AudioVisualizerDemo implements ApplicationListener {
    private AudioControlGraph audioGraph;
    private WebAudioAPIMusic music;
    private float[] frequencyData;
    private float[] timeData;
    private ShapeRenderer shapeRenderer;
    
    @Override
    public void create() {
        if (WebAudioAPIManager.isSupported()) {
            WebAudioAPIManager manager = WebAudioAPIManager.getInstance();
            manager.createContext();
            
            audioGraph = manager.createAudioGraph();
            music = (WebAudioAPIMusic) Gdx.audio.newMusic(Gdx.files.internal("music/electronic.ogg"));
            music.setLooping(true);
            
            // Initialize visualization data arrays
            frequencyData = new float[128];
            timeData = new float[128];
            
            shapeRenderer = new ShapeRenderer();
        }
    }
    
    @Override
    public void render() {
        // Update audio analysis data
        if (audioGraph != null && music.isPlaying()) {
            frequencyData = audioGraph.getFrequencyData();
            timeData = audioGraph.getTimeData();
        }
        
        // Clear screen
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        
        // Draw frequency spectrum
        drawFrequencySpectrum();
        
        // Draw waveform
        drawWaveform();
        
        // Start music on user input
        if (Gdx.input.justTouched() && music != null && !music.isPlaying()) {
            music.play();
        }
    }
    
    private void drawFrequencySpectrum() {
        if (frequencyData == null) return;
        
        shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
        shapeRenderer.setColor(0, 1, 0, 0.8f); // Green bars
        
        float barWidth = Gdx.graphics.getWidth() / (float)frequencyData.length;
        
        for (int i = 0; i < frequencyData.length; i++) {
            float height = frequencyData[i] * Gdx.graphics.getHeight() * 0.4f;
            float x = i * barWidth;
            float y = Gdx.graphics.getHeight() * 0.6f;
            
            shapeRenderer.rect(x, y, barWidth - 1, height);
        }
        
        shapeRenderer.end();
    }
    
    private void drawWaveform() {
        if (timeData == null) return;
        
        shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
        shapeRenderer.setColor(1, 1, 0, 1); // Yellow waveform
        
        float centerY = Gdx.graphics.getHeight() * 0.3f;
        float amplitude = 100;
        float stepX = Gdx.graphics.getWidth() / (float)(timeData.length - 1);
        
        for (int i = 0; i < timeData.length - 1; i++) {
            float x1 = i * stepX;
            float y1 = centerY + timeData[i] * amplitude;
            float x2 = (i + 1) * stepX;
            float y2 = centerY + timeData[i + 1] * amplitude;
            
            shapeRenderer.line(x1, y1, x2, y2);
        }
        
        shapeRenderer.end();
    }
    
    @Override
    public void dispose() {
        if (audioGraph != null) audioGraph.dispose();
        if (music != null) music.dispose();
        if (shapeRenderer != null) shapeRenderer.dispose();
    }
}

Dynamic Audio Loading

public class DynamicAudioLoader {
    private WebAudioAPIManager audioManager;
    private Map<String, WebAudioAPISound> loadedSounds;
    private Map<String, WebAudioAPIMusic> loadedMusic;
    
    public DynamicAudioLoader() {
        loadedSounds = new HashMap<>();
        loadedMusic = new HashMap<>();
        
        if (WebAudioAPIManager.isSupported()) {
            audioManager = WebAudioAPIManager.getInstance();
            audioManager.createContext();
        }
    }
    
    public void loadSound(String name, String path, Runnable onComplete) {
        if (loadedSounds.containsKey(name)) {
            if (onComplete != null) onComplete.run();
            return;
        }
        
        // Load sound asynchronously
        Timer.schedule(new Timer.Task() {
            @Override
            public void run() {
                try {
                    WebAudioAPISound sound = (WebAudioAPISound) Gdx.audio.newSound(Gdx.files.internal(path));
                    loadedSounds.put(name, sound);
                    System.out.println("Loaded sound: " + name);
                    if (onComplete != null) onComplete.run();
                } catch (Exception e) {
                    System.err.println("Failed to load sound: " + name + " - " + e.getMessage());
                }
            }
        }, 0.1f); // Small delay to simulate async loading
    }
    
    public void loadMusic(String name, String path, Runnable onComplete) {
        if (loadedMusic.containsKey(name)) {
            if (onComplete != null) onComplete.run();
            return;
        }
        
        Timer.schedule(new Timer.Task() {
            @Override
            public void run() {
                try {
                    WebAudioAPIMusic music = (WebAudioAPIMusic) Gdx.audio.newMusic(Gdx.files.internal(path));
                    loadedMusic.put(name, music);
                    System.out.println("Loaded music: " + name);
                    if (onComplete != null) onComplete.run();
                } catch (Exception e) {
                    System.err.println("Failed to load music: " + name + " - " + e.getMessage());
                }
            }
        }, 0.1f);
    }
    
    public WebAudioAPISound getSound(String name) {
        return loadedSounds.get(name);
    }
    
    public WebAudioAPIMusic getMusic(String name) {
        return loadedMusic.get(name);
    }
    
    public void playSound(String name) {
        WebAudioAPISound sound = getSound(name);
        if (sound != null) {
            sound.play();
        } else {
            System.err.println("Sound not loaded: " + name);
        }
    }
    
    public void playSound(String name, float volume, float pitch) {
        WebAudioAPISound sound = getSound(name);
        if (sound != null) {
            sound.play(volume, pitch, 0);
        } else {
            System.err.println("Sound not loaded: " + name);
        }
    }
    
    public void playMusic(String name) {
        WebAudioAPIMusic music = getMusic(name);
        if (music != null) {
            music.play();
        } else {
            System.err.println("Music not loaded: " + name);
        }
    }
    
    public void unloadSound(String name) {
        WebAudioAPISound sound = loadedSounds.remove(name);
        if (sound != null) {
            sound.dispose();
            System.out.println("Unloaded sound: " + name);
        }
    }
    
    public void unloadMusic(String name) {
        WebAudioAPIMusic music = loadedMusic.remove(name);
        if (music != null) {
            music.dispose();
            System.out.println("Unloaded music: " + name);
        }
    }
    
    public void dispose() {
        // Dispose all loaded audio
        for (WebAudioAPISound sound : loadedSounds.values()) {
            sound.dispose();
        }
        loadedSounds.clear();
        
        for (WebAudioAPIMusic music : loadedMusic.values()) {
            music.dispose();
        }
        loadedMusic.clear();
        
        if (audioManager != null) {
            audioManager.dispose();
        }
    }
}

Web Audio API Benefits

Performance Advantages

// Web Audio API provides several performance benefits over HTML5 Audio:

public class AudioPerformanceComparison {
    
    public void demonstrateAdvantages() {
        if (WebAudioAPIManager.isSupported()) {
            // ✓ Lower latency audio playback
            // ✓ Multiple simultaneous audio instances
            // ✓ Precise timing control
            // ✓ Audio processing and effects
            // ✓ Better mobile device support
            // ✓ No browser audio instance limits
            
            System.out.println("Using Web Audio API for optimal performance");
        } else {
            // Fall back to HTML5 Audio
            // ✗ Higher latency
            // ✗ Limited simultaneous sounds
            // ✗ No advanced audio processing
            // ✗ Browser-dependent limitations
            
            System.out.println("Falling back to HTML5 Audio");
        }
    }
    
    public void testAudioLatency() {
        WebAudioAPISound testSound = (WebAudioAPISound) Gdx.audio.newSound(Gdx.files.internal("test_click.wav"));
        
        long startTime = System.currentTimeMillis();
        testSound.play();
        long endTime = System.currentTimeMillis();
        
        System.out.println("Audio playback latency: " + (endTime - startTime) + "ms");
        // Web Audio API typically shows <10ms latency
        // HTML5 Audio often shows 50-200ms latency
    }
}

Browser Compatibility

// Web Audio API support across browsers
public class BrowserCompatibility {
    
    public static void checkAudioSupport() {
        if (WebAudioAPIManager.isSupported()) {
            System.out.println("Web Audio API supported - using advanced audio features");
            
            // Available in:
            // - Chrome 14+
            // - Firefox 25+
            // - Safari 6+
            // - Edge 12+
            // - Mobile Chrome/Safari
            
        } else {
            System.out.println("Web Audio API not supported - using HTML5 Audio fallback");
            
            // Fallback for:
            // - Internet Explorer
            // - Very old browser versions
            // - Browsers with Web Audio disabled
        }
    }
    
    public static void handleAudioContext() {
        // Modern browsers require user interaction before audio context can start
        WebAudioAPIManager manager = WebAudioAPIManager.getInstance();
        
        if (Gdx.input.justTouched()) {
            // Resume context after user interaction
            manager.resumeContext();
            System.out.println("Audio context resumed");
        }
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-com-badlogicgames-gdx--gdx-backend-gwt

docs

application.md

audio.md

files.md

graphics.md

index.md

input.md

networking.md

preloader.md

webaudio.md

widgets.md

tile.json