Modules / Sound

audioplugin

Sounds the rules raise, on a ledger, played once each on every page where they were raised.

Putting it in a scene

The scene's sim lists the audio kind from #plugins/audio/audio.sim.ts, and the scene has one { "is": "audio" } actor, the ledger. List the ledger's actor before any actor that raises a sound in its own before. Its prefab is exactly the three fields below. The sounds are sound entries of the loader catalogue.

"plugins": ["loader", "scenemanager", "player", "render2d", "audio"],
"world": {
  "audio": {
    "hold": 0.25,
    "screen": { "model": "linear", "ref": 0, "max": 256, "rolloff": 1 },
    "cues": {
      "shot": { "sounds": ["shot1", "shot2"], "gain": 0.8, "rate": [0.95, 1.05], "range": 400, "cut": true }
    },
    "loops": {
      "rain": { "sound": "rain", "field": { "kind": "skydome3d", "path": "mix.rain" }, "over": [0, 1], "gain": [0, 0.6] },
      "music": { "named": { "kind": "game", "path": "song" }, "fade": 2, "loop": { "town": [8, 64] } }
    },
    "volume": { "level": "volume", "muted": "muted" }
  }
},
"prefabs": {
  "audio": { "step": 0, "next": 1, "live": [] }
}

A rule raises a sound with sound:

import { sound } from '#plugins/audio/audio.sim.ts'

sound(w, 'shot', [120, 80])

The listener is the renderer the scene has: render3d's camera, else render2d's view of a world (a scene with world.render2d), else none. With none, every sound plays unplaced. Under render3d the listener takes the camera's position and orientation each frame. Node has no Web Audio, so a client made there has no context and plays nothing; audio can still be listed.

Properties

Read from world.audio. A child scene inherits them from the manager scene.

.hold : number

Seconds an entry stays on the ledger. A page reads only the last packet's rows each frame, so an entry that stayed one step would be missed whenever two packets arrive in one frame. Sixty steps a second with a hold of 0.25 carries an entry in 15 packets when it was raised in a before and in 14 when raised in an act, which a seat's message runs before any before.

Figure 1. With hold 0.25 s and 60 steps a second, the packets that carry an entry raised in a before and one raised in an act. The ledger drops an entry once its gap times the step length reaches the hold.

.space : { model, ref, max, rolloff }

How a sound placed in the world fades with distance where render3d's camera listens, in metres. model is "linear", "inverse" or "exponential", the distance model of a Web Audio panner. ref is the distance inside which the sound is at full volume, max the distance the fade ends at, and rolloff how fast it falls. A scene that places sounds under render3d gives it.

.screen : { model, ref, max, rolloff }

The same four numbers where render2d's view listens, with the distance in canvas pixels from the nearest edge of the view. A sound on screen is at full volume and one off it fades with how far off it is. A scene that places sounds under render2d gives it.

Figure 2. A placed sound under render2d on a 640 pixel wide canvas, with screen set to linear, ref 0, max 256, rolloff 1. Its pan runs from −1 at the left edge to 1 at the right edge and holds at those past the edges. Its gain is 1 anywhere on the view and falls to 0 at 256 pixels off it.

The pan is the sound's x across the canvas width, from the engine prefab's width: −1 at the left edge, 0 in the middle, 1 at the right edge.

.cues : { [name]: Cue }

The sounds a rule can raise, by name. A cue is:

KeyMeaning
soundsLoader sound names. Each entry plays one of them, picked by its id, so every seat hears the same one.
gainThe cue's level, 1 being the sound as recorded.
rateOptional [lo, hi] playback rate. Each entry plays at a rate picked in that range by its id. Without it the rate is 1.
rangeOptional distance this cue fades out over, in place of the listener's max, in metres under render3d and pixels under render2d.
cutOptional. When true, the cue stops the last sound still playing from the same row.

.loops : { [name]: Loop }

Sounds that repeat, by name. Each starts once its sound has loaded. There are three kinds.

KindMeaning
{ sound, field, over, gain, loop? }Plays sound at a level that follows a number. field is { kind, path }: path is fields joined by dots, as in "mix.rain", read off the first row of that kind, and 0 while there is no such row. The level runs from gain[0] to gain[1] as the number runs from over[0] to over[1], held at the ends.
{ sound, listener: "height", over, gain, loop? }The same, with the number being the listener's height: the render3d camera's y in metres, and 0 under any other listener.
{ named, fade, loop? }Music. named is { kind, path } as above and the field holds the name of a sound, or nothing. When the name changes, the old sound fades out and the new one fades in from its start, each over fade seconds.

loop is the part of the sound that repeats, [start, end] in seconds. The sound plays from 0 and, once it reaches end, goes back to start for good, as a song with an intro does. A named loop gives one per sound, as { "town": [8, 64] }. A sound without one repeats whole. A level follows its number smoothly, over one step.

.volume : { level, muted }

The names of the store keys the volume is read from: level holds the volume, and muted silences everything while true. Without it, or in a scene without a store, the volume is 1. Every sound goes out through it.

The ledger

The one audio row. Its prefab holds these three fields and nothing else, because every field goes to every seat in every packet.

FieldMeaning
stepThe steps the ledger has counted.
nextThe id the next entry gets.
liveThe entries, each { id, cue, at?, from?, step }: the cue's name, where it sounds, the row it comes from, and the ledger's step it was raised on. An entry leaves the list hold seconds later.

A sound raised from a player row goes on that row's own voiced list instead, which leaves after hold the same way. The seat's own player row is drawn from the copy that runs ahead, so its sounds play the frame after the key.

How a sound plays

Each entry plays once on every page, the first frame the page sees it. The entries a list holds when a page first sees that list are never played, so a seat that arrives late hears nothing raised before it came. An entry seen while the sound is still loading, or before the first click or key press has unlocked the browser's audio, is dropped without playing.

A sound has a place from its from row's pos, followed while that row exists and kept where it last was once the row is gone, or else from its at. With neither it plays unplaced. Under render3d a placed sound plays from that point in space with space's numbers. Under render2d the point is projected onto the canvas through the camera of that frame, and its pan and gain follow screen.

Methods

Rules

From #plugins/audio/audio.sim.ts.

sound( w, cue, at?, from? )

Raises a cue: appends an entry with the next id. at is the point it sounds at and from the id of the row it comes from. Given from without at, at is that row's pos as it stands, so a page that sees the entry after the row has gone still plays it where it was raised. With neither, the sound has no place. Two calls in one step are two sounds.

chance( w, id, odds ) : boolean

Whether something happens to row id this step, with probability odds, 0 to 1. The answer comes from the id and the ledger's step, so it is the same on every machine and does not move the scene's random numbers.

Acts

A rule calls it with w.call.

play( cue, x, y, z, from )

Does what sound does, for a rule that reaches the ledger through another plugin's row. Its arguments are numbers, a string and null: the point is three numbers, or null for none, and from is a row id or null. No seat may send it.

On the page

Through plugins.audio, from an actor or another plugin.

audition( plugins, what ) : Promise<void>

Plays once, unplaced and now, a catalogue sound by its name or a cue object as it would be written in cues. It loads the sound first.

stream( { rate, lead, most } ) : { push, close }

Opens a stream for sound the page makes as it goes, such as the samples of an emulated sound chip, which come a frame's worth at a time. rate is samples a second, lead the seconds a block that arrives after the last has run out is started ahead of the audio clock, so a slightly late next block still joins it, and most the seconds ahead of the clock beyond which a block is dropped, so a page whose frames run fast never builds up delay. Blocks play unplaced, through the volume, and join without a gap.

push( samples: Float32Array ) plays one block of mono samples, starting where the last block ends. It does nothing while the browser's audio is locked or when there is no context. close() stops the blocks still playing.

close( )

Stops every sound and loop, removes the page's click and key listeners, and closes the context.

Properties of the module

context : AudioContext | undefined

The page's one Web Audio context, undefined where there is no Web Audio, as in Node. It resumes on the first pointer press or key press on the page if the browser has not already let it run.

Mods

audio takes no mods. It gives one.

  • cutscene
    audio.cutscene.ts: a scene whose world.cutscene.mods is { "play": "audio" } sounds the cutscene's sound and voice cues through the ledger. The mod's play( w, cue, at?, from? ) raises the cue at the point at, or from the row from, or with no place.

Used by

  • cutscene
    Its sound and voice cues play through the ledger.

In the editor

The editor auditions a catalogue sound or a cue with audition.

In the guide