Skip to content

Embedding the tiny player

Igor Zinken edited this page Jul 8, 2023 · 19 revisions

The Efflux Tiny Player can be used to play back an Efflux song using a lightweight, headless JavaScript API which can be embedded in a web page or used in demos. The Tiny Player reuses the same code as the full blown Efflux application to ensure songs and instruments sound the same, have the same features and remain in sync whenever updates occur.

The Tiny Player is aimed to be as small as possible and while it reuses the same code as Efflux itself, it does not include Vue nor its reactivity model. Error messages are also kept to a bare minimum. Efforts to squeeze every last byte out of the package are welcomed, but should not come at the expense of readability and maintainability of the shared code. Keep in mind that maintainable inline Tiny Player code can be written while still being minified through the build process. Import used functions instead of entire namespaces to benefit from tree shaking. If your target environment is controlled you can change transpilation settings, meaning no polyfills are added, greatly reducing the output file size.

The build configuration of the Tiny Player build is located in ./vite.config.tiny.js.

Tiny Player is still under development

The current status of the Tiny Player is that it can load and play back songs reusing all of Efflux' audio capabilities. It is however still quite large in file size and further optimizations will be done to minimize the total output size. Current size gZipped is 94.82 Kb.

The file size is currently impacted by https://github.com/igorski/efflux-tracker/issues/54 (though it can be omitted by uncommenting the rollupOptions.externals-definition, bringing down the size to 26.31 Kb gZipped.

Those of the demo scene may have success with using "PNG-style" compression algorithms to shrink down the size of both the Tiny Player as well as the song, given that an Efflux song was used within a 64 Kb WebGL demo.

Building the Tiny Player

Simply execute the following command from the repository root (after having resolved all NPM dependencies) :

npm run tiny

after which ./dist/tiny.iife.js is created. This file can be included in a webpage and will expose an Object called eTiny onto Window.

Tiny Player API

The eTiny Object exposes the following methods:

window.eTiny = {
    l(
        xtkObject: object | string,
        optHandler?: ( c: number, t: number ) => void,
        optAudioContext?: BaseAudioContext
    ): Promise<boolean>,
    p(): void,
    s(): void,
    j( patternIndex: number ): void,
    on({ f, i, a, mp, t }): TinyEvent,
    off( generatedEventObject: TinyEvent ): void,
    a(): BaseAudioContext
};

l (load) : is invoked with the definition of an .XTK song, either as an Object or stringified JSON. It is your responsibility to fetch / create the song content outside of the Tiny Player. This method will then generate the audio events and setup the instruments and modules for playback. Boolean result / logged Error will indicate whether the player is ready for playback. The only reason for failure would be audio playback not being supported in the environment, or given song being invalid. Note that xtkObject here is not a binary file, but a JSON Object (use "Export project (.json)" within Efflux).

The method accepts an optional secondary argument optHandler which is a function that is invoked whenever the sequencer encounters an external event instruction (defined by code XE). The callback will receive an Object structure { c: number, t: number } where c is an 8-bit value (0x00-0xFF range) defined for the XE event and t is the offset (in seconds) of the event relative to the song start. It's up to you to map these values to a meaningful action. You can use this to sync an event to the sequence of your song, timed at the sample level.

The third argument is an optional AudioContext instance. When supplied, this AudioContext should not be in suspended state (e.g. initialized after a user interaction event as otherwise audio will be muted depending on browser / device). When null, the AudioContext will be created automatically, though note that for reasons mentioned above, this method should be called directly on a user interaction (e.g. button click).

p (play) : starts playback of the song.

s (stop) : stops playback of the song.

j (jump) : jumps to a pattern of choice. Accepts Number value (will be clamped within song pattern range)

on (noteOn) : plays a note for a given instrument. Returns unique event Object generated to synthesize the event (to be passed to the off method to halt playback). Accepts the following Object:

{
    f: number,  // frequency of note in Hz
    i: number,  // index of instrument (defined in loaded song) to use
    a: number,  // number of event action (defaults to 1 for 'noteOn')
    t: number,  // number, audioContext time at which to start playback, defaults to instant playback on invocation
    mp: Object  // Object, optional defines module parameter change   
}

off (noteOff) : stops playing note started with on(). Argument is the Object generated and returned by on().

a (audioContext) : retrieve the generated AudioContext. This can be used in case you want to hook into the audio (for instance: to create an audio visualizer).

Testing Tiny player

Build the Tiny player as described above. Embed the generated script into a web page, open it and open the dev console. The easiest way to get a test song is to copy the contents of 01_omf2097.json to the clipboard. In the dev console:

const songData = <PASTE_CLIPBOARD_CONTENTS>;

And then:

eTiny.l( songData ).then(() => eTiny.p() );