Skip to main content

SoundServer

Introduction

The sim's native API for playing sounds from JS/HTML instruments (Coherent.call('PLAY_INSTRUMENT_SOUND', soundId)) is functional but bare. The SDK class SoundServer provides a richer interface for playing sounds. With SoundServer, you can queue sounds, play multiple sound events in sequence, loop sounds, and be notified when sounds are finished playing.

Setting Up SoundServer

SoundServer was designed to be lightweight. Using it only requires a single instance of the SoundServer class and access to the event bus.

The first step to using SoundServer is to create an instance of the SoundServer class. There should be only one instance of SoundServer across all instruments in your airplane. Once you have an instance of SoundServer, you need to hook up its onSoundEnd() callback method to the identically named callback in BaseInstrument.

import { EventBus, SoundServer } from '@microsoft/msfs-sdk';

class MyInstrument extends BaseInstrument {

private readonly bus = new EventBus();
private readonly soundServer = new SoundServer(this.bus);

// ...

public onSoundEnd(soundEventId: Name_Z): void {
super.onSoundEnd(soundEventId);

this.soundServer.onSoundEnd(soundEventId);
}
}

Once the above items are completed, the final step is to ensure that all code that interacts with SoundServer waits until the server is initialized. To know exactly when the server is initialized, you should subscribe to the sound_server_initialized topic on the event bus:

import { SoundServerEvents } from '@microsoft/msfs-sdk';

bus.getSubscriber<SoundServerEvents>().on('sound_server_initialized').handle(isInit => {
if (isInit) {
// Do things...
}
});

Using Sound Packets

SoundServer operates using the concept of sound packets. A sound packet is the fundamental "unit" of sound that can be played with SoundServer. Each sound packet has the following properties:

  • key: A string that is used to reference the packet. Packets with the same key are not allowed to play at the same time. When a packet is queued, it is queued with other packets with the same key.
  • sequence: A string or array of strings that define the sound events that are played for the packet. Each string in the sequence should be an avionics sound event ID defined in the airplane's sound.xml. If an array of strings is used, then each sound event in the array is played in order. Sound events are also known as sound atoms because they are the shortest bits of sound that can be played, and once a sound event starts playing it cannot be stopped until it ends.
  • continuous: Whether the packet's sound event sequence plays in an infinite loop.
  • timeout: The maximum amount of time the packet's sound event sequence is considered to be "still playing". After the timeout duration, the system proceeds as if the sequence has finished playing, regardless of whether all the sound events in the sequence have actually finished playing. This behavior is included to prevent the system from permanently locking up if a sound event fails to play. If the packet is continuous, then the timeout is reset every time the packet loops to the beginning of its sequence. The default timeout is 10000 milliseconds.

To play sound packets, use the event bus to send commands to SoundServer. There are three different commands that will cause a packet to be played, each with slightly different behavior:

import { SoundServerControlEvents } from '@microsoft/msfs-sdk';

// This will play the sound packet if and only if another packet with the same key is not currently playing.
bus.getPublisher<SoundServerControlEvents>().pub('sound_server_play', {
key: 'my-packet',
sequence: 'my_sound_event',
continuous: false
}, true, false);

// This will queue the sound packet to be played at the earliest opportunity. If no packet with the same key is
// already playing or queued, then the packet will be played immediately. Otherwise, it will be queued to play after
// all other packets with the same key that are currently playing or queued have finished playing.
bus.getPublisher<SoundServerControlEvents>().pub('sound_server_queue', {
key: 'my-packet',
sequence: 'my_sound_event',
continuous: false
}, true, false);

// This will play the sound packet at the earliest opportunity. If no packet with the same key is already playing,
// then the packet will be played immediately. Otherwise, the currently playing packet with the same key will be
// stopped at the earliest opportunity (remember that sound atoms cannot be interrupted while they are playing), any
// queued packets with the same key will be discarded, and the new packet will start playing once the currently playing
// packet has been stopped.
bus.getPublisher<SoundServerControlEvents>().pub('sound_server_interrupt', {
key: 'my-packet',
sequence: 'my_sound_event',
continuous: false
}, true, false);
caution

When publishing any of the topics defined by SoundServerControlEvents, you must specify that the topic be synced to other instruments and not cached (the third and fourth parameters of pub(), respectively). Failure to specify these options will result in incorrect behavior.

caution

The sim does not allow more than one instance of the same avionics sound event to be played simultaneously. Attempting to play a sound event while it is already playing simply has no effect. Therefore, it is recommended to avoid playing sound packets with different keys that also contain the same sound event at the same time. Doing so will not cause any errors, but the results will likely not match the original intention.

You can also command SoundServer to stop packets from playing once they are already playing or queued by referencing their keys:

import { SoundServerControlEvents } from '@microsoft/msfs-sdk';

// This will stop a continuous sound packet from looping. Has no effect on non-continuous packets that are already
// playing. Any queued packets with the specified key will be discarded.
bus.getPublisher<SoundServerControlEvents>().pub('sound_server_stop', 'my-packet', true, false);

// This will stop an already playing packet at the earliest opportunity (remember that sound atoms cannot be
// interrupted while they are playing). Any queued packets with the specified key will be discarded.
bus.getPublisher<SoundServerControlEvents>().pub('sound_server_kill', 'my-packet', true, false);

// Same as 'sound_server_stop', but for all packets.
bus.getPublisher<SoundServerControlEvents>().pub('sound_server_stop_all', undefined, true, false);

// Same as 'sound_server_kill', but for all packets.
bus.getPublisher<SoundServerControlEvents>().pub('sound_server_kill_all', undefined, true, false);

Playing Simple Sounds

Sometimes you just need to play a simple sound event and don't need the fancy sequencing or queueing features of SoundServer. To facilitate these use cases, SoundServer supports simplified commands:

import { SoundServerControlEvents } from '@microsoft/msfs-sdk';

bus.getPublisher<SoundServerControlEvents>().pub('sound_server_play_sound', 'my_sound_event', true, false);
// ... is equivalent to ...
bus.getPublisher<SoundServerControlEvents>().pub('sound_server_play', {
key: 'my_sound_event',
sequence: 'my_sound_event',
continuous: false
}, true, false);

// ---------------------------------------------------------------------------------------------------------

bus.getPublisher<SoundServerControlEvents>().pub('sound_server_start_sound', 'my_sound_event', true, false);
// ... is equivalent to ...
bus.getPublisher<SoundServerControlEvents>().pub('sound_server_play', {
key: 'my_sound_event',
sequence: 'my_sound_event',
continuous: true
}, true, false);

// ---------------------------------------------------------------------------------------------------------

bus.getPublisher<SoundServerControlEvents>().pub('sound_server_stop_sound', 'my_sound_event', true, false);
// ... is equivalent to ...
bus.getPublisher<SoundServerControlEvents>().pub('sound_server_stop', 'my_sound_event', true, false);

Subscribing to Sound Packet Events

If you need to know when a sound packet starts or finishes playing, you can subscribe to the following event bus topics:

import { SoundServerEvents } from '@microsoft/msfs-sdk';

bus.getSubscriber<SoundServerEvents>().on('sound_server_packet_started').handle(key => {
console.log(`A sound packet with key: ${key} has started playing.`);
});

bus.getSubscriber<SoundServerEvents>().on('sound_server_packet_ended').handle(key => {
console.log(`A sound packet with key: ${key} has finished playing.`);
});

Using SoundServerController

For convenience, the SoundServerController class is provided to abstract away some of the boilerplate associated with publishing commands to SoundServer over the event bus. For each command topic in SoundServerControlEvents, there is a corresponding method on SoundServerController which publishes the command. The controller also facilitates waiting for SoundServer to be initialized. For example:

import { SoundServerController } from '@microsoft/msfs-sdk';

const controller = new SoundServerController(bus);

// Wait for SoundServer to be initialized...
controller.awaitInitialized().then(() => {

controller.play({
key: 'my-packet',
sequence: 'my_sound_event',
continuous: false
});

controller.queue({
key: 'my-packet',
sequence: 'my_sound_event',
continuous: false
});

// ...

});