The media system
Schedule skybox and music @media labels defined in MAST.
Overview
Media labels are declared in MAST with the @media/kind/path "Display" syntax and discovered at runtime. media_schedule and media_schedule_random look up registered labels by kind ("skybox" or "music") and apply them via the engine's set_sky_box / set_music_folder calls, then run the label as a sub-task.
The ID parameter targets a specific ship or client; 0 (the default) applies the change globally on the server.
Use skybox_schedule / music_schedule as convenient wrappers when you already know the media name; use the _random variants to pick from all registered labels of that kind automatically.
A skybox no longer picks the music
It used to. Scheduling a media label runs its body, and nothing else ever chose a
track, so every @media/skybox label ended in if client_id==0: music_schedule_random()
— copied into thirty A28 labels, eight LegendaryMissions ones, and every mission that
inlined them. A skybox label with an empty body left a game silent.
The two are now independent. A skybox sets the sky; music is selected on its own, and a skybox label body is free for things that are genuinely sky-specific.
Choosing the music
MUSIC_SELECT names a bank — a bare folder name, an @media/music label's display name,
or "random". It resolves strongest-first:
| Source | Beats |
|---|---|
var.MUSIC_SELECT= on the engine command line |
everything |
COSMOS_SETTINGS environment JSON |
the profile and below |
profiles/<name>.yaml |
the mission and below |
the mission's settings.yaml |
mods and the built-in |
a mod's settings_set_mod_default("MUSIC_SELECT", ...) |
the built-in |
the library built-in, "random" |
— |
At runtime the operator's console dropdown and a map's Defaults: MUSIC_SELECT outrank
the value above, in that order.
A mission that pins music in its own settings.yaml locks every mod out of that key —
that is what "explicit" means here. Leave it unset unless you mean it.
Discovering what is available
music_get_list() / skybox_get_list() return the labels that are actually usable: a
missing folder or a false if condition drops the label, so a picker can never offer
something scheduling would refuse. This is what the LegendaryMissions server console builds
its Music dropdown from, which is why a mod's banks appear there without that console
knowing the mod exists.
music_find(spec) resolves an index, a path, a display name, or an unambiguous substring —
the same matcher maps_find uses, so a name means the same thing on a command line, in a
settings file and in a dropdown. An ambiguous spec returns None rather than guessing.
End-of-game stings
A bank is not one track: it holds start, main, victory and failure alongside the
low/ medium/ high/ tiers. music_play_sting(name) plays one out of the bank that is
currently playing:
music_play_sting("victory")
Missions used to write sbs.play_music_file(0, "music/default/victory") — the literal
appeared about forty times across the mission repos — and the default in it was
hardcoded, so a game scored to Artemis2 or to a mod's soundtrack still ended on the stock
sting. The bank was never the mission's to know.
The fallback is per file, not per bank: a mod's bank may legitimately ship its own
main and no victory, and losing its whole soundtrack over one missing file would be the
wrong trade — so only that one file falls back to default.
Music must be a bare name, and that is the engine's rule
set_sky_box takes a path in any spelling. set_music_folder does not: it resolves a
bare name under data/audio/music/, and handing it a path does not fail — it hangs the
engine. The call never returns (measured in missions/music_probe, engine 1.3.6).
So a bank shipped in a mod's media pack is found by sbs_utils, and then deliberately not
handed over: the label logs a warning naming the folder it found and plays default. Copy
the folder into data/audio/music/ to use it today. When an engine build is measured to
survive a path, set MUSIC_ENGINE_ACCEPTS_PATHS: true and packs work directly.
Quick example
@media/skybox/nebula "Nebula"
@media/skybox/deep_space "Deep Space"
@media/music/battle "Battle Music"
== setup ==
skybox_schedule_random()
# "", None or "random" picks at random; anything else is resolved by name,
# and a spec that matches nothing warns and falls back rather than going silent.
music_schedule_select(MUSIC_SELECT)
from sbs_utils.procedural.media import (
skybox_schedule, skybox_schedule_random,
music_schedule, music_schedule_random, music_schedule_select,
music_get_list, music_find, music_current,
)
# Pick a specific skybox
skybox_schedule("nebula")
# Pick a random skybox
skybox_schedule_random()
# Whatever MUSIC_SELECT asked for, including "random"
music_schedule_select(settings_get_defaults().get("MUSIC_SELECT"))
# Ship-specific music (pass ship ID)
music_schedule("battle", ID=ship_id)
# What a picker offers, and what is playing now
[(m.path, m.display_name) for m in music_get_list()]
music_current() # -> the bank name, "default" until something schedules one
API
media_find(kind, spec)
Find one @media label from a loose spec - an index, a path, a display name, or
an unambiguous substring of either.
Uses the same matcher as maps_find (maps.label_find_by_spec) so a name typed
into a settings file, a launch argument or a dropdown resolves the one way everywhere.
An AMBIGUOUS spec returns None rather than guessing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
|
required |
spec
|
index, path, display name, or substring. |
required |
Returns:
| Type | Description |
|---|---|
|
MediaLabel | None |
media_get_list(kind)
Every usable @media label of a kind, for a picker or a report.
"Usable" is the point: labels whose art or audio folder is missing are dropped, and so
are labels whose if condition is false - the same test the random pick applies, so a
dropdown can never offer something scheduling would refuse. Sorted by declaration order
so a list is stable between runs.
This is the function every mod has been hand-rolling. a28_skyboxes.py,
venus_skies.py and a28_verify.py each reach into MediaLabel.folders directly
and re-implement the filtering, which is how one of them can quietly disagree with what
the game will actually pick.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
|
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
|
media_play_audio(file, ids_or_obj=0, volume=1.0, pitch=1.0)
Play an audio file NOW - a stinger, a voice line, an alarm.
Promoted out of HereThereBeMonsters, which called sbs.play_audio_file raw in
seven places behind its own enable flag. The engine call needs a path relative to
the Artemis audio directory, which is what get_mission_audio_file builds - a
mission should name its file the way it stores it (audio/briefing_01) and never
have to know that.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file
|
str
|
the file, relative to the mission folder. |
required |
ids_or_obj
|
int
|
a client id, or 0 (the default) for everyone. |
0
|
volume
|
float
|
0-1. |
1.0
|
pitch
|
float
|
1.0 is unshifted. |
1.0
|
Returns:
| Name | Type | Description |
|---|---|---|
bool |
whether the engine was asked to play it. Silent - not an exception - when |
|
|
there is no engine or the mission disabled audio, because a missing sound must |
||
|
never end the task that was telling the story. |
media_read_relative_file(file)
Read a file sitting beside the .mast that is running - from the addon's zip when that .mast came from a mastlib, else from its folder.
EVERY failure is logged and named. It returns None on failure, and a None flows
straight into document_get_amd_file(content=None), which yields an empty tree that
renders as a flat, contentless page - a screen that looks broken while saying
nothing about why. Reported as: a document whose headings "stopped being
recognized", running a mission that gets this addon from a mastlib.
media_schedule(kind, name, ID=0)
Schedule a named @media label of the given kind.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
Media kind, e.g. |
required |
name
|
str | MediaLabel
|
Media path name or a |
required |
ID
|
int
|
Ship or client ID; |
0
|
Returns:
| Type | Description |
|---|---|
|
Label | None: The scheduled label, or |
media_schedule_random(kind, ID=0)
Schedule a randomly chosen @media label of the given kind.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
Media kind, e.g. |
required |
ID
|
int
|
Ship or client ID; |
0
|
Returns:
| Type | Description |
|---|---|
|
Label | None: The scheduled media label, or |
music_bank_has(bank, stinger)
Whether a bank carries a named one-shot ("victory", "failure", ...).
A bank is conventionally start/main/victory/failure.ogg plus low/ medium/ high/,
but nothing enforces it, so a mod's bank may legitimately omit one. Asking lets a caller
fall back to default for that ONE file instead of abandoning the mod's music.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bank
|
str
|
a bank name, e.g. from :func: |
required |
stinger
|
str
|
the file, without |
required |
Returns:
| Type | Description |
|---|---|
|
bool |
music_current(ID=0)
The music bank currently playing - the bare folder name last given to the engine.
"default" until something schedules music, because that is what the engine plays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ID
|
int
|
ship or client id; |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
the bank name. |
music_find(spec)
Find one music @media label. See :func:media_find.
music_get_list()
Every usable music @media label. See :func:media_get_list.
music_play_sting(name, ids_or_obj=0)
Play a one-shot out of the bank that is CURRENTLY PLAYING - victory, failure,
start, main.
Missions had no way to say this. sbs.play_music_file(0, "music/default/victory") is
the literal that appears ~40 times across the mission repos, and the default in it is
hardcoded - so a game scored to Artemis2, or to a mod's own soundtrack, still ended on
the stock sting. The bank was never the mission's to know; it is whatever MUSIC_SELECT,
a map, or the operator chose.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
the one-shot, without |
required |
ids_or_obj
|
int
|
a ship or client id, or 0 (the default) for everyone. |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
bool |
whether the engine was asked to play it. |
Falls back to the default bank PER FILE, not per bank. A bank is conventionally
start/main/victory/failure.ogg plus low/ medium/ high/, but nothing enforces that,
so a mod's bank may legitimately ship its own main and no victory - and losing the
mod's whole soundtrack over one missing file would be the wrong trade.
music_reset()
Forget which bank is playing, and re-arm the empty-media warning. Called from
reset_mission_state - run 2 has its own labels and deserves its own warning.
music_schedule(name, ID=0)
Schedule a specific music track by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Music media path name. |
required |
ID
|
int
|
Ship or client ID; |
0
|
music_schedule_random(ID=0)
Schedule a randomly chosen music @media label.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ID
|
int
|
Ship or client ID; |
0
|
music_schedule_select(spec, ID=0)
Schedule the music a setting, a map or an operator ASKED for.
This is what replaced "the skybox label picks the music". Every skybox label used to
end in if client_id==0: music_schedule_random() - copied into thirty A28 labels,
eight LM ones and every mission that inlined them - because scheduling a skybox ran its
body and nothing else ever chose a track. A skybox now sets the sky and nothing else.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
|
required | |
ID
|
int
|
ship or client id; |
0
|
Returns:
| Type | Description |
|---|---|
|
MediaLabel | None: what was scheduled, or None when there is no music at all. |
A spec that matches nothing WARNS BY NAME and falls back to random. Silence there was
the tempting choice and the wrong one: MUSIC_SELECT: Artmeis2 would play a random
track, which is indistinguishable from working.
skybox_find(spec)
Find one skybox @media label. See :func:media_find.
skybox_get_list()
Every usable skybox @media label. See :func:media_get_list.
skybox_schedule(name, ID=0)
Schedule a specific skybox by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Skybox media path name. |
required |
ID
|
int
|
Ship or client ID; |
0
|
skybox_schedule_random(ID=0)
Schedule a randomly chosen skybox @media label.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ID
|
int
|
Ship or client ID; |
0
|