Skip to content

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 := true

Whether 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: Resource

The .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() -> void

Renders 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.