
Research
wget to Wipeout: Malicious Go Modules Fetch Destructive Payload
Socket's research uncovers three dangerous Go modules that contain obfuscated disk-wiping malware, threatening complete data loss.
sound-manager-ts
Advanced tools
A lightweight, TypeScript-based Web Audio API manager for seamless sound control in web apps and games
A powerful and lightweight (13KB gzipped) sound management system I crafted to make Web Audio API accessible and enjoyable. Perfect for web applications, games, and interactive experiences that demand precise audio control without the complexity. No more wrestling with time calculations or audio states - everything is handled for you. Simply listen to sound events or use getSoundState('soundId') to access comprehensive audio data, ready to integrate with your UI.
Codepen.io (Demo / Playground) JavScript
Codepen.io (Demo / Playground) TypeScript
๐ Modern & Efficient
๐ฎ Perfect for Games & Apps
๐ ๏ธ Developer Friendly
๐๏ธ Volume Control & Fading
Easily adjust volume levels for individual sounds or globally. Supports smooth fade-in and fade-out effects.
๐ฏ Spatial Audio Positioning
Create immersive 3D audio experiences with spatial audio positioning (x, y, z coordinates).
โฏ๏ธ Playback Control Play, pause, resume, and stop sounds with precision. Supports seamless looping and custom start/end times.
๐๏ธ Pan & Balance Adjustment
Adjust stereo panning for individual sounds or globally. Supports both stereo and spatial panning.
โก Event-Driven Architecture Built with an event-driven design, allowing you to hook into sound events like play, pause, volume changes, and more.
๐ฑ Mobile-Friendly
Optimized for mobile devices with support for auto-resume on focus and auto-mute on page hidden.
๐๏ธ Sound Groups
Organize sounds into groups for easier management. Apply volume, pan, and playback rate adjustments to entire groups.
โฉ Playback Rate Control
Adjust the playback speed of sounds without affecting pitch. Perfect for slow-motion or fast-forward effects.
๐ Looping & Max Loops
Loop sounds indefinitely or set a maximum number of loops for controlled playback.
๐ถ Sound Sprites
Split audio files into smaller segments (sprites) for precise playback of specific sections.
๐ Progress Tracking
Track playback progress in real-time with events for progress updates, duration changes, and more.
๐ Mute & Unmute
Mute or unmute individual sounds, groups, or the entire audio context.
๐ Reset & Cleanup
Reset individual sounds or the entire sound manager to their initial state. Clean up resources when no longer needed.
๐ก Cross-Origin Support
Load sounds from external sources with cross-origin support.
๐ง Debug Mode
Enable debug mode for detailed logging and troubleshooting.
๐ง Audio Context Management
Automatically handle audio context suspension and resumption for better performance and compatibility.
Development Status: This sound manager has undergone significant recent enhancements, with numerous additional features including sound groups, sprites, and more. After extensive testing and resolving various edge cases, version 5.2.0 now appears stable with most features and scenarios thoroughly validated.
The documentation page will be published on GitHub within the next month or so.
Contribution: If you encounter any issues or have ideas for enhancements, please don't hesitate to share them. Your input is valuable and will help shape the final version!
Supports all modern browsers including Chrome, Firefox, Safari, and Edge (98.5% global coverage).
Transform your web audio experience with just a few lines of code!
My journey in web development spans back to the Flash era, where among various projects, I developed a sound manager using ActionScript 3.0. As technology evolved, so did I, embracing new challenges and opportunities to grow. This Sound Manager TypeScript project represents not just a modern reimagining of a concept I once built in Flash, but also my challange for continuous learning and adaptation in the ever-changing landscape of web development.
I built this library in my spare time. What started as a personal study project has grown into a robust solution that I'm excited to share with the developer community.
Feel free to use this library in your projects, and I hope it inspires you to pursue your own passion projects, regardless of how technology changes. Sometimes the best learning comes from rebuilding something you once loved in a completely new way.
npm install sound-manager-ts
import { SoundManager } from "sound-manager-ts";
const soundManager = new SoundManager();
soundManager.addEventListener(SoundEventsEnum.LOADED, (event: SoundEvent) => {
console.log('Sound loaded', event);
});
await soundManager.loadSounds([{ id: "music", url: "/sounds/music.mp3" }]);
soundManager.play("music");
For TypeScript projects, it is recommended to install the package and import it directly. This method provides better type safety and allows you to take full advantage of TypeScript features.
npm install sound-manager-ts
After the installation a folder
In your TypeScript file, you can import and use the Sound Manager like this:
import { SoundManager, SoundManagerConfig, SoundEventsEnum } from "sound-manager-ts";
// Optional configuration
const config: SoundManagerConfig = {
autoMuteOnHidden: true, // Mute when tab is hidden
autoResumeOnFocus: true, // Resume on tab focus
defaultVolume: 0.8, // Default volume (0-1)
};
// Initialize sound manager with config
const soundManager = new SoundManager(config);
// Listen to load event
soundManager.addEventListener(SoundEventsEnum.LOADED, (event: SoundEvent) => {
console.log('Sound loaded', event);
});
// Define sounds to preload
const soundsToLoad = [
{ id: "background-music", url: "/assets/sounds/background.mp3" },
{ id: "click-effect", url: "/assets/sounds/click.wav" },
];
// Preload sounds
soundManager
.loadSounds(soundsToLoad)
.then(() => {
console.log("All sounds loaded successfully");
})
.catch((error) => {
console.error("Error loading sounds:", error);
});
// Play a sound
soundManager.play("background-music", {
volume: 0.7,
fadeInDuration: 2,
});
If you prefer to include Sound Manager directly as a library file in your project, you can use the UMD (Universal Module Definition) version. This approach allows you to integrate the sound manager without package managers or build tools - simply include the JavaScript file in your HTML.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sound Manager Implementation</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.sound-controls { margin: 20px 0; padding: 15px; background: #f5f5f5; border-radius: 5px; }
button { padding: 8px 12px; margin-right: 10px; cursor: pointer; }
</style>
</head>
<body>
<h1>Sound Manager Implementation</h1>
<div class="sound-controls">
<button id="playBtn">Play Background Music</button>
<button id="stopBtn">Stop Music</button>
<button id="clickBtn">Play Click Sound</button>
</div>
<!--
====================================================================
CDN INSTALLATION OPTIONS
====================================================================
-->
<!-- Option 1: UMD Version (Works everywhere) -->
<script src="https://unpkg.com/sound-manager-ts@5.5.4/dist/sound-manager-ts.umd.js"></script>
<!--
Alternative UMD options:
- Download and use local file: <script src="/path/to/sound-manager-ts.umd.js"></script>
- Specific version: <script src="https://unpkg.com/sound-manager-ts@5.5.4/dist/sound-manager-ts.umd.js"></script>
-->
<!-- Option 2: ESM Version (Modern browsers/bundlers) -->
<!--
<script type="module">
import { SoundManager } from 'https://unpkg.com/sound-manager-ts@5.5.4/dist/sound-manager-ts.es.js';
// Your ESM code here
</script>
-->
<script>
// ====================================================================
// INITIALIZATION
// ====================================================================
const soundManager = new SoundManagerTS.SoundManager({
debug: true, // Enable console logs for debugging
autoMuteOnHidden: true, // Mute when tab is hidden
autoResumeOnFocus: true, // Resume when tab regains focus
defaultVolume: 0.7, // Default volume (0-1)
spatialAudio: false, // Enable 3D audio if needed
fadeInDuration: 1, // Default fade-in duration (seconds)
fadeOutDuration: 1 // Default fade-out duration (seconds)
});
// ====================================================================
// Add eventlistener LOADED
// ====================================================================
soundManager.addEventListener(SoundEventsEnum.LOADED, (event: SoundEvent) => {
console.log('Sound loaded', event);
});
// ====================================================================
// SOUND DEFINITIONS
// ====================================================================
const sounds = {
background: {
id: "background-music",
url: "https://example.com/sounds/background.mp3",
options: { loop: true, volume: 0.6 }
},
click: {
id: "click-effect",
url: "https://example.com/sounds/click.wav",
options: { volume: 0.8 }
}
};
// ====================================================================
// SOUND LOADING (Using async/await)
// ====================================================================
async function initializeSounds() {
try {
// Load all sounds
await soundManager.loadSounds([
{ id: sounds.background.id, url: sounds.background.url },
{ id: sounds.click.id, url: sounds.click.url }
]);
console.log("All sounds loaded successfully");
// Set up event listeners after sounds are loaded
setupControls();
} catch (error) {
console.error("Error loading sounds:", error);
alert("Failed to load sounds. Please check console for details.");
}
}
// ====================================================================
// CONTROL FUNCTIONS
// ====================================================================
function setupControls() {
document.getElementById('playBtn').addEventListener('click', () => {
soundManager.play(sounds.background.id, {
...sounds.background.options,
fadeInDuration: 2 // Override default fade-in
});
});
document.getElementById('stopBtn').addEventListener('click', () => {
soundManager.stop(sounds.background.id);
});
document.getElementById('clickBtn').addEventListener('click', () => {
soundManager.play(sounds.click.id, sounds.click.options);
});
}
// ====================================================================
// ERROR HANDLING & EVENTS
// ====================================================================
soundManager.addEventListener(SoundManagerTS.SoundEventsEnum.ERROR, (event) => {
console.error("Sound Manager Error:", event.error);
});
soundManager.addEventListener(SoundManagerTS.SoundEventsEnum.ENDED, (event) => {
console.log(`Sound ${event.soundId} finished playing`);
});
soundManager.addEventListener(SoundManagerTS.SoundEventsEnum.PROGRESS, (event) => {
console.log(`Sound Progress: ${event.progress} %`);
console.log(`Sound Progress Info: ${event.progressInfo}`);
});
// Initialize the sound manager when page loads
window.addEventListener('DOMContentLoaded', initializeSounds);
</script>
</body>
</html>
import { SoundManager, SoundManagerConfig, SoundEventsEnum } from 'sound-manager-ts';
// Optional configuration
export interface SoundManagerConfig {
autoUnlock?: boolean; // Unlock audio for mobile browser that have restrictions
autoMuteOnHidden?: boolean; // Automatically mute when page or tab of your browser is not active
autoResumeOnFocus?: boolean; // Automatically resume when page or tab of your browser gets focus
createNewInstance?: boolean; // Create a new instance of the sound when playing it.
// By default this is false. This is useful when you want to play the same sound multiple times simultaneously.
// ------- Loading Configuration: -------------------------------------------------------------
// Loading Behaviour
webAudioPreferred?: boolean; // Whether to prefer Web Audio API (default: true)
html5AudioFallback?: boolean; // Whether to use HTML5 Audio as fallback (default: true)
maxParallelLoads?: number; // Maximum parallel sound loads (default: 6)
retryDelay?: number; // Delay between retry attempts in seconds (default: 0.5 seconds)
// Network Handling
fetchRetries?: number; // Number of retries for failed fetches (default: 2)
fetchTimeout?: number; // Timeout for fetch requests in seconds
corsProxy?: string; // URL of CORS proxy service, the ones I tested that work great are:
// corsProxy: "https://cors-anywhere.herokuapp.com/", or corsProxy: "https://corsproxy.io/?", or your own proxy
fetchStrategy?: 'direct-first' | 'proxy-first' | 'direct-only';
// Security & Limits
maxAudioSize?: number; // in bytes, currently the max is set to 50MB (50 * 1024 * 1024)
audioCache?: boolean; // Cache the audio file when loading.
crossOrigin?: "anonymous" | "use-credentials" | null;
credentialStrategy?: 'auto' | 'omit' | 'include';
// -----End Loading Configuration-------------------------------------------------------------
debug?: boolean; // Enable debug logging
defaultDuration?: number; // Default duration for new sounds, default is undefined (full length of the sound)
defaultPan?: number; // The default pan value = 0, in the center. Posiible values are (-1 to 1)
defaultPanSpatialPosition?: { x: number; y: number; z: number };
defaultPanType?: SoundPanType; // Default pan type
defaultPlaybackRate?: number // The default playbackRate is 1
defaultStartTime?: number; // Default start time for new sounds
defaultVolume?: number; // Default volume for new sounds (0-1)
fadeInDuration?: number; // Default fade-in duration in seconds
fadeOutDuration?: number; // Default fade-out duration in seconds
loopSounds?: boolean // Loop all sounds by default
maxLoops?: number // if loopSounds is true and maxLoops is set, the sound will loop maxLoops times (-1 is for infinite)
pannerNodeConfig?: SoundPannerConfig; // Panner settings for 3D sound
spatialAudio?: boolean; // Enable spatial audio features
trackProgress?: boolean; // Track progress of the sound playback.
// This will keep track of the process and will dispatch the 'progress' event. This is useful when you want to show the progress of the sound playback.
}
// Initialize sound manager with config
const soundManager = new SoundManager(config);
// Listen to Sound Loaded event
soundManager.addEventListener(SoundEventsEnum.LOADED, (event: SoundEvent) => {
console.log('Sound loaded', event);
});
// Define sounds to preload
const soundsToLoad = [
{ id: 'background-music', url: '/assets/sounds/background.mp3' },
{ id: 'click-effect', url: '/assets/sounds/click.wav' }
];
// Preload sounds (recommended)
try {
await soundManager.loadSounds(soundsToLoad);
console.log('All sounds loaded successfully');
} catch (error) {
console.error('Error loading sounds:', error);
}
// Add event listeners
soundManager.addEventListener(SoundEventsEnum.STARTED, (event) => {
console.log(`Sound ${event.soundId} started playing at ${event.timestamp}`);
});
soundManager.addEventListener(SoundEventsEnum.ENDED, (event) => {
console.log(`Sound ${event.soundId} finished playing`);
});
// Play a sound with options
soundManager.play('background-music', {
volume: 0.7,
loop: true,
fadeInDuration: 2,
fadeOutDuration: 2,
playbackRate: 0.5,
pan: -0.5,
startTime: 0
});
// Control individual sounds
soundManager.pauseSound('background-music');
soundManager.resume('background-music');
soundManager.stop('background-music');
soundManager.seek('background-music', 12); // Seek to 12 seconds
// Volume control
soundManager.setSoundVolume('background-music', 0.5);
soundManager.setGlobalVolume(0.8);
// Pan control
soundManager.setPan('background-music', -0.5); // Pan left
soundManager.setGlobalPan(0.3); // Slight right pan for all sounds
// Fade effects
soundManager.fadeIn('background-music', 2); // Fade in over 2 seconds
soundManager.fadeOut('background-music', 1); // Fade out over 1 second
soundManager.fadeGlobalIn(1.5); // Fade in all sounds
soundManager.fadeGlobalOut(1.5); // Fade out all sounds
// Playback rate
soundManager.setPlaybackRate('background-music', 1.5);
// Full example using Sprites
const soundsToLoad = [
{ id: "game-sound", url: gameSounds },
];
await this.soundManager.loadSounds(soundsToLoad);
let mySprite: any = {
intro: [0, 2], // 0,2 means start from 0 seconds until 2 seconds.
levelup: [2.4, 4], // start from 2.4 seconds till 4 seconds.
jump: [4, 5],
fail: [5, 7]
};
this.soundManager.setSoundSprite("game-sound", mySprite);
this.soundManager.playSprite("game-sound", "intro", { fadeInDuration: 1, pan: 0.8, playbackRate: 1.5});
this.soundManager.playSprite("game-sound", "jump", { loop: true});
this.soundManager.playSprite("game-sound", "levelup", { fadeOutDuration: 1, pan: -0.8});
setTimeout( ()=> {
this.soundManager.playSprite(this.id, "fail", { pan: 0.8});
}, 500);
// Sound Group example.
//
// In this example, when pressing the letter c, a piano note is triggerd. These piano notes are
// played in the sound group 'pian-group' where volume, paning and more can be managed.
// First, we create a Sound Group called piano-group.
// This group will manage up to 12 sound instances and set default options like volume and panning.
this.soundManager.createSoundGroup('piano-group', {
maxInstances: 12, // Limit the group to 12 simultaneous sounds
playOptions: {
volume: 0.8, // Default volume for sounds in this group
pan: 0, // Default panning (center)
},
});
// Next, we set up an event listener to play a new sound instance whenever a key is pressed.
// In this case, pressing the C key will play the piano-note sound.
document.addEventListener('keydown', (e) => {
if (e.key === 'c') {
// Play the "piano-note" sound with custom options
const sound = this.soundManager.play('piano-note', {
// groupId: 'piano-group', // Optionally, you can add the sound to a group here
trackProgress: true, // Enable progress tracking for this instance
loop: true, // Loop the sound
volume: 1, // Set volume (overrides group default)
playbackRate: 1, // Playback speed (1 = normal speed)
pan: Math.random() * 2 - 1, // Random panning between left (-1) and right (1)
createNewInstance: true, // Create a new instance of the sound
});
}
});
// To track the progress of each sound instance, we add an event listener for the PROGRESS event.
// This allows you to monitor how far along each sound is in its playback.
this.soundManager.addEventListener(
SoundEventsEnum.PROGRESS,
(event) => {
console.log(`Progress for instance ${event.instanceId}: ${event.progress}`);
},
{ originalId: "piano-note" } // Optional: Filter by originalId
);
// 3D Spatial Audio
// Set on a specific sound the 3d / spatial audio positioni
soundManager.setSpatialPosition(5, 0, -2, 'background-music');
// Set the master spatial position (x, y, z)
soundManager.setMasterSpatialPosition(10, 0, -3);
// Mute controls
soundManager.muteAllSounds();
soundManager.unmuteAllSounds();
soundManager.mute('background-music');
soundManager.unmute('background-music');
soundManager.toggleMute('background-music');
soundManager.toggleGlobalMute();
// Spatial audio (if enabled in config)
soundManager.setSpatialPosition('background-music', 1, 0, -1);
soundManager.resetSpatialPosition('background-music');
soundManager.removeSpatialEffect();
soundManager.isSpatialAudioActive('background-music');
soundManager.updatePannerConfig('background-music',
<SoundPannerConfig>{
panningModel: PanningModel.HRTF,
distanceModel: DistanceModel.Inverse,
refDistance: 1,
maxDistance: 10000,
rolloffFactor: 0.2,
coneInnerAngle: 360,
coneOuterAngle: 360,
coneOuterGain: 0,
}
);
// State checks
const isPlaying = soundManager.isPlaying('background-music');
const isPaused = soundManager.isPaused('background-music');
const isStopped = soundManager.isStopped('background-music');
const state = soundManager.getSoundState('background-music');
// Reset all sound settings to default values
soundManager.reset();
// Or use the SoundResetOptions
soundManager.reset({
keepVolumes: true; // Keep current volume settings
keepPanning: false; // Keep current panning settings
keepSpatial: false; // Keep spatial audio settings
unloadSounds: false; // Unload all sounds
})
// Cleanup
soundManager.destroy();
export interface SoundManagerInterface {
// Playback control
play(id: string, options?: PlayOptions, skipDispatchEvent?: boolean): void;
playSprite(id: string, spriteKey: string, options: PlayOptions, skipDispatchEvent?: boolean): void
pause(id: string, skipDispatchEvent?: boolean): void;
resume(id: string, skipDispatchEvent?: boolean): void;
stop(id: string, skipDispatchEvent?: boolean): void;
seek(id: string, time: number, skipDispatchEvent?: boolean): void;
// Volume control
getVolume(id: string): number;
setSoundVolume(id: string, volume: number): void;
getSoundVolume(id: string): number;
setGlobalVolume(volume: number): void;
getGlobalVolume(): number;
// Loop control
setLoop(id: string, loop: boolean): void
getLoop(id: string): boolean
// Mute control
muteAllSounds(): void;
unmuteAllSounds(): void;
mute(id: string): void;
unmute(id: string): void;
toggleGlobalMute(): void;
toggleMute(id: string): void;
// Sound loading and management
loadSounds(soundsToLoad: { id: string; url: string }[]): Promise<void>;
loadSound(id: string, url: string): Promise<void>;
updateSoundUrl(id: string, newUrl: string): Promise<void>;
unloadSound(id: string): void
removeSound(id: string): void
isSoundLoaded(id: string): boolean;
hasSound(id: string): boolean;
// State checks
isPlaying(id: string): boolean;
isPaused(id: string): boolean;
isStopped(id: string): boolean;
getSoundState(id: string): SoundStateInfo;
getSoundCount(): number;
isReady(): boolean;
// Progress tracking
getCurrentTime(id: string): number;
getDuration(id: string): number;
getProgress(id: string): number; // Returns the progress as a ratio (0-1)
getProgressPercentage(id: string): number;
startProgressTracking(id: string): void;
stopProgressTracking(id: string): void;
// Batch operations
stopAllSounds(): void;
pauseAllSounds(): void;
resumeAllSounds(): void;
reset(options?: SoundResetOptions): void;
// Fading
fadeIn(id: string, duration: number, startVolume?: number, endVolume?: number): void;
fadeOut(id: string, duration?: number, startVolume?: number, endVolume?: number, stopAfterFade?: boolean): void;
fadeGlobalIn(duration?: number, startVolume?: number, endVolume?: number): void;
fadeGlobalOut(duration?: number, startVolume?: number, endVolume?: number): void;
// Spatial audio
isSpatialAudioEnabled(): boolean;
setSpatialPosition(x: number, y: number, z: number, soundId?: string | null, soundPannerConfig?: SoundPannerConfig, skipEvent?: boolean): void;
getSpatialPosition(soundId: string): { x: number; y: number; z: number } | null;
setMasterSpatialPosition(x: number, y: number, z: number, config?: SoundPannerConfig, skipEvent?: boolean): void;
resetSpatialPosition(id: string): void;
removeSpatialEffect(id: string): void;
isSpatialAudioActive(id: string): boolean;
updatePannerConfigById(soundId: string, newConfig: Partial<SoundPannerConfig>): void;
// Pan control
setPan(id: string, pan: number): void;
removePan(id: string): void;
setGlobalPan(value: number): void;
getGlobalPan(): number;
resetPan(id?: string): void;
resetGlobalPan(): void;
cleanupGlobalPan(): void;
isStereoPanActive(id: string): boolean;
// Sprite logic
setSoundSprite(id: string, sprite: { [key: string]: [number, number] }): void;
getSpriteConfig(id: string): { [key: string]: [number, number] } | undefined;
removeSpriteConfig(id: string): void
// Context management
suspendContext(): Promise<void>;
resumeContext(): Promise<void>;
getContext(): AudioContext;
// Utilities
setDebugMode(debug: boolean): void;
getConfig(): Readonly<SoundManagerConfig>;
getSound(id: string): Sound | undefined;
getBuffer(id: string): AudioBuffer | undefined;
getSource(id: string): AudioBufferSourceNode | undefined;
getGainNode(id: string): GainNode | undefined;
getSoundIds(): string[];
updateSoundOptions(soundId: string, options: Partial<PlayOptions>): void;
setPlaybackRate(id: string, rate: number): void;
getLastError(): Error | null;
roundValue(value: number, decimals: number): number; // Default precision is this.DEFAULT_PRECISION
destroy(): void;
// Listeners / Event handling
addEventListener(type: SoundEventsEnum, callback: (event: SoundEvent) => void): void;
removeEventListener(type: SoundEventsEnum, callback: (event: SoundEvent) => void): void;
dispatchEvent(event: SoundEvent): void;
hasEventListener(type: SoundEventsEnum): boolean;
}
Options for playing a sound
export interface PlayOptions {
createNewInstance?: boolean; // Create a new instance of the sound when playing it.
// By default this is false. This is useful when you want to play the same sound multiple times simultaneously.
duration?:number; // in seconds
fadeInDuration?: number; // in seconds
fadeInStartVolume?: number; // 0 to 1
fadeOutDuration?: number; // in seconds, when you play a sound it will immidiately start fading out
fadeOutEndVolume?: number; // 0 to 1
fadeOutBeforeEndDuration?: number; // in seconds, fade out before the sound ends
groupId?: string; // Group ID for the sounds that will be in this group.
isSeeking?: boolean; // used internally for the seek method
loop?: boolean; // default: false
maxLoops?: number; // -1 for infinte, number > 0 for specific number of loops
pan?: number; // -1 (left) to 1 (right)
panSpatialPosition?: { x: number; y: number; z: number }; // If you want to use 3D panning you must also set panType to SoundPanType.Spatial
panType?: SoundPanType; // 'stereo' or 'spatial' (default is 'stereo')
pauseAtDurationReached?: boolean; // This will only work if you set the duration and if that duration
// is reached it will pause. Note: Loop must be false.
playbackRate?: number; // 0.5 to 4 (normal speed is 1)
startTime?: number; // in seconds
trackProgress?: boolean; // Track progress of the sound playback.
// This will keep track of the process and will dispatch the 'progress' event.
// This is useful when you want to show the progress of the sound playback.
volume?: number; // 0 to 1
}
Event object dispatched by the sound manager:
export interface SoundEvent {
currentTime?: number;
duration?:number;
error?: Error;
instanceId?: string; // Add this for instance tracking
isMaster?: boolean;
isMuted?: boolean;
options?: PlayOptions;
originalId?: string; // Add this to track the original sound ID
pan?: number;
pannerConfig?: SoundPannerConfig;
playbackRate?: number;
position?: { x: number; y: number; z: number };
previousPan?: number;
progress?: number; // ratio from 0 to 1
progressInfo?: SoundProgressStateInfo;
resetOptions?: SoundResetOptions;
sound?: Sound;
soundId?: string;
timestamp?: number;
type: SoundEventsEnum;
volume?: number;
}
Available event types:
export enum SoundEventsEnum {
ENDED = 'ended',
ERROR = 'error',
FADE_IN_COMPLETED = 'fade_in_completed',
FADE_MASTER_IN_COMPLETED = 'fade_master_in_completed',
FADE_MASTER_OUT_COMPLETED = 'fade_master_out_completed',
FADE_OUT_COMPLETED = 'fade_out_completed',
GLOBAL_SPATIAL_POSITION_CHANGED = 'global_spatial_position_changed',
LOOP_COMPLETED = 'loop_completed',
MASTER_PAN_CHANGED = 'master_pan_changed',
MASTER_VOLUME_CHANGED = 'master_volume_changed',
MUTE_GLOBAL = 'mute_global',
MUTED = 'muted',
OPTIONS_UPDATED = 'options_updated',
PAN_CHANGED = 'pan_changed',
PAN_RESET = 'pan_reset',
PAUSED = 'paused',
PLAYBACK_RATE_CHANGED = 'playback_rate_changed',
PROGRESS = 'progress',
RESET = 'reset',
RESUMED = 'resumed',
SEEKED = 'seeked',
SPATIAL_POSITION_CHANGED = 'spatial_position_changed',
SPATIAL_POSITION_RESET = 'spatial_position_reset',
SPRITE_SET = 'sprite_set',
STARTED = 'started',
STOPPED = 'stopped',
UNLOADED = 'unloaded',
UNMUTE_GLOBAL = 'unmute_global',
UNMUTED = 'unmuted',
UPDATED_URL = 'updated_url',
VOLUME_CHANGED = 'volume_changed',
}
export interface SoundGroup {
id: string; // internal usage (groupName)
sounds: Set<string>; // Stores sound IDs belonging to this group
maxInstances?: number; // Maximum number of concurrent instances allowed in the group
playOptions?: PlayOptions; // Add playOptions to the group
}
Configuration options:
export interface SoundManagerConfig {
autoUnlock?: boolean; // Unlock audio for mobile browser that have restrictions
autoMuteOnHidden?: boolean; // Automatically mute when page or tab of your browser is not active
autoResumeOnFocus?: boolean; // Automatically resume when page or tab of your browser gets focus
createNewInstance?: boolean; // Create a new instance of the sound when playing it.
// By default this is false. This is useful when you want to play the same sound multiple times simultaneously.
// ------- Loading Configuration: -------------------------------------------------------------
// Loading Behaviour
webAudioPreferred?: boolean; // Whether to prefer Web Audio API (default: true)
html5AudioFallback?: boolean; // Whether to use HTML5 Audio as fallback (default: true)
maxParallelLoads?: number; // Maximum parallel sound loads (default: 6)
retryDelay?: number; // Delay between retry attempts in seconds (default: 0.5 seconds)
// Network Handling
fetchRetries?: number; // Number of retries for failed fetches (default: 2)
fetchTimeout?: number; // Timeout for fetch requests in seconds
corsProxy?: string; // URL of CORS proxy service, the ones I tested that work great are:
// corsProxy: "https://cors-anywhere.herokuapp.com/", or corsProxy: "https://corsproxy.io/?", or your own proxy
fetchStrategy?: 'direct-first' | 'proxy-first' | 'direct-only';
// Security & Limits
maxAudioSize?: number; // in bytes, currently the max is set to 50MB (50 * 1024 * 1024)
audioCache?: boolean; // Cache the audio file when loading.
crossOrigin?: "anonymous" | "use-credentials" | null;
credentialStrategy?: 'auto' | 'omit' | 'include';
// -----End Loading Configuration-------------------------------------------------------------
debug?: boolean; // Enable debug logging
defaultDuration?: number; // Default duration for new sounds, default is undefined (full length of the sound)
defaultPan?: number; // The default pan value = 0, in the center. Posiible values are (-1 to 1)
defaultPanSpatialPosition?: { x: number; y: number; z: number };
defaultPanType?: SoundPanType; // Default pan type
defaultPlaybackRate?: number // The default playbackRate is 1
defaultStartTime?: number; // Default start time for new sounds
defaultVolume?: number; // Default volume for new sounds (0-1)
fadeInDuration?: number; // Default fade-in duration in seconds
fadeOutDuration?: number; // Default fade-out duration in seconds
loopSounds?: boolean // Loop all sounds by default
maxLoops?: number // if loopSounds is true and maxLoops is set, the sound will loop maxLoops times (-1 is for infinite)
pannerNodeConfig?: SoundPannerConfig; // Panner settings for 3D sound
spatialAudio?: boolean; // Enable spatial audio features
trackProgress?: boolean; // Track progress of the sound playback.
// This will keep track of the process and will dispatch the 'progress' event. This is useful when you want to show the progress of the sound playback.
}
Information about a sound's current state:
export interface SoundStateInfo {
progress: number; // ratio from 0 to 1
startTime: number; // in seconds
currentTime: number; // in seconds
elapsedTime: number; // in seconds
adjustedElapsedTime: number; // Elapsed time adjusted for playback rate
duration: number; // in seconds
rawDuration: number | null; // in seconds
playbackRate: number | null;
state: SoundState;
volume: number; // value from 0 to 1
pan: number; // value form 0 to 1
panSpatialPosition: { x: number; y: number; z: number };
}
Possible states of a sound:
export enum SoundState {
Playing = "playing",
Paused = "paused",
Stopped = "stopped",
}
export interface Sound {
buffer: AudioBuffer;
source: AudioBufferSourceNode | null;
positionTracker?: ConstantSourceNode;
currentLoopCount?: number;
gainNode: GainNode;
groupId?: string;
id: string;
isFadingIn?: boolean;
isFadingOut?: boolean;
originalVolume?: number;
pannerNode?: PannerNode | null; // for 3D panning
pan?: number; // Normal panning value -1 to 1
panSpatialPosition? : { x: number; y: number; z: number };
panType?: SoundPanType;
pausedAt?: number;
playOptions?: PlayOptions;
previousVolume?: number;
sprite?: { [key: string]: [number, number] }; // Sprite support
startTime?: number; // in seconds
state?: SoundState;
stereoPanner?: StereoPannerNode | null; // just plain left to right panning
volume?: number; // values from 0 to 1
duration?: number; // in seconds
currentTime?:number; // in seconds
instanceId?:string;
instanceCount?:number;
baseId?: string; // Base sound ID (e.g., "game-sound_jump")
}
Sound progress information, is connected to the sound event ->progressInfo
export interface SoundProgressStateInfo {
soundId: string;
currentTime: number;
duration: number;
rawDuration: number;
progress: number; // 0-1
}
export enum SoundPanType {
Stereo = 'stereo',
Spatial = 'spatial'
}
export enum PanningModel {
HRTF = "HRTF",
EqualPower = "equalpower",
}
export enum DistanceModel {
Linear = "linear",
Inverse = "inverse",
Exponential = "exponential",
}
export interface SoundPannerConfig {
/**
* Determines which spatialisation algorithm to use to position the audio in 3D space.
* - 'HRTF': More accurate, head-related transfer function (default)
* - 'equalpower': Basic equal-power panning
*/
panningModel?: PanningModel;
/**
* Determines how the volume of the audio source decreases as it moves away from the listener.
* - 'linear': Volume reduces linearly with distance
* - 'inverse': Volume reduces inversely with distance (realistic, default)
* - 'exponential': Volume reduces exponentially with distance
*/
distanceModel?: DistanceModel;
/**
* The reference distance for reducing volume as the audio source moves further from the listener.
* Default is 1 meter.
* @min 0
*/
refDistance?: number;
/**
* The maximum distance between the audio source and the listener, after which the volume will not be reduced any further.
* Default is 10000 meters.
* @min refDistance
*/
maxDistance?: number;
/**
* Describes how quickly the volume reduces as the source moves away from the listener.
* - For 'linear': Valid range [0, 1], default 1
* - For 'inverse': Valid range [0, โ], default 1
* - For 'exponential': Valid range [0, โ], default 1
*/
rolloffFactor?: number;
/**
* The angle, in degrees, of a cone inside which there will be no volume reduction.
* Default is 360 (no cone).
* @range [0, 360]
*/
coneInnerAngle?: number;
/**
* The angle, in degrees, of a cone outside which the volume will be reduced by a constant value.
* Default is 360 (no cone).
* @range [0, 360]
*/
coneOuterAngle?: number;
/**
* The amount of volume reduction outside the outer cone.
* Default is 0.
* @range [0, 1]
*/
coneOuterGain?: number;
}
export interface SoundResetOptions {
keepVolumes?: boolean; // Keep current volume settings
keepPanning?: boolean; // Keep current panning settings
keepSpatial?: boolean; // Keep spatial audio settings
keepPlaybackRate?: boolean // Keep playback rate
unloadSounds?: boolean; // Unload all sounds
}
The package includes a comprehensive demo showcasing all features:
npm install
Features are automatically adapted based on browser support:
This project is developed by Chris Schardijn. It is free to use in your project.
๐ New Features
Dual Loading System
Web Audio API (primary) with HTML5 Audio fallback (html5AudioFallback)
Configurable preference via webAudioPreferred (default: true)
Advanced Loading Controls
maxParallelLoads: Throttle concurrent loads (default: 10)
maxAudioSize: Safety limit (default: 50MB)
audioCache: Control browser caching behavior
CORS Management
corsProxy support with smart URL handling
Configurable strategies:
fetchStrategy: 'direct-first' | 'proxy-first' | 'direct-only'
credentialStrategy: 'auto' | 'omit' | 'include'
Automatic retries (fetchRetries)
Configurable timeouts (fetchTimeout)
Delay between retries (retryDelay)
๐ New Event: SoundEvent.LOAD In the event, you have this information of the loaded sound:
{
bufferSize,
channels,
duration,
fileSize,
sampleRate,
sound, // The Sound object
soundId,
timestamp,
type, // The Event type, in this case 'loaded' or SoundEvent.LOAD
}
๐ฑ Mobile Improvements
โ๏ธ Configuration Updates
interface SoundManagerConfig {
// Loading Behavior
webAudioPreferred?: boolean; // Default: true
html5AudioFallback?: boolean; // Default: true
maxParallelLoads?: number; // Default: 10
retryDelay?: number; // Seconds, default: 0.5
// Network Handling
fetchRetries?: number; // Default: 2
fetchTimeout?: number; // Seconds, default: 10
corsProxy?: string; // e.g. "https://corsproxy.io/?"
fetchStrategy?: 'direct-first' | 'proxy-first' | 'direct-only';
// Security & Limits
maxAudioSize?: number; // Bytes, default: 50MB
audioCache?: boolean; // Default: false
crossOrigin?: "anonymous" | "use-credentials" | null;
// Mobile
autoUnlock?: boolean; // Default: true
}
๐ Bug Fixes
๐ New Feature: fadeOutBeforeEndDuration with optionally setting fadeOutEndVolume. Added the fadeOutBeforeEndDuration option to PlayOptions. This feature automatically fades out a sound as it nears the end of playback. Example: If a sound has a duration of 10 seconds and you set fadeOutBeforeEndDuration to 3, the sound will begin fading out at 7 seconds and complete the fade by 10 seconds. This is particularly useful for creating smoother transitions and avoiding abrupt endings.
Old Method | New Method |
---|---|
soundManager.setSoundPosition(id) | soundManager.setSpatialPosition(id) |
soundManager.resetSoundPosition(id) | soundManager.resetSpatialPosition(id) |
playOptions (interface) | PlayOptions (interface) |
preloadSounds | loadSounds |
Added new methods
// Playback Control
setLoop(id: string, loop: boolean): void
getLoop(id: string): boolean
//Sound Loading and Management
loadSound(id: string, url: string): Promise<void>
loadSounds (renamed from preloadSounds)
unloadSound(id: string): void
removeSound(id: string): void
//Group Management (entirely new)
createSoundGroup(groupName: string, options: SoundGroup): void
addToSoundGroup(groupName: string, soundId: string): void
removeFromSoundGroup(groupName: string, soundId: string): void
getGroup(groupName: string): SoundGroup | undefined
removeSoundGroup(groupName: string): void
//Sprite Logic (expanded)
getSpriteConfig(id: string): { [key: string]: [number, number] } | undefined
removeSpriteSound(id: string): void
removeSpriteConfig(id: string): void
//Context Management (new)
suspendContext(): Promise<void>
resumeContext(): Promise<void>
getContext(): AudioContext
//Progress Tracking (expanded)
getDuration(id: string): number
startProgressTracking(id: string): void
stopProgressTracking(id: string): void
setProgressUpdateInterval(interval: number): void
//Spatial Audio (expanded)
isSpatialAudioSupported(): boolean
getSpatialPosition(soundId: string): { x: number; y: number; z: number } | null
getMasterSpatialPosition(): { x: number; y: number; z: number } | null
resetMasterSpatialPosition(): void
// renamed from setSoundPosition
setSpatialPosition(x: number, y: number, z: number, soundId?: string | null, soundPannerConfig?: SoundPannerConfig, skipDispatchEvent?: boolean): void;
//Reset Operations (expanded)
resetSound(id: string, options?: SoundResetOptions): void
resetPan(id?: string): void
//Sound/Buffer/Source/GainNode Retrieval (new)
getBuffer(id: string): AudioBuffer | undefined
getSource(id: string): AudioBufferSourceNode | undefined
getGainNode(id: string): GainNode | undefined
//Utilities (expanded)
isReady(): boolean
getSoundCount(): number
getLastError(): Error | null
roundValue(value: number, decimals: number): number
//Event Handling (expanded)
removeEventListenersForInstance(instanceId: string): void
dispatchEvent(event: SoundEvent): void
hasEventListener(type: SoundEventsEnum): boolean
- Added more PlayOptions
* fadeIn -> renamded to fadeInDuration
* fadeOut -> renamed to fadeOutDuration
* panSpatialPosition?: { x: number; y: number; z: number }
* PanType?: SoundPanType (stereo or spatial)
* trackProgress?: boolean (wheter to track playback progress)
* createNewInstance (if false, it will use the previously instance of the sound)
* playbackRate
* isSeeking
* duration (seconds)
* pauseAtDurationReached (by default it will trigger the stop method when the duration is reached)
- Added more information to the getSoundState(id) `SoundStateInfo`
* elapsedTime
* panSpatialPosition
* rawDuration
- Rebuild demo page
* seperate component for the spatial grid
* added dark theme
* seperate component master constrols
* seperate component sound controls
* added playbackRate UI
- Bug fixes
* startTime in PlayOptions was not working correctly.
* fix issues with the PlayOptions
* fix issues with spatial audio
* fix reset method
* fix playBackRate issues
* fadeIn / fadeOut
* fix issues with new instances
* fix issues with Sprites
* fixed typescript support .d.ts files
### 4.0.0 (Major update)
- Added PROGRESS event listener for sound playback monitoring
- Rebuilt sound sprite system with improved logic
- Fixed sound configuration issues (volume, fadeIn/fadeOut)
- Simplified build process and removed unnecessary Vite plugins
- Rebuild demo structure and split the code in components.
- Added new sound management methods:
```typescript
getCurrentTime(id: string): number;
getProgress(id: string): number; // Returns the progress as a ratio (0-1)
getProgressPercentage(id: string): number;
setDebugMode(debug: boolean): void;
Old Method | New Method |
---|---|
soundManager.playSound(id) | soundManager.play(id) |
soundManager.stopSound(id) | soundManager.stop(id) |
soundManager.pauseSound(id) | soundManager.pause(id) |
soundManager.resumeSound(id) | soundManager.resume(id) |
soundManager.seekTo(id, time) | soundManager.seek(id, time) |
soundManager.setVolumeById(id, volume) | soundManager.setSoundVolume(id, volume) |
soundManager.getVolumeById(id) | soundManager.getSoundVolume(id) |
soundManager.setGlobalVolume(volume) | soundManager.setGlobalVolume(volume) |
soundManager.getGlobalVolume() | soundManager.getGlobalVolume() |
soundManager.muteAllSounds() | soundManager.muteAll() |
soundManager.unmuteAllSounds() | soundManager.unmuteAll() |
soundManager.muteSoundById(id) | soundManager.mute(id) |
soundManager.unmuteSoundById(id) | soundManager.unmute(id) |
soundManager.toggleMute() | soundManager.toggleGlobalMute() |
soundManager.fadeMasterIn(...) | soundManager.fadeGlobalIn(...) |
soundManager.fadeMasterOut(...) | soundManager.fadeGlobalOut(...) |
soundManager.setMasterPan(value) | soundManager.setGlobalPan(value) |
soundManager.getMasterPan() | soundManager.getGlobalPan() |
soundManager.resetMasterPan() | soundManager.resetGlobalPan() |
soundManager.cleanupMasterPan() | soundManager.cleanupGlobalPan() |
๐ ๏ธ Enhanced configuration system
๐ Expanded event system
๐จ UI/UX enhancements
๐ฎ Added comprehensive loop control system
๐ฏ Enhanced seeking functionality
๐ Implemented 3D spatial audio support
๐ Bug Fixes
๐ต Audio Improvements
๐ Documentation
โก Dependencies
โจ New Features
๐ง Improvements
๐ Major Features
๐จ Technical Improvements
๐ฎ Demo
๐ Bug Fixes
โจ Enhancements
๐ง Build System
๐ Major Improvements
๐ Security & Stability
๐ Development
๐ Initial Release
FAQs
A lightweight, TypeScript-based Web Audio API manager for seamless sound control in web apps and games
We found that sound-manager-ts demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Socket's research uncovers three dangerous Go modules that contain obfuscated disk-wiping malware, threatening complete data loss.
Research
Socket uncovers malicious packages on PyPI using Gmail's SMTP protocol for command and control (C2) to exfiltrate data and execute commands.
Product
We redesigned Socket's first logged-in page to display rich and insightful visualizations about your repositories protected against supply chain threats.