Skip to content

song_select

Anthony Samms edited this page Jun 2, 2026 · 3 revisions

The song select screen is where the player browses songs and folders, selects a difficulty, and transitions into gameplay. It manages a state machine that routes input and drawing across several sub-systems: the global navigator, the song_select_player, and a song_select_script that drives the footer and overlay animations.

The screen is constructed with the name "song_select", which the base Screen class uses to load the correct textures and sounds from the active skin.

SongSelectState

An enum tracking which phase the screen is currently in.

Value Description
BROWSING The player is scrolling through the song/folder list
SONG_SELECTED A song has been opened and the difficulty selector is visible
DIFF_SORTING The diff_sort UI is open for filtering songs by difficulty
SEARCHING The search_box is open for text-based song searching
DAN_SELECTED A Dan challenge folder has been selected

SongSelectScreen

Inherits from Screen. Constructed with the screen name "song_select".

Members

Member Type Description
state SongSelectState Current screen state
player unique_ptr<SongSelectPlayer> The player's UI controller
script unique_ptr<SongSelectScript> Lua script interface for skin customisation
diff_fade_out FadeAnimation* Fade used when transitioning out of difficulty select
game_transition optional<Transition> Fade-to-black transition when starting a song
dan_transition optional<DanTransition> Slide transition when entering Dan mode
diff_sort_selector optional<DiffSortSelect> The difficulty sort overlay, created on demand
search_box optional<SearchBox> The song search overlay, created on demand
select_timer unique_ptr<Timer> Countdown timer shown while a song is selected
diff_select_timer unique_ptr<Timer> Countdown timer shown during difficulty selection
indicator unique_ptr<Indicator> Play-count indicator displayed on screen
coin_overlay CoinOverlay Global coin display
allnet_indicator AllNetIcon Online connectivity indicator
stats_future future<Statistics> Async task computing difficulty statistics for diff sort
cached_stats Statistics Statistics stored once the future resolves
song_num unique_ptr<SongNum> Total song count display
shader ray::Shader Colour-transform shader applied during rendering
last_diff_sort pair<int,int> The most recently applied difficulty sort filter

Lifecycle

void on_screen_start() override;

Calls Screen::on_screen_start() to load skin textures and sounds, then starts background music and the entry voice clip. Loads the color shader, creates the SongSelectScript, initializes the global navigator with the configured song paths, launches the async stats_future task, creates the SongSelectPlayer, and sets up the timer, indicator, and song-count display objects.

Screens on_screen_end(Screens next_screen) override;

Joins the navigator's loader thread, then delegates to Screen::on_screen_end which unloads all sounds, music, and textures.

Update

std::optional<Screens> update() override;

The main loop. Each frame it:

  1. Calls Screen::update() for first-frame initialisation.
  2. Advances the diff_fade_out and script animations.
  3. Checks whether stats_future has resolved; if so, caches Statistics and passes them to a newly created DiffSortSelect.
  4. Calls the appropriate handle_input_* method for the current state.
  5. Updates the player, navigator, timers, and overlays.
  6. Handles state transitions — if the player is ready and the voice has finished playing, a game_transition fade is started; once it completes the screen returns Screens::GAME.
  7. If the Back box is selected during SONG_SELECTED, returns to BROWSING.
  8. If the back key is pressed from BROWSING, returns Screens::ENTRY.

Returns std::nullopt while the screen is still active.

Input Handling

void handle_input(double current_ms); // private dispatcher

Routes input to the correct handler based on state.

void handle_input_browsing(double current_ms);

Delegates to player->handle_input_browsing(). If the result signals SONG_SELECTED, SEARCHING, or DAN_SELECTED, the state is updated accordingly and the appropriate overlay is created. If the result signals entering a Difficulty Sort folder, awaiting_diff_sort is set and the stats_future task is launched.

void handle_input_selecting();

Delegates to player->handle_input_selecting(). A return of BROWSING exits the difficulty selector and resets to browsing; a confirmed selection marks the player ready.

void handle_input_diff_sorting();

Forwards left/right/select inputs to the DiffSortSelect overlay. On confirmation, calls navigator.apply_diff_sort() and returns to BROWSING. On cancel, calls navigator.cancel_diff_sort().

void handle_input_search();

Feeds character input into the SearchBox. On confirmation, passes the search string to navigator and transitions to BROWSING.

Draw

void draw() override;

Renders in the following order:

  1. navigator.draw_background() — genre-based scrolling background.
  2. player->draw_background_diffs() — faded difficulty decorations behind the list.
  3. navigator.draw() — all visible boxes.
  4. script->draw_footer() — Lua-driven footer strip.
  5. navigator.draw_score_history() — score history panel for the open song.
  6. player->draw_selector() and player->draw() — difficulty selector and player character/nameplate.
  7. diff_sort_selector->draw() or search_box->draw() when those overlays are active.
  8. draw_overlays() — timers, indicators, coin overlay, allnet icon, and script overlays.
  9. game_transition->draw() or dan_transition->draw() when transitioning out.
void draw_overlays(); // protected, virtual

Draws the select_timer, diff_select_timer, indicator, song_num, coin_overlay, allnet_indicator, and calls script->draw_overlays(state).

Selection

virtual void select_song(SongBox* song);

Records the selected song path, hash, and genre in SessionData, adds the song to the recent list, and starts the game_transition fade. Overridden in the practice and 2P variants to set different session targets.

Virtual extension points

SongSelectScreen is subclassed by SongSelect2PScreen (two-player mode) and SongSelectPracticeScreen (practice mode). Both variants override select_song, handle_input, draw, get_game_screen_target, and several other methods to support their respective modes.


SongSelect2PScreen

SongSelect2PScreen inherits from SongSelectScreen and adds a second SongSelectPlayer for P2. It uses the same "song_select" skin assets and routes to Screens::GAME_2P on confirmation.

Additional members

Member Type Description
player_2 unique_ptr<SongSelectPlayer> P2's UI controller (difficulty selector, nameplate, character)

Lifecycle

void on_screen_start() override;

Calls SongSelectScreen::on_screen_start(), then constructs player_2 for PlayerNum::P2.

Update

std::optional<Screens> update() override;

Mirrors the base update loop but advances both player and player_2 each frame. The transition to GAME_2P fires only when both player->is_ready and player_2->is_ready are true and both have selected a playable difficulty. If either player confirms the Back option, both players' ready and selected-song state are reset and the screen returns to BROWSING.

Input Handling

void handle_input_browsing(double current_ms) override;

Runs P1's browsing input first. If P1 selects a song, P2's difficulty list is immediately populated with that song's difficulties (and vice versa), and the state advances to SONG_SELECTED. Non-song-select state changes (folder navigation, search, etc.) are propagated from whichever player triggered them.

void handle_input_selecting() override;

Runs each player's difficulty-select input independently, skipping a player once is_ready is set.

void select_song(SongBox* song) override;

Writes SessionData for both PlayerNum::P1 and PlayerNum::P2 — each with their own selected difficulty and hash — then starts the game_transition fade. A Loading.png from the song directory is added to the transition if present.

Draw

void draw() override;

Draws background diffs for both players, then the navigator, footer script, and both players' difficulty selectors side-by-side. The is_half flag passed to each player's draw() is true when both players have selected the same difficulty, shrinking each selector to half-width to avoid overlap.

Clone this wiki locally