Skip to main content

duck

Added in 6.5.0

Turn a target down while one of its trigger sounds plays, and bring it back up when the last trigger stops. The usual case is music that makes room for a voice. See Ducking for how it works underneath.

duck(target: string | string[], options: DuckOptions): () => void;

Parameters​

ParameterTypeDefaultDescription
targetstring | string[]requiredWhat goes down. A sound id, a group, a stream or a variation name, or a list of them.
optionsDuckOptionsrequiredThe triggers and the shape of the duck, see below.

DuckOptions​

PropertyTypeDefaultDescription
whenstring | string[]requiredThe sound, group, stream or variation whose playing turns the target down. One name or several.
amountnumber0.3The level the target drops to, from 0 (silent) to 1 (no change).
attacknumber0.05Seconds to go down once a trigger starts.
releasenumber0.5Seconds to come back up after the last trigger stops.

The full type is on the DuckOptions page.

Returns​

() => void: a function that removes the duck again. It does the same as unduck, for every target you passed.

Example​

import { SoundHub } from 'soundhub';

const soundHub = new SoundHub();
await soundHub.loadSounds([
{ id: 'music', url: '/audio/theme.mp3' },
{ id: 'voice', url: '/audio/line-01.mp3' },
]);

soundHub.duck('music', { when: 'voice', amount: 0.3 });

soundHub.play('music', { loop: true });
soundHub.play('voice'); // the music drops to 30% in 50 ms
// and comes back over half a second when the voice ends

A group of lines as the trigger​

soundHub.createSoundGroup('dialogue');

soundHub.duck(['music', 'ambience'], {
when: 'dialogue',
amount: 0.25,
attack: 0.1,
release: 0.8,
});

soundHub.play('line-12', { groupId: 'dialogue' });
soundHub.play('line-13', { groupId: 'dialogue' });
// The music and the ambience stay down until the second line has finished

Removing it again​

const removeDuck = soundHub.duck('music', { when: 'voice' });

// Later, when the cutscene is over
removeDuck();

How names match​

Every name in target and when is compared against:

  • a sound id, such as 'music'
  • the sound its overlapping instances came from, so 'laser' covers 'laser:3'
  • the sound a sprite was cut from, so 'ui' covers the sprite 'ui_click'
  • a group name
  • a stream id
  • a variation name from createVariations

Good to know​

  • Only a trigger you can hear counts. A paused or muted trigger does not duck, and resuming or unmuting it takes the target down again.
  • Several triggers, or overlapping instances of one, keep the target down until the last of them stops.
  • The duck has a gain node of its own between the target and the master bus. The target's volume, mute and any fade in progress are left alone, so a setSoundVolume during a duck is still there after it.
  • The duck follows the triggers whether the target plays or not. A sound that starts while its trigger plays starts at the ducked level.
  • A sound that joins a target group later, with addToSoundGroup, is routed through the duck from then on.
  • A sound never ducks itself, even when it matches both a target and a trigger name.
  • Calling duck() again for the same target replaces its options. When the target is down at that moment, it glides from its current level to the new amount over the new attack, without going back up first and without a second duck_started.
  • amount is clamped to 0 to 1, and a negative attack or release counts as 0.
  • An empty target or an empty when throws an error.
  • The target is checked on every change in playback, including a play, pause, resume or stop with skipDispatchEvent, which dispatches no event.
  • duck_started and duck_ended arrive on the event bus with the target as soundId and the level as volume.

See also​