DEV Community

Cover image for TCJSGame Sound Class: Complete Reference Guide
Kehinde Owolabi
Kehinde Owolabi

Posted on • Edited on

TCJSGame Sound Class: Complete Reference Guide

TCJSGame Sound Class: Complete Reference Guide

Game Audio

Introduction to the Sound Class

TCJSGame's Sound Class provides simple yet powerful audio capabilities for your games. It wraps the HTML5 Audio API to handle sound effects, background music, and audio management with an easy-to-use interface.

🎵 Basic Sound Implementation

Creating Sound Objects

// Create sound effects
const jumpSound = new Sound("sounds/jump.wav");
const coinSound = new Sound("sounds/coin.mp3");
const explosionSound = new Sound("sounds/explosion.ogg");

// Create background music
const bgMusic = new Sound("music/background.mp3");

// Example: Preloading essential sounds
function preloadSounds() {
    const sounds = {
        jump: new Sound("sounds/jump.wav"),
        hit: new Sound("sounds/hit.wav"),
        victory: new Sound("sounds/victory.mp3"),
        music: new Sound("music/game_theme.mp3")
    };
    return sounds;
}

const gameSounds = preloadSounds();
Enter fullscreen mode Exit fullscreen mode

Basic Playback Control

// Play sounds
jumpSound.play();
coinSound.play();

// Stop sounds
bgMusic.stop();
explosionSound.stop();

// Example: Player jump with sound
function playerJump() {
    if (player.gravitySpeed === 0) {
        player.speedY = -12;
        jumpSound.play();
    }
}

// Example: Coin collection
function collectCoin(coin) {
    coin.hide();
    score += 10;
    coinSound.play();
}
Enter fullscreen mode Exit fullscreen mode

🎮 Advanced Audio Features

Looping Background Music

// While Sound class doesn't have built-in loop, you can implement it
class LoopingSound extends Sound {
    constructor(src, loop = false) {
        super(src);
        this.loop = loop;
        this.sound.loop = loop;
    }

    play() {
        this.sound.currentTime = 0; // Reset to start
        super.play();
    }
}

// Usage
const backgroundMusic = new LoopingSound("music/background.mp3", true);
backgroundMusic.play();

// Alternative: Manual looping
function playBackgroundMusic() {
    bgMusic.play();
    bgMusic.sound.addEventListener('ended', function() {
        this.currentTime = 0;
        this.play();
    }, false);
}
Enter fullscreen mode Exit fullscreen mode

Volume Control

// Volume control (0.0 to 1.0)
jumpSound.sound.volume = 0.7;      // 70% volume
bgMusic.sound.volume = 0.3;        // 30% volume for background music
explosionSound.sound.volume = 1.0; // Full volume

// Example: Volume settings system
class AudioManager {
    constructor() {
        this.sounds = {};
        this.masterVolume = 1.0;
        this.musicVolume = 0.6;
        this.sfxVolume = 0.8;
        this.muted = false;
    }

    addSound(name, src, type = 'sfx') {
        this.sounds[name] = {
            sound: new Sound(src),
            type: type
        };
        this.updateVolume(name);
    }

    play(name) {
        if (!this.muted && this.sounds[name]) {
            this.sounds[name].sound.play();
        }
    }

    updateVolume(name) {
        if (this.sounds[name]) {
            const soundObj = this.sounds[name];
            let volume = this.masterVolume;

            if (soundObj.type === 'music') volume *= this.musicVolume;
            if (soundObj.type === 'sfx') volume *= this.sfxVolume;

            soundObj.sound.sound.volume = this.muted ? 0 : volume;
        }
    }

    toggleMute() {
        this.muted = !this.muted;
        Object.keys(this.sounds).forEach(name => this.updateVolume(name));
    }
}

// Usage
const audio = new AudioManager();
audio.addSound('jump', 'sounds/jump.wav', 'sfx');
audio.addSound('music', 'music/theme.mp3', 'music');
audio.play('jump');
Enter fullscreen mode Exit fullscreen mode

🎯 Practical Game Examples

Platformer Sound System

class PlatformerAudio {
    constructor() {
        this.sounds = {
            jump: new Sound("sounds/jump.wav"),
            land: new Sound("sounds/land.wav"),
            coin: new Sound("sounds/coin.wav"),
            enemyHit: new Sound("sounds/enemy_hit.wav"),
            playerHit: new Sound("sounds/player_hit.wav"),
            levelComplete: new Sound("sounds/level_complete.wav"),
            bgMusic: new Sound("music/background.mp3")
        };

        // Set volumes
        this.sounds.bgMusic.sound.volume = 0.4;
        this.sounds.jump.sound.volume = 0.7;
    }

    playJump() {
        this.sounds.jump.play();
    }

    playLand() {
        this.sounds.land.play();
    }

    playCoin() {
        this.sounds.coin.play();
    }

    startBackgroundMusic() {
        this.sounds.bgMusic.play();
        // Implement looping
        this.sounds.bgMusic.sound.addEventListener('ended', () => {
            this.sounds.bgMusic.play();
        });
    }

    stopBackgroundMusic() {
        this.sounds.bgMusic.stop();
    }
}

// Usage in game
const gameAudio = new PlatformerAudio();

function update() {
    // Play jump sound when player jumps
    if (display.keys[32] && player.gravitySpeed === 0) { // Spacebar
        player.speedY = -12;
        gameAudio.playJump();
    }

    // Play land sound when player hits ground
    if (player.y >= display.canvas.height - player.height && player.gravitySpeed > 0) {
        gameAudio.playLand();
    }
}
Enter fullscreen mode Exit fullscreen mode

Shooter Game Audio

class ShooterAudio {
    constructor() {
        this.sounds = {
            laser: new Sound("sounds/laser.wav"),
            explosion: new Sound("sounds/explosion.wav"),
            powerup: new Sound("sounds/powerup.wav"),
            shield: new Sound("sounds/shield.wav"),
            gameOver: new Sound("sounds/game_over.wav"),
            bgMusic: new Sound("music/space_theme.mp3")
        };
    }

    playLaser() {
        this.sounds.laser.play();
    }

    playExplosion() {
        this.sounds.explosion.play();
    }

    playPowerup() {
        this.sounds.powerup.play();
    }
}

// Usage
const shooterAudio = new ShooterAudio();

function fireLaser() {
    const laser = new Component(5, 15, "red", player.x, player.y, "rect");
    laser.speedY = -10;
    display.add(laser);
    shooterAudio.playLaser();
}

function enemyDestroyed(enemy) {
    enemy.hide();
    score += 100;
    shooterAudio.playExplosion();
}
Enter fullscreen mode Exit fullscreen mode

🔧 Audio Management Systems

Sound Pool for Frequent Sounds

class SoundPool {
    constructor(src, poolSize = 5) {
        this.sounds = [];
        this.currentIndex = 0;

        // Create pool of sound instances
        for (let i = 0; i < poolSize; i++) {
            const sound = new Sound(src);
            this.sounds.push(sound);
        }
    }

    play() {
        // Round-robin through sound instances to allow overlapping
        this.sounds[this.currentIndex].play();
        this.currentIndex = (this.currentIndex + 1) % this.sounds.length;
    }
}

// Usage for frequent sounds like laser shots
const laserPool = new SoundPool("sounds/laser.wav", 3);

function rapidFire() {
    // Can play multiple times quickly without cutting off previous sounds
    laserPool.play();
}
Enter fullscreen mode Exit fullscreen mode

Sequential Sound Playback

class SoundSequence {
    constructor() {
        this.sounds = [];
        this.currentSound = 0;
        this.playing = false;
    }

    addSound(sound, delay = 0) {
        this.sounds.push({ sound, delay });
    }

    play() {
        if (this.playing || this.sounds.length === 0) return;

        this.playing = true;
        this.currentSound = 0;
        this.playNext();
    }

    playNext() {
        if (this.currentSound >= this.sounds.length) {
            this.playing = false;
            return;
        }

        const { sound, delay } = this.sounds[this.currentSound];

        setTimeout(() => {
            sound.play();
            this.currentSound++;

            // Wait for sound to finish or use fixed delay
            setTimeout(() => this.playNext(), delay);
        }, delay);
    }
}

// Usage for cutscenes or tutorials
const introSequence = new SoundSequence();
introSequence.addSound(new Sound("voices/intro1.wav"), 1000);
introSequence.addSound(new Sound("voices/intro2.wav"), 2000);
introSequence.addSound(new Sound("voices/intro3.wav"), 1500);
introSequence.play();
Enter fullscreen mode Exit fullscreen mode

🎚️ Advanced Audio Techniques

Spatial Audio Simulation

class SpatialAudio {
    constructor(sound, maxDistance = 500) {
        this.sound = sound;
        this.maxDistance = maxDistance;
    }

    playAt(x, y, listenerX, listenerY) {
        const distance = Math.sqrt((x - listenerX) ** 2 + (y - listenerY) ** 2);
        const volume = Math.max(0, 1 - (distance / this.maxDistance));

        this.sound.sound.volume = volume;
        this.sound.play();

        // Reset volume after playback starts
        setTimeout(() => {
            this.sound.sound.volume = 1.0;
        }, 100);
    }
}

// Usage
const explosionSpatial = new SpatialAudio(new Sound("sounds/explosion.wav"));

function playDistantExplosion(x, y) {
    const playerX = player.x + player.width / 2;
    const playerY = player.y + player.height / 2;
    explosionSpatial.playAt(x, y, playerX, playerY);
}
Enter fullscreen mode Exit fullscreen mode

Audio Fading Effects

class AudioFader {
    constructor(sound) {
        this.sound = sound;
        this.fadeInterval = null;
    }

    fadeIn(duration = 2000) {
        this.sound.sound.volume = 0;
        this.sound.play();

        const steps = duration / 50; // 50ms intervals
        const volumeStep = 1 / steps;
        let currentStep = 0;

        this.fadeInterval = setInterval(() => {
            currentStep++;
            this.sound.sound.volume = Math.min(1, volumeStep * currentStep);

            if (currentStep >= steps) {
                clearInterval(this.fadeInterval);
            }
        }, 50);
    }

    fadeOut(duration = 2000) {
        const steps = duration / 50;
        const volumeStep = this.sound.sound.volume / steps;
        let currentStep = 0;

        this.fadeInterval = setInterval(() => {
            currentStep++;
            this.sound.sound.volume = Math.max(0, this.sound.sound.volume - volumeStep);

            if (currentStep >= steps) {
                this.sound.stop();
                clearInterval(this.fadeInterval);
            }
        }, 50);
    }
}

// Usage for smooth music transitions
const musicFader = new AudioFader(backgroundMusic);
musicFader.fadeIn(3000); // Fade in over 3 seconds

// Later when changing levels
musicFader.fadeOut(2000); // Fade out over 2 seconds
Enter fullscreen mode Exit fullscreen mode

🎮 Complete Game Audio System

Comprehensive Audio Manager

class GameAudioManager {
    constructor() {
        this.sounds = new Map();
        this.music = null;
        this.masterVolume = 1.0;
        this.sfxVolume = 0.8;
        this.musicVolume = 0.6;
        this.muted = false;
    }

    loadSound(name, path, options = {}) {
        const sound = new Sound(path);

        if (options.volume) {
            sound.sound.volume = options.volume;
        }

        this.sounds.set(name, {
            sound: sound,
            type: options.type || 'sfx',
            pool: options.pool ? new SoundPool(path, options.pool) : null
        });
    }

    play(name) {
        if (this.muted || !this.sounds.has(name)) return;

        const soundData = this.sounds.get(name);

        if (soundData.pool) {
            soundData.pool.play();
        } else {
            soundData.sound.play();
        }
    }

    playMusic(name) {
        if (this.music) {
            this.music.stop();
        }

        if (this.sounds.has(name)) {
            this.music = this.sounds.get(name).sound;
            this.music.play();

            // Enable looping for music
            this.music.sound.loop = true;
        }
    }

    setVolume(type, volume) {
        switch(type) {
            case 'master':
                this.masterVolume = volume;
                break;
            case 'sfx':
                this.sfxVolume = volume;
                break;
            case 'music':
                this.musicVolume = volume;
                break;
        }
        this.updateAllVolumes();
    }

    updateAllVolumes() {
        this.sounds.forEach((soundData, name) => {
            let volume = this.masterVolume;

            if (soundData.type === 'sfx') volume *= this.sfxVolume;
            if (soundData.type === 'music') volume *= this.musicVolume;

            soundData.sound.sound.volume = this.muted ? 0 : volume;
        });
    }

    toggleMute() {
        this.muted = !this.muted;
        this.updateAllVolumes();
    }
}

// Usage
const audioManager = new GameAudioManager();

// Load sounds
audioManager.loadSound('jump', 'sounds/jump.wav', { type: 'sfx', volume: 0.7 });
audioManager.loadSound('coin', 'sounds/coin.wav', { type: 'sfx', volume: 0.8 });
audioManager.loadSound('explosion', 'sounds/explosion.wav', { type: 'sfx', pool: 3 });
audioManager.loadSound('bg_music', 'music/theme.mp3', { type: 'music', volume: 0.5 });

// Play sounds in game
function update() {
    if (display.keys[32] && player.gravitySpeed === 0) {
        audioManager.play('jump');
    }
}

// Start background music
audioManager.playMusic('bg_music');
Enter fullscreen mode Exit fullscreen mode

⚡ Performance Optimization

Sound Preloading and Caching

class SoundPreloader {
    constructor() {
        this.loadedSounds = new Map();
        this.loadingPromises = [];
    }

    preload(soundList) {
        soundList.forEach(soundDef => {
            const promise = new Promise((resolve) => {
                const sound = new Sound(soundDef.path);
                sound.sound.addEventListener('canplaythrough', () => {
                    this.loadedSounds.set(soundDef.name, sound);
                    resolve();
                });
                sound.sound.load(); // Force loading
            });
            this.loadingPromises.push(promise);
        });

        return Promise.all(this.loadingPromises);
    }

    getSound(name) {
        return this.loadedSounds.get(name);
    }
}

// Usage
const preloader = new SoundPreloader();
const soundsToPreload = [
    { name: 'jump', path: 'sounds/jump.wav' },
    { name: 'coin', path: 'sounds/coin.wav' },
    { name: 'bg_music', path: 'music/theme.mp3' }
];

preloader.preload(soundsToPreload).then(() => {
    console.log('All sounds loaded!');
    // Start game
});
Enter fullscreen mode Exit fullscreen mode

🐛 Common Issues and Solutions

Audio Autoplay Restrictions

// Handle browser autoplay policies
function setupAudioWithUserInteraction() {
    let audioEnabled = false;

    // Enable audio on first user interaction
    document.addEventListener('click', function enableAudio() {
        if (!audioEnabled) {
            audioEnabled = true;
            backgroundMusic.play();
            document.removeEventListener('click', enableAudio);
        }
    });
}

// Alternative: Audio context resume
async function unlockAudio() {
    const context = new (window.AudioContext || window.webkitAudioContext)();
    await context.resume();

    // Now audio should play
    backgroundMusic.play();
}
Enter fullscreen mode Exit fullscreen mode

File Format Compatibility

// Support multiple formats for better compatibility
class MultiFormatSound {
    constructor(basePath, formats = ['mp3', 'wav', 'ogg']) {
        this.sound = null;

        for (let format of formats) {
            const testSound = new Sound(`${basePath}.${format}`);
            // Check if format is supported
            if (testSound.sound.canPlayType) {
                this.sound = testSound;
                break;
            }
        }

        if (!this.sound) {
            console.warn(`No supported audio format found for: ${basePath}`);
        }
    }

    play() {
        if (this.sound) this.sound.play();
    }

    stop() {
        if (this.sound) this.sound.stop();
    }
}

// Usage
const compatibleSound = new MultiFormatSound('sounds/jump', ['mp3', 'wav', 'ogg']);
Enter fullscreen mode Exit fullscreen mode

📚 Conclusion

TCJSGame's Sound Class provides:

  • Simple audio playback with minimal code
  • HTML5 Audio API integration for broad compatibility
  • Flexible sound management for various game types
  • Basic volume control through direct property access
  • Easy integration with game events and interactions

While the base Sound Class is simple, you can extend it with:

  • Audio managers for volume control and organization
  • Sound pools for frequent effects
  • Spatial audio for immersive experiences
  • Fading effects for smooth transitions
  • Preloading systems for better performance

Sound is crucial for game immersion, and TCJSGame makes it accessible for developers of all skill levels. Combine these audio techniques with TCJSGame's visual and movement systems to create truly engaging gaming experiences!

Top comments (0)