Sound
Overview

Davstack Sound

Davstack sound is the simplest way to add sound FX to your React/next.js app. It's built on top of Howler.js, a popular audio library that supports all major browsers.

Visit the DavStack Sound Docs (opens in a new tab) for more information and examples.

Features

  • Super Simple API: Just call soundStore.playSound('soundName') to play a sound. No hooks required.
  • Excellent DX: Define all your sounds in once place and play them with full type safety.
  • Optimized Performance: All sounds are cached and preloaded, so they play instantly. Howler.js is lazy-loaded to keep bundle size down.
  • Easily change global sound settings: Change volume, mute, or stop all sounds with a single line of code.

Demo Usage

// lib/sound-store.ts
import { createSoundStore } from '@davstack/sound';
 
// path relative to /public (assuming you are using next.js) eg `./public/sounds/switch-on.mp3`
const SOUND_BASE_PATH = './sounds';
export const soundStore = createSoundStore({
	soundNameToPath: {
		switchOn: `${SOUND_BASE_PATH}/switch-on.mp3`,
		switchOff: `${SOUND_BASE_PATH}/switch-off.mp3`,
		// ...
	},
});
 
// components/button.tsx
import { soundStore } from '@/lib/sound-store';
 
export const Button = () => {
	return (
		<button
			onClick={() => {
				soundStore.playSound('switchOn');
			}}
		>
			Click me
		</button>
	);
};

Installation

npm install @davstack/sound

Note: This package is built with Davstack Store (opens in a new tab) and therefore Zustand (opens in a new tab) is a peer dependency, so you will need to install it separately if you haven't already.

Usage Guide

Add sound assets into your project

First, Add sounds to /public eg ./public/sounds/pop.mp3 (assuming you are using next.js)

For a small selection of high quality sounds, I recommend material design sound assets (opens in a new tab)

Define your sound store

// lib/sound-store.ts
 
// path relative to /public (assuming you are using next.js)
const SOUND_BASE_PATH = './sounds';
const soundStore = createSoundStore({
	soundNameToPath: {
		pop: `${SOUND_BASE_PATH}/pop.mp3`,
		switchOn: `${SOUND_BASE_PATH}/switch-on.mp3`,
		switchOff: `${SOUND_BASE_PATH}/switch-off.mp3`,
		// ...
	},
});

Initialize sounds in app (optional)

The first sound you play may have some delay. This is because the audio assets need some time to load. Additionally, Howler (the library we're using) is lazily loaded to keep bundle size down / prevent impacting initial page load time. However, this further delays the first sound.

To mitigate this, place the soundStore.InitAllSounds component in your app eg in root layout component

// app/layout.tsx
import { soundStore } from '@/lib/sound-store';
 
export const Layout = ({ children }) => {
	return (
		<>
			{children}
			<soundStore.InitAllSounds />
		</>
	);
};

Note: The browser does not allow audio context to be loaded until a user gesture is detected. To get around this, the InitAllSounds component will wait for the first user interaction before initializing the audio context.

Play sounds

// components/button.tsx
import { soundStore } from '@/lib/sound-store';
 
export const Button = () => {
	return (
		<button
			onClick={() => {
				soundStore.playSound('pop');
			}}
		>
			Click me
		</button>
	);
};

Play sounds with custom options

export interface PlayOptions {
	/**
	 * The playback rate of the sound (1 is normal speed, 2 is double speed, 0.5 is half speed)
	 * If not set, it will use the global playback rate.
	 */
	playbackRate?: number;
	/**
	 * The volume of the sound. If not set, it will use the global volume.
	 * If both global volume and options volume are set, it will multiply them.
	 */
	volume?: number;
	/**
	 * overrides the global soundEnabled setting
	 */
	forceSoundEnabled?: boolean;
	id?: string;
}
 
// example usage
soundStore.playSound('pop', {
	volume: 0.5,
	playbackRate: 2,
});

Change global sound settings

// components/sound-controls.tsx
import { soundStore } from '@/lib/sound-store';
 
export const VolumeControl = () => {
	const volume = soundStore.volume.use();
	return (
		<input
			title="Volume control"
			type="range"
			min="0"
			max="1"
			step="0.01"
			value={volume}
			onMouseUp={() => {
				// make sure you actually have "pop" sound configured in your sound store
				soundStore.playSound('pop');
			}}
			onTouchEnd={() => {
				soundStore.playSound('pop');
			}}
			onChange={(e) => {
				soundStore.volume.set(parseFloat(e.target.value));
			}}
		/>
	);
};
 
export const SoundToggle = () => {
	const soundEnabled = soundStore.soundEnabled.use();
	return (
		<button
			onClick={() => {
				if (soundEnabled) {
					soundStore.playSound('switchOff');
					soundStore.soundEnabled.set(false);
				} else {
					soundStore.soundEnabled.set(true);
					soundStore.playSound('switchOn');
				}
			}}
		>
			{soundEnabled
				? 'sound on (press to turn off)'
				: 'sound off (press to turn on)'}
		</button>
	);
};

Note: if the global sound is set to 0.5 and you pass in a volume of 0.5 to the playSound function, the sound will play at 0.25 volume (0.5 * 0.5)

Acknowledgements

Davstack Sound has been heavily inspired by use-sound (opens in a new tab), another great library for adding sound to your react app. A big shout-out to Josh w Comeau (opens in a new tab) for his amazing work.

Contributing

Contributions are welcome! Please read our contributing guide for details on our code of conduct and the submission process.

License

This project is licensed under the MIT License. See the LICENSE file for details.