Skip to content

ChiptuneSfxPlayer

Extends AudioStreamPlayer. Read from the plugin's source on every build.

A sound the game fires, like a jump, a hit or a coin. An AudioStreamPlayer that plays a .sfx.tres from the SFX editor.

Set document and it plays. What a game uses, by need:

Fire it. play, or play_at for a pitch per shot: a coin that climbs a step with each pickup.

Repeats that differ. variants takes that many variations in turn, and pitch_variance rolls the pitch of each play. The engine picks both on every play, from autoplay or an animation track too.

The same variety every run. seed picks which variations.

One node’s own copy. unique gives this node its own copy of the document, so a script’s edit to it is heard here alone.

ChiptuneSfxPlayer2D is this node placed in 2D. A .music.tres plays on ChiptuneSongPlayer. For 3D, or any other slot, put a ChiptuneAudioStream on the player: Godot’s own AudioStreamPlayer3D, say.

stream is built from document, and is neither shown nor saved.

play() and stop() are AudioStreamPlayer’s.

Properties

document

@export_custom(PROPERTY_HINT_RESOURCE_TYPE, "ChiptuneSfxSpec") var document: Resource

The .sfx.tres to play.

Hinted to ChiptuneSfxSpec alone so the picker and its Quick Load search offer sounds and not every resource in the project. A song belongs on a ChiptuneSongPlayer, which has the loop. The hint is an editor filter and not the contract: anything answering render() still plays when it is assigned from code or written into a scene by hand, a song included. See ChiptuneAudioStream.

unique

@export var unique := false

Whether this node plays its own copy of document, apart from the file every other node of it shares.

Off, a script’s edit to document is heard on every node holding the same file. On, the copy is made when the document is assigned, so document.instrument.decay = 0.1 changes this node alone; the scene still stores the file. A save of the file while the game runs copies it again, and the node’s own edits go with it. In the editor the node holds the file, so the inspector shows what the scene stores.

Declared after document, so the file comes first in the inspector. A scene the editor saves sets it after the document has rendered, and the copy it makes then sounds the same, so it is held without a second render.

pitch_variance

@export_range(0, 12) var pitch_variance := 0

How far each play may wander from the pitch it was asked for, in semitones either way, rolled per play. Zero plays it where it is.

Nothing is rendered for it, so it is free; a sound played higher is also shorter, as any sample is.

variants

@export_range(1, 8) var variants := 1

How many versions of the sound this node takes in turn, one per play: at one, the document as it is; above one, that many variations on it, each a ChiptuneSfx.nudge of the document on the document’s note, rendered once when it loads and again when it changes.

Each variant keeps the wave, the arpeggio and the note and nudges every other number a little, so a hit is not the same hit twice.

seed

@export var seed := 0

Which variations variants makes: the same seed is the same set of sounds, taken in the same order.

Methods

play_at

func play_at(pitch: int, from_position := 0.0) -> void

Plays the sound at the MIDI note pitch, sixty being middle C, by setting AudioStreamPlayer.pitch_scale from how far it is from the document’s own ChiptuneSfxSpec.pitch. Nothing is rendered, so a coin that climbs a step per pickup costs what play does.

The pitch_scale goes back to what it was when the sound ends, or at the next play() made through this node’s script. A play() the engine takes directly while the sound is still sounding, from a variable typed ChiptuneSfxPlayer or autoplay, plays at the note asked for here.

play

func play(from_position := 0.0) -> void

AudioStreamPlayer.play, back at the document’s pitch after a play_at. Only that: the variants and the spread are picked by the engine, on every play.

rebuild

func rebuild() -> void

Renders document now, in this frame, and leaves a playing node playing from where it had got to.

A change to the document is heard without this: the stream follows the document’s changed and renders once on the next idle frame, however many fields moved. This is for the frame the change was made in, a pitch set and the sound played on the same line.

The audio is written into the stream the node already has, so anything holding this node’s stream keeps the resource it was handed. It is a full render on the main thread: see “What it costs” in the module README before calling it every frame.

Signals

document_changed

signal document_changed

Emitted after new audio for document lands in this node’s stream, whatever asked for it: the document changing, rebuild, a save.

JamChip, by GlitterSnack. MIT licence. Built from the source on every merge. Its artwork and screenshots are made with JamChip.