ChiptuneSongPlayer2D
Extends AudioStreamPlayer2D. Read from the plugin's source on every build.
Music the game steers, from a place on screen, panned and quieter with
distance. An AudioStreamPlayer2D that plays a .music.tres, a song from
the Tracker.
ChiptuneSongPlayer placed in 2D: a radio in a room, a band on a stage, a
boss whose theme follows it. What a game uses, by need, is the same:
Loop or stinger. loop, and
AudioStreamPlayer2D.finished when it is off.
On the beat. beat, bar, looped,
position_bar, pattern_index, pattern_row.
Sections. jump_to_pattern, jump_to_row.
Layers. set_channel_muted, set_channel_volume,
set_channel_solo.
A part’s sound. set_instrument, clear_instrument.
A class of its own, since Godot’s players descend from Node and Node2D and
share no ancestor that has a stream; both hold the same engine, so a song
streams, lands its changes and says its beats the same way on each, and
at a AudioStreamPlayer2D.volume_db of -80 or below renders
nothing and keeps its place; one only out of the listener’s range still
renders. Music heard the same wherever the camera is belongs on
ChiptuneSongPlayer. For 3D, put a ChiptuneAudioStream on Godot’s own AudioStreamPlayer3D: it
plays the song whole, with no beat signals and nothing to steer it.
AudioStreamPlayer2D.seek and the position play takes do
not move the music, since a generator cannot seek: jump_to_pattern
and jump_to_row do.
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
document
@export_custom(PROPERTY_HINT_RESOURCE_TYPE, "ChiptuneSong") var document: ResourceThe .music.tres to play. The picker offers songs alone; a sound
assigned from code still plays, and warns.
loop
@export var loop := trueWhether the song comes round again or plays once: the song’s own
ChiptuneSong.loop until the node sets its own, own_loop,
as on ChiptuneSongPlayer; on with no song. A loop comes round at the end
of the last row; a one-shot plays the last note’s
tail out and stops. Turned over while the music plays, it takes effect at
the end of the pass under way. Declared after document, as on
ChiptuneSongPlayer, so a stored loop = false renders nothing.
own_loop
@export_storage var own_loop := falseWhether loop is this node’s own rather than its song’s: set by
setting loop to anything but the song’s, and stored with the
scene. Set it false to follow the song again.
unique
@export var unique := falseWhether this node plays its own copy of document, so a script’s
edit to it is heard here alone; the scene still stores the file, and a save
while the game runs copies it again. The mixer, tempo and
transpose on the node are its own either way.
tempo
var tempo := 0.0The tempo this node plays the song at, in bpm; 0 is the song’s own
ChiptuneSong.bpm. Lands at the next pattern. Not stored: it is what
a game does to the music, not part of the scene.
transpose
var transpose := 0Semitones added to every note as it starts; 0 plays the song as written. Lands at the next pattern.
Methods
streaming
static func streaming(path: String) -> intHow many live nodes of this class are streaming a song from the file at
path, which no ChiptuneAudioStream counts when it is saved. What
the live path asks before it reloads the file.
rebuild
func rebuild() -> voidTakes document as it stands now, for a change the same frame has
to hear; a change on its own is heard on the next idle frame. It builds the
notes and renders nothing, as on ChiptuneSongPlayer.
jump_to_pattern
func jump_to_pattern(order_index: int, on_bar_end := true) -> voidMoves the music to the start of the pattern at order_index in the
song’s order.
At the next bar line when on_bar_end, so the music turns on the
beat; at the next buffer when not, a fifth of a second from now. Before
play, it is where play starts. A second jump before the first is
heard replaces it: the last one asked for is the one that lands.
jump_to_row
func jump_to_row(order_index: int, row: int) -> voidMoves the music to row row of the pattern at order_index in
the song’s order, at the next buffer, a fifth of a second from now.
What steps the music note to note: jump_to_row(pattern_index(), pattern_row() + 1) is the next row. A row past the end of its
pattern goes on into the entries after it, and a negative one back into
those before, so the step works across a pattern’s edge; past either end of
the song it stops at the first or the last row. A note that started before
the row and would still be sounding is not picked up part way: what plays
from the row on is what starts on it. Before play, it is where play
starts. A second jump before the first is heard replaces it.
position_rows
func position_rows() -> intThe row being heard, counted from the start of the song through its order.
pattern_row
func pattern_row() -> intThe row being heard in its own pattern, from 0, the number
jump_to_row takes beside pattern_index; -1 with no song.
pattern_index
func pattern_index() -> intThe entry of the song’s order being heard, the number
jump_to_pattern takes; -1 with no song.
position_bar
func position_bar() -> intThe bar being heard, from 0 at the start of the song. Every pattern starts a bar, so a pattern whose rows are not a whole number of bars ends on a short one.
set_channel_muted
func set_channel_muted(channel: int, muted: bool) -> voidSilences channel on this node, from the next buffer, without a
click; false lets it sound again, a channel the song itself mutes included.
is_channel_muted
func is_channel_muted(channel: int) -> boolWhether channel is silent on this node: muted here or in the song,
or left out by another channel’s solo.
set_channel_volume
func set_channel_volume(channel: int, volume: float) -> voidSets channel’s volume on this node, 0 to 1, over the song’s.
set_channel_solo
func set_channel_solo(channel: int, solo: bool) -> voidWhile any channel is soloed, only the soloed ones sound.
set_send
func set_send(channel: int, effect: StringName, amount: float, seconds := 0.0) -> boolEases channel’s send to effect, &"echo", to amount,
0 to 1, over seconds: set_send(1, &"echo", 0.3) gives the lead
alone a little echo, and set_send(1, &"echo", 0.8, 2.0) swells it for a
win. Heard from the next buffer. False for an effect or a channel this
version does not have.
set_instrument
func set_instrument(channel: int, instrument: ChiptuneInstrument) -> voidPlays channel’s notes on instrument in place of the one the
song names, from the next pattern. An instrument the song never held works.
clear_instrument
func clear_instrument(channel: int) -> voidGives channel back the instruments the song names.
Signals
document_changed
signal document_changedEmitted when a change to document is heard: an edit to the song, a
save pushed from the dock, or rebuild. While the music plays that
is at the next pattern, which is where the change lands.
beat
signal beat(index: int)Emitted on each beat as it is heard, index counting from 0 at the
bar line. A beat is ChiptuneSong.rows_per_beat rows from the start
of a pattern.
bar
signal bar(index: int)Emitted on each bar line as it is heard, index as
position_bar counts it. A pattern starts a bar.
looped
signal loopedEmitted as the loop comes round to the first bar, before that bar’s
bar.
Constants
BEATS_PER_BAR
const BEATS_PER_BAR := _SongEngine.BEATS_PER_BARBeats in a bar, for bar and position_bar. A song has no
time signature; four is what the tracker’s grid and the live path’s bar
already count in.
STREAM_BUFFER_SECONDS
const STREAM_BUFFER_SECONDS := _SongEngine.STREAM_BUFFER_SECONDSHow far ahead of the ear the generator is filled. Long enough that a frame which runs long does not starve it, short enough that a mute is heard at once; the tracker holds the same.
JamChip, by GlitterSnack. MIT licence. Built from the source on every merge. Its artwork and screenshots are made with JamChip.