Skip to content

JavaScript player

About

You can directly embed our player into your page, with the following benefits:

  • Avoid the requirement for a genuine interaction (click) to activate the audio context, assuming users are already interacting with your page.
  • Implement your own interface and visualisation, as simple or complex as you need.
  • Connect our Web Audio API nodes directly to your own.

Bundles

We have several bundles that can be loaded separately or together. Each bundle will add to the public API exposed at window.syrinscape.

integration

Convenience functions for integrations.

player

Web Audio API player, with no interface. You decide how (or if) the player appears on your page, and how users interact with it.

visualisation

Efficient and configurable requestAnimationFrame loop for pluggable visualisers.

These URLs will always redirect to the latest versions:

HTML
<script src="https://syrinscape.com/integration.js"></script>
<script src="https://syrinscape.com/player.js"></script>
<script src="https://syrinscape.com/visualisation.js"></script>

Tip

Use the pinned versions referenced in https://syrinscape.com/latest-bundles/ to avoid unexpected changes.

Player

Init

Start the player with an anonymous session:

JavaScript
syrinscape.player.init()

Note

A genuine user interaction is required to activate the audio context.

If not already active, syrinscape.player.init() configures a one-time click event handler on document that captures and later replays the first click, after activating the audio context and starting the player.

Configure

Pass a configure() callback to configure the player before startup:

JavaScript
syrinscape.player.init({
    async configure() {
        // Audio context. Leave undefined to create one.
        syrinscape.config.audioContext = new AudioContext()

        // Auth token.
        syrinscape.config.token = prompt(
            'Enter your Syrinscape auth token (from https://syrinscape.com/account/auth-token/). By default an anonymous token has been created for you. Leave blank to create a new one.',
            syrinscape.config.token,
        )

        // Wait until async token change is finished, because `sessionId` might change.
        await syrinscape.config.sync()

        // Session ID. Use the GM's session ID for all users. Get from
        // `syrinscape.config.sessionId` in the GM's browser.
        syrinscape.config.sessionId = prompt(
            "Enter the GM's session ID. By default a session has been created with you as the GM.",
            syrinscape.config.sessionId,
        )
    },
})

Reconfigure

You can reconfigure any time after startup.

Join a new session:

JavaScript
syrinscape.config.sessionId = "new-session-id"

Reconnect as a new user:

JavaScript
syrinscape.config.token = "new-token"

Note

Assigning a new config.token will asynchronously update config.authenticated and config.sessionId in the background. If you need to access these immediately, call await config.sync().

Interface

You can implement your own interface and visualisation, as simple or complex as you need.

For players, you should implement (at least):

  • Local volume
  • Mute
  • Visualisation

For Game Masters, you should implement:

  • Global volume
  • Oneshot volume
  • Stop
  • Launch (Syrinscape, for full control)
  • Login

You might also want to consider:

  • Global Oneshots
  • Search
  • Embedded Mood and Element links in adventure text

Show and hide

Because the player will only start after the web audio context is activated, you can use the onActive() and onInactive() callbacks to show or hide your interface and a CTA to activate.

HTML
<style>
    .cta,
    .interface {
        display: none;
    }
</style>
<button class="cta">
    Activate
</button>
<div class="interface">
    ...
</div>
<script>
    syrinscape.player.init({
        onActive() {
            // Hide CTA. Show interface.
            document.querySelector('.cta').style.display = 'none'
            document.querySelector('.interface').style.display = 'revert'
        },
        onInactive() {
            // Show CTA. Hide interface.
            document.querySelector('.cta').style.display = 'revert'
            document.querySelector('.interface').style.display = 'none'
        },
    })
</script>

Local volume and mute

  • Apply local volume changes immediately (while adjusting).
  • Save the last local volume on change, and restore on unmute.
  • Set initial volume from config on startup.
HTML
<input class="local-volume" type="range" min="0" max="1.5" step="0.01" value="1">
<button class="mute" onclick="syrinscape.player.audioSystem.toggleMute()">
    Mute
</button>
<script>
    // Get controls.
    let localVolume = document.querySelector('.local-volume')
    let mute = document.querySelector('.mute')

    // Set local volume immediately (while adjusting) and last local volume (to
    // restore on unmute) on change.
    localVolume.addEventListener('input', () => {
        syrinscape.player.audioSystem.setLocalVolume(localVolume.value)
    })
    localVolume.addEventListener('change', () => {
        syrinscape.config.lastLocalVolume = localVolume.value
    })

    // Update interface on `setLocalVolume` event.
    syrinscape.events.setLocalVolume.addListener((event) => {
        localVolume.value = event.detail
        if (event.detail === 0) {
            mute.textContent = 'Unmute'
        } else {
            mute.textContent = 'Mute'
        }
    })
</script>

Visualisation

Our visualisation bundle runs a requestAnimationFrame loop that executes registered visualisers on each iteration.

Adjust the frame rate by setting syrinscape.config.visualisationFramerate. The default is 30 frames per second.

Each visualiser is a function that should:

  • Get a chunk of frequency or waveform data. For example, from the global analyser node syrinscape.player.audioEffectSystem.analyser.
    • Call .getFrequencyData() or .getTimeDomainData() to get a chunk of frequency or waveform data, or .getData() to get both.
  • Render a frame of visualisation, however you like.
    • We provide d3VisualiseFrequencyData() and d3VisualiseWaveformData(), which we use in our own interfaces. D3.js is included in the bundle.
  • Return true if it did any work.
    • If no visualisers do any work, the loop will be paused until the next sample starts, to avoid needless busywork.

Register global visualiser:

HTML
<style>
    /* TODO: Slim down to essential styles, and explain them. Move styles into the visualiser, if possible. */
    .visualisations-container {
        left: 0;
        bottom: 0;
        opacity: 0.7;
        position: absolute;
        width: 100%;
    }
    .visualisations {
        padding-top: 50%;
        position: relative;
        width: 100%;
    }
    .d3-frequency {
        height: 100%;
        left: 0;
        position: absolute;
        top: 0;
        transform: scaleY(-1);
        width: 100%;
    }
    .d3-waveform {
        height: 100%;
        left: 0;
        position: absolute;
        top: 0;
        transform: scaleY(-1);
        width: 100%;
    }
    .d3-waveform svg {
        height: 100%;
        width: 100%;
    }
</style>
<div class="visualisations-container">
    <div class="visualisations">
        <svg class="d3-frequency"></svg>
        <svg class="d3-waveform"></svg>
    </div>
</div>
<script>
    // Register 'global' frequency and waveform visualiser.
    syrinscape.visualisation.add('global', () => {
        // Get combined frequency and waveform data.
        let data = syrinscape.player.audioEffectSystem.analyser.getData()

        // Visualise frequency and waveform data.
        // You can replace this section to visualise the data any way you like.
        syrinscape.visualisation.d3VisualiseFrequencyData(data, '.d3-frequency')
        syrinscape.visualisation.d3VisualiseWaveformData(data, '.d3-waveform')

        // If analyser is still active, report that visualiser is still active.
        // If no visualisers are active, visualisation loop will be paused.
        return syrinscape.player.audioEffectSystem.analyser.isActive
    })
</script>

Stop

Stop everything:

HTML
<button onclick="syrinscape.player.controlSystem.stopAll()">
    Stop
</button>

Login

Show a login button to anonymous users:

HTML
<button class="login" onclick="syrinscape.integration.requestAuthToken()">
    Login
</button>
<script>
    // Show or hide login button when config is updated.
    syrinscape.events.updateConfig.addListener(() => {
        if (syrinscape.config.authenticated) {
            document.querySelector('.login').style.display = 'none'
        } else {
            document.querySelector('.login').style.display = 'revert'
        }
    })
</script>

Launch

Launch Syrinscape as Game Master to control the session:

HTML
<button onclick="syrinscape.integration.launchAsGameMaster()">
    Launch
</button>

Events

Several events are emitted, which you may like to listen for.

Each registered event is available at syrinscape.events. Triggered events will send a CustomEvent object to listeners with a detail key, which could be a number, a string, or an object, depending on the event.

List of events

syrinscape.playerActive

Triggered when the player (audio context) is active.

syrinscape.playerInactive

Triggered when the player (audio context) is inactive.

syrinscape.setElementVolume

Triggered when an element's volume is set.

Details:

  • elementId (number)
  • volume (number)
syrinscape.setGlobalVolume

Triggered when the global volume is set.

Details:

  • volume (number)
syrinscape.setLocalVolume

Triggered when the local volume is set.

Details:

  • volume (number)
syrinscape.startElement

Triggered when an element is started.

Details:

  • elementId (number)
  • timeToFirstSample (number)

    Time in milliseconds until the element's first sample will start.

syrinscape.startMood

Triggered when a mood is started.

Details:

  • moodId (number)
  • moodTitle (string)
syrinscape.startSample

Triggered when a sample is started.

Details:

  • elementId (number, optional)
  • playlistEntryId (number, optional)
  • sampleId (number)
  • timeToNextSample (number, optional)

    Time in milliseconds until the next sample will start, for samples started by an element.

  • timeToStop (number)

    Time in milliseconds until the sample will stop.

syrinscape.startVisualisation

Triggered when the visualisation is started, e.g. when a mood or element is started.

syrinscape.stopElement

Triggered when an element is stopped.

Details:

  • elementId (number)
  • timeToStop (number)

    Time in milliseconds until the element will stop.

syrinscape.stopSample

Triggered when a sample stop is requested and again when the stop is completed.

Details:

  • elementId (number, optional)
  • playlistEntryId (number, optional)
  • sampleId (number)
  • timeToStop (number)

    Time in milliseconds until the sample will stop or 0 when the stop is completed.

syrinscape.stopMood

Triggered when a mood is stopped. Does not trigger when a mood is stopped by a new mood starting.

syrinscape.stopVisualisation

Triggered when the visualisation is stopped, e.g. when there are no more samples playing.

syrinscape.updateConfig

Triggered when syrinscape.config is updated.

Details:

  • authenticated (boolean, optional)
  • displayName (string, optional)
  • lastLocalVolume (number, optional)
  • localVolume (number, optional)
  • sessionId (string, optional)
  • token (string, optional)
  • visualisationFramerate (number, optional)

Add a listener

JavaScript
syrinscape.events.setLocalVolume.addListener(
    (event) => console.log(`volume: ${event.detail}`)
)

Complete example

  • Load the bundles.
  • Style the HTML.
  • Implement an interface with stop, volume, mute, launch and login.
  • Show a call to action when the audio context is not active.
  • Activate on first interaction.
  • Show the interface.
  • Prompt for an auth token and session ID.
  • Start the player.
  • Reconnect as new user on login.
HTML
<!-- Load bundles. -->
<script src="https://syrinscape.com/integration.js"></script>
<script src="https://syrinscape.com/player.js"></script>
<script src="https://syrinscape.com/visualisation.js"></script>

<!-- Styles for the interface. -->
<style>
    .syrinscape {
        align-items: center;
        background-image: url("https://syrinscape.com/static/img/BG-Base.jpg");
        background-size: cover;
        display: flex;
        justify-content: center;
        padding-top: 50%;
        position: relative;
        width: 100%;
    }
    .cta,
    .interface {
        display: none;
        margin-top: -50%;
    }
    .visualisations-container {
        left: 0;
        bottom: 0;
        opacity: 0.7;
        position: absolute;
        width: 100%;
    }
    .visualisations {
        padding-top: 50%;
        position: relative;
        width: 100%;
    }
    .d3-frequency {
        height: 100%;
        left: 0;
        position: absolute;
        top: 0;
        transform: scaleY(-1);
        width: 100%;
    }
    .d3-waveform {
        height: 100%;
        left: 0;
        position: absolute;
        top: 0;
        transform: scaleY(-1);
        width: 100%;
    }
    .d3-waveform svg {
        height: 100%;
        width: 100%;
    }
    .controls {
        display: flex;
        margin: 50px auto;
        position: relative;  /* always above the visualisation */
        width: 75%;
    }
    .local-volume {
        flex-grow: 1;
        margin: 0 1em;
    }
</style>

<!-- Markup for the interface. -->
<div class="syrinscape">
    <div class="cta">
        <button>Activate</button>
    </div>
    <div class="interface">
        <div class="visualisations-container">
            <div class="visualisations">
                <svg class="d3-frequency"></svg>
                <svg class="d3-waveform"></svg>
            </div>
        </div>
        <div class="controls">
            <button onclick="syrinscape.player.controlSystem.stopAll()">
                Stop
            </button>
            <input class="local-volume" type="range" min="0" max="1.5" step="0.01" value="1">
            <button class="mute" onclick="syrinscape.player.audioSystem.toggleMute()">
                Mute
            </button>
            <button onclick="syrinscape.integration.launchAsGameMaster()">
                Launch
            </button>
            <button class="login" onclick="syrinscape.integration.requestAuthToken()">
                Login
            </button>
        </div>
    </div>
</div>

<!-- Configure and start the player. -->
<script>
    (() => {
        // Get controls.
        let localVolume = document.querySelector('.local-volume')
        let mute = document.querySelector('.mute')

        // Set local volume immediately (while adjusting) and last local volume (to
        // restore on unmute) on change.
        localVolume.addEventListener('input', () => {
            syrinscape.player.audioSystem.setLocalVolume(localVolume.value)
        })
        localVolume.addEventListener('change', () => {
            syrinscape.config.lastLocalVolume = localVolume.value
        })

        // Update interface on `setLocalVolume` event.
        syrinscape.events.setLocalVolume.addListener((event) => {
            localVolume.value = event.detail
            if (event.detail === 0) {
                mute.textContent = 'Unmute'
            } else {
                mute.textContent = 'Mute'
            }
        })

        // Show or hide login button when config is updated.
        syrinscape.events.updateConfig.addListener(() => {
            if (syrinscape.config.authenticated) {
                document.querySelector('.login').style.display = 'none'
            } else {
                document.querySelector('.login').style.display = 'revert'
            }
        })

        // Register 'global' frequency and waveform visualiser.
        syrinscape.visualisation.add('global', () => {
            // Get combined frequency and waveform data.
            let data = syrinscape.player.audioEffectSystem.analyser.getData()

            // Visualise frequency and waveform data.
            // You can replace this section to visualise the data any way you like.
            syrinscape.visualisation.d3VisualiseFrequencyData(data, '.d3-frequency')
            syrinscape.visualisation.d3VisualiseWaveformData(data, '.d3-waveform')

            // If analyser is still active, report that visualiser is still active.
            // If no visualisers are active, visualisation loop will be paused.
            return syrinscape.player.audioEffectSystem.analyser.isActive
        })

        syrinscape.player.init({
            async configure() {
                // Audio context. Leave undefined to create one.
                syrinscape.config.audioContext = new AudioContext()

                // Auth token.
                syrinscape.config.token = prompt(
                    'Enter your Syrinscape auth token (from https://syrinscape.com/account/auth-token/). By default an anonymous token has been created for you. Leave blank to create a new one.',
                    syrinscape.config.token,
                )

                // Wait until async token change is finished, because `sessionId` might change.
                await syrinscape.config.sync()

                // Session ID. Use the GM's session ID for all users. Get from
                // `syrinscape.config.sessionId` in the GM's browser.
                syrinscape.config.sessionId = prompt(
                    "Enter the GM's session ID. By default a session has been created with you as the GM.",
                    syrinscape.config.sessionId,
                )
            },

            onActive() {
                // Hide CTA. Show interface.
                document.querySelector('.cta').style.display = 'none'
                document.querySelector('.interface').style.display = 'revert'
            },

            onInactive() {
                // Show CTA. Hide interface.
                document.querySelector('.cta').style.display = 'revert'
                document.querySelector('.interface').style.display = 'none'
            },
        })
    })()
</script>