ChiptuneMusicPlayer
Extends AudioStreamPlayer. Read from the plugin's source on every build.
An AudioStreamPlayer that plays a song, with a loop that lands on the beat.
ChiptunePlayer is the node for a sound. This is the node for music, and
the difference is the loop: a song has bars, so it has a place the music
ends that is not the place the audio ends, and only something that knows it
is holding a song can tell them apart. Give it a .music.tres, leave
loop on, and the last bar runs into the first.
With loop off it plays the song once, tail and all, and emits
AudioStreamPlayer.finished — which is what a stinger, a jingle or a
game-over theme wants.
There is no 2D or 3D version. Those exist for audio that comes from somewhere in the scene, and music does not come from anywhere.
stream is hidden and not saved. It is built from document
and loop, so showing it would be a property that can disagree with
the two it is made of.
Properties
loop
@export var loop := trueWhether the song comes round again or plays once.
On by default, because music that stops after one pass is almost never what a scene holding a song wants, and the jam’s game reached the plugin for the first time by writing that loop by hand.
It changes what is rendered rather than only how it is played: a looping render is the song played on a loop until the audio stops changing, so the trailing row a held note gets is inside the music rather than between the last bar and the first.
Declared before document on purpose. A scene assigns exported
properties in the order they are declared, so a scene storing loop = false
sets it here while there is nothing to render, and the document that follows
is rendered once. The other way round it was rendered twice — a full looping
render thrown away, 253 ms of it on a sixteen-row song.
document
@export_custom(PROPERTY_HINT_RESOURCE_TYPE, "ChiptuneSong") var document: ResourceThe .music.tres to play.
Hinted to ChiptuneSong alone: a sound belongs on a ChiptunePlayer, which
is where the sound-shaped controls are. The hint is an editor filter and not
the contract — anything answering render() still plays when it is assigned
from code.
Methods
rerender
func rerender() -> voidRenders document again, so a change made to the song from a script
is heard on the next AudioStreamPlayer.play.
A song is an ordinary resource and nothing watches its fields: a script that
sets ChiptuneSong.bpm, or mutes a channel through
ChiptuneMixer.set_muted, has changed the song and not the audio
until this is called.
Rendered with loop as it stands, into the stream the node already
has, so a looping song comes back looping and anything holding the stream
keeps the resource it was handed. A song is the expensive render — about
245 ms per second of audio, once more per pass a loop takes to settle — so
this is a call for a moment the music can change, not one for every frame.
“What it costs” in the module README has the numbers.
Constants
DERIVED_PROPERTIES
const DERIVED_PROPERTIES: Array[StringName] = [&"stream"]Neither shown nor stored. stream is built from document,
so showing it would be two properties that can disagree and storing it would
put the rendered audio in somebody’s scene file.
JamChip 0.1.0, by GlitterSnack. MIT licence. Built from the source on every merge, artwork and screenshots included, with JamChip.