Cassette Music - EverestAPI/Resources GitHub Wiki
Cassette Music
Cassette music has a reputation for being difficult and confusing to work with, so this guide aims to demystify and clarify how cassette music works. It will go over how the cassette music system works, how to create songs for the cassette system, sequence them in FMOD, and how to implement them in Lönn. This guide will also include some best practices when working with it and some advanced techniques to showcase the possibilities of the system.
This guide also aims to be a definitive explanation of everything there is to know about cassette music. If there are any errors, missing facts, or clarifications needed, please reach out to @verticalzone
in the Celeste Discord, or update this page yourself!
How Cassette Music Works
In FMOD
Under the hood, cassette music actually operates in fundamentally the same way as all music events in FMOD. The key difference is that instead of using the main playhead and timeline in FMOD, cassette music uses a parameter and parameter sheet that take on the same roles. This allows for Celeste itself to control the playback speed of the song in the game code (rather than the FMOD bank controlling this), which in turn lets Celeste adjust the cassette blocks with the same code; ensuring they never desync from the music.
This parameter is called sixteenth_note
in all vanilla cassette music, and is capped at values between 1 and 257, although only 1-256 are actually used by Celeste. Each value represents the next moment in time, at a temporal "resolution" of a 16th note, or four notes per "beat" of the music. The parameter's sheet then becomes the de facto timeline, where scrolling to higher values for the parameter is akin to scrolling to later timecodes on the main timeline.
Unlike the main timeline, however, the parameter sheet runs at a monumentally lower temporal resolution. As such, rather than streaming audio at the sample rate, as the main timeline does, the parameter sheet functions like a sequencer or music box instead.
When Celeste wants to play cassette music, it will manually step the parameter from 1 to 2, to 3, etc., at a fixed speed, acting as the playhead. The individual notes of the song are placed on this sheet at the right time, so that they play once the "playhead" crosses over them, like the strikers of a music box reaching each pin, or the playhead in a DAW reaching MIDI data on the piano roll.
Like a music box, the "notes" on the parameter sheet hold no temporal information within themselves, only instrument and pitch. As they are not on the main timeline, the audio files that make up these notes are played as "asynchronous" clips by FMOD. Like a sampler, they begin playing their audio file when they are triggered, completely independent of what the main playhead (or parameter playhead, for that matter) is doing.
Beyond what is happening in the parameter sheet, the FMOD event for each cassette song does still have its main timeline. To prevent the event from cancelling itself early, each cassette song features a loop region to prevent the main playhead from ever reaching the end of the event and stopping it.
(There is also a snapshot that is triggered by the main playhead, which lowers the volume of all ambience layers, to ensure they don't obscure the music at all. When Celeste stops a cassette song, it will change a parameter called ahdsr_controller
from 1.0 to 0.0, which enables a transition region that allows the playhead to escape the loop and cancel the snapshot. The snapshot itself uses an AHDSR with a 2 second attack and release, so that the effects of the snapshot comes in/out gradually. So, when the snapshot ends, the ambience will fade back in slowly, rather than instantly. This AHDSR also delays the event from ending until the ambience has fully faded back in.)
In Celeste/Everest
When Madeline enters a room with at least one cassette block, Celeste will fade out the current music (without pausing it), and begin the "snap" sound effect to "count in" the song. After four snaps, Celeste will begin to play the cassette music by incrementing the sixteenth_note
parameter at a steady pace. By default, it will update it every 10 frames, resulting in the music playing at a tempo of quarter note = 90 bpm, and the cassette blocks will update every two beats, or eight steps of the parameter, accompanied by a "snap" sound effect. By default, the parameter will immediately stop updating if Madeline begins to transition to another room, even if that room also has cassette blocks. Everest adds the ability to remove this pause, allowing the parameter to update during room transitions, so that the music plays continually.
The number of blocks to cycle through is determined by the highest index block in the room, with a minimum of two colours (blue and pink). Which block is active when is not tied to the music, but rather the game tries to ensure that blue is activated on the first update after Madeline enters a room or respawns. This is why sometimes the blue blocks will enable on beat one, and sometimes on beat three in vanille cassette music. If Madeline collects a Cassette Tape, the system is disabled by deactivating all cassette blocks, ending the cassette music event, and fading the main music back in.
Unlike most game logic, this system does not get paused during freeze frames, to ensure the music does not "hitch" every time a freeze frame occurs, and to not result in players mis-timing their inputs. The way this is implemented is slightly faulty, but mostly a non-issue. However, lag frames still affect the system. If the game is lagging, the parameter cannot be properly updated to play the next note until the lag is finished. When lag occurs, Celeste will account for it by quickly playing through the lost steps once the game stops lagging, until it is caught up to where it "should" be. This tends to happen whenever Madeline respawns, especially in larger/laggier rooms, and the "sped up" part tends to be contained during Madeline's respawn animation, so it rarely affects gameplay.
Upon the release of Farewell, an option was added to have rooms with cassette blocks use the main music, rather than the cassette music specified in the metadata. This mode also omits the "snap" sound effects on top of the music. Despite this, the "count-in" section still occurs before the parameter begins updating, resulting in four beats of silence where the original system would have played four snaps. When in this mode, rather than ending the event upon collecting a cassette, the sixteenth_note
parameter is stopped instead.
Creating Cassette Music
Map Making: How to Implement Cassette Music in Lönn
To implement cassette music in a map, simply set the Cassette Song
metadata to the desired FMOD event, just as one would implement the main music. The vanilla events will appear in the dropdown in Lönn, and the full event path is needed for custom events, just like the main music. If no song is set, Celeste will default to Chapter 1's cassette song.
To use the alternate method seen in Farewell, where cassette rooms play the main music and omit the snap sound effects, set the Cassette Song
to the character "-" without the quotes. This single character tells Celeste to use the main music instead for cassette rooms.
Lönn features several metadata options to customize cassette music in a map. As these are set in metadata, they are relatively rigid and unchangeable mid-map.
Max Beats
tells Celeste how many steps there are for the sixteenth_note
parameter. Once it reaches the value set here (default is 256), the next step will loop back to 1. Setting this value lower than the number of steps in the event will cause the song to loop early. Setting it higher will result in a silence as the value continues to count up before the song can loop. (The tooltip for this incorrectly states that the FMOD event has one fewer step on the parameter; it actually has one more step in all vanilla events.)
Beats per Tick
defines how many steps (or "beats") of the parameter occur between each "snap" sound effect. Set to 4 by default, reflecting how the parameter steps are 16th notes to the snaps being quarter notes. Setting this value to 3 would result in each snap having three parameter steps in between, useful for a song in compound meter (three subdivisions per beat).
Ticks per Swap
defines how many snaps (or "ticks") occur between the cassette blocks updating to their next state. Set to 2 by default, which is why cassette blocks change on beats 1 and 3, and don't change on beast 2 and 4. This value is also used by Celeste to determine which snap sound to use; a "high" sound on block changes, and a "low" sound on all other snaps.
Leading Beats
defines how long the "count-in" before the music begins is. Interestingly, this counter refers to units equivalent to a step of the sixteenth_note
parameter, but does not actually affect the parameter. Set to 16 by default, and when combined with the default 4 beats per tick, results in the standard 4 snaps before the music starts. Because this value is tied directly to steps of the parameter and not the number of swaps, unexpected behaviours can arise when this value does not correspond to a whole number of swaps. When this happens, the swap sequence will begin on the first step of the lead-in, then reset to the start once the music begins playing. This will result in whichever block is active when the music begins to remain active for longer than the Ticks per Swap
value. As such, is it recommended for the mapper to ensure the value for Leading Beats
to correspond to a whole number of swaps. This can be guaranteed by multiplying the Beats per Tick
and Ticks per Swap
values together, and having this value be a multiple of that product.
Beat Index Offset
is used to offset the snaps/swaps from the music. Defaults to 0, resulting in a swap occurring at the same time the music begins. A common use case for an offset is to move the swaps to the "backbeat" of the music (beats 2 and 4, instead of 1 and 3). Like Leading Beats
, this value is measured in steps of the parameter. So, to achieve the backbeat timing with all other values at their default (such as vanilla cassette music), an offset of 4 should be used. When the music begins, instead of starting with sixteenth_note
equal to 1, it will be set to 1 plus this offset, resulting in some notes being skipped when the music begins, though they will still be heard when the song loops.
Tempo Multiplier
and Number of Colours
are only used when Old Behaviour
is ticked. The old behaviour is largely depreciated and not recommended to be used.
Active During Transitions
is what allows cassette music to continue playing during room transitions. For single cassette rooms (like the A and B sides in vanilla) it is recommended to keep this unticked. If multiple cassette rooms neighbour each other, it is recommended to enable this to avoid a gap in the music/pulse.
The final element to consider in cassette mapmaking is the tempo. With a tempo multiplier of 1.0, Celeste will play the music at quarter note = 90 bpm. As mentioned above, if Old Behaviour
is ticked, then this is set in the metadata. However, this locks every cassette room in the map to one tempo. With Old Behaviour
unticked, the tempo is instead determined by the cassette blocks themselves, and done on a per-room basis instead. If more than one block has a tempo multiplier set to anything other than 1.0, the tempo on the oldest block in the room is prioritized. This is usually, but not always, the block with the lowest ID. It is recommended to only have one block in a room define a tempo to avoid any confusion during the mapping process.
Production: How to write Cassette Music
WIP
Sequencing: How to create a Cassette Music Event in FMOD
WIP
Advanced Techniques
WIP