Skip to content

MusicPlayer

Lightweight sequencer built on top of AudioEngine to play background melodies as tracks.

Description

MusicPlayer is a thin client for the music system. It sends commands to the AudioScheduler, which handles the actual sequencing of notes using sample-accurate timing. This ensures that background music is completely independent of the game's frame rate and render performance.

The player uses one audio channel (typically a Pulse channel) for music, leaving other channels available for sound effects.

Namespace

namespace pixelroot32::audio {
    class MusicPlayer {
        // ...
    };
}

Inheritance

  • Base class: None (standalone class)
  • Used by: Engine (manages music player instance)

Constructors

MusicPlayer(AudioEngine& engine)

Constructs the MusicPlayer.

Parameters: - engine (AudioEngine&): Reference to the AudioEngine used to play sounds

Notes: - Typically created and managed by Engine - Access via engine.getMusicPlayer()

Example:

auto& audio = engine.getAudioEngine();
pixelroot32::audio::MusicPlayer musicPlayer(audio);

Public Methods

void play(const MusicTrack& track)

Starts playing a track.

Parameters: - track (const MusicTrack&): The track to play

Returns: - void

Notes: - Decoupled: Sends a command to start sequencing the track in the audio thread/core. - Stops any currently playing track. - Starts from the beginning of the track. - If track has loop = true, will loop automatically. - Uses one audio channel (typically Pulse).

Example:

static const MusicNote MELODY[] = {
    makeNote(INSTR_PULSE_LEAD, Note::C, 0.20f),
    makeNote(INSTR_PULSE_LEAD, Note::E, 0.20f),
    makeNote(INSTR_PULSE_LEAD, Note::G, 0.25f),
    makeRest(0.10f),
};

static const MusicTrack GAME_MUSIC = {
    MELODY,
    sizeof(MELODY) / sizeof(MusicNote),
    true,  // loop
    WaveType::PULSE,
    0.5f   // volume
};

void init() override {
    auto& music = engine.getMusicPlayer();
    music.play(GAME_MUSIC);
}

void stop()

Stops playback and silences the channel.

Returns: - void

Notes: - Immediately stops the current note - Resets playback to the beginning - Channel is freed for other use

Example:

void onGameOver() {
    auto& music = engine.getMusicPlayer();
    music.stop();
}

void pause()

Pauses playback.

Returns: - void

Notes: - Current note continues until it ends, then playback pauses - Playback state is preserved (can resume from where it paused) - Use for pause menus

Example:

void onPause() {
    auto& music = engine.getMusicPlayer();
    music.pause();
}

void resume()

Resumes playback.

Returns: - void

Notes: - Only works if playback was paused - Resumes from where it was paused - Use to unpause after pause menu

Example:

void onResume() {
    auto& music = engine.getMusicPlayer();
    music.resume();
}

bool isPlaying() const

Checks if a track is currently playing.

Returns: - bool: true if playing, false otherwise

Notes: - Returns false if stopped or paused - Use to check playback state before operations

Example:

auto& music = engine.getMusicPlayer();
if (music.isPlaying()) {
    // Music is active
} else {
    // Music is stopped or paused
}

void setTempoFactor(float factor)

Sets the global tempo scaling factor.

Parameters: - factor (float): Tempo multiplier - 1.0f: Normal speed - 2.0f: Double speed - 0.5f: Half speed

Returns: - void

Notes: - Affects all note durations - Useful for speed-up effects or slow-motion - Applied to all tracks

Example:

auto& music = engine.getMusicPlayer();
music.setTempoFactor(1.5f);  // 50% faster
music.setTempoFactor(0.5f);   // 50% slower
music.setTempoFactor(1.0f);   // Normal speed

float getTempoFactor() const

Gets the current tempo scaling factor.

Returns: - float: Current factor (default 1.0f)

Example:

float currentTempo = musicPlayer.getTempoFactor();

MusicTrack Structure

A MusicTrack contains:

  • notes (const MusicNote*): Array of music notes
  • noteCount (size_t): Number of notes in the array
  • loop (bool): Whether to loop the track
  • waveType (WaveType): Wave type to use (typically PULSE)
  • volume (float): Volume level (0.0 to 1.0)

MusicNote Structure

A MusicNote contains:

  • instrument (InstrumentPreset): Instrument preset to use
  • note (Note): Musical note (C, D, E, etc.)
  • duration (float): Duration in seconds

Use helper functions: - makeNote(instrument, note, duration): Create a note - makeRest(duration): Create a rest (silence)

Usage Example

#include "audio/MusicPlayer.h"
#include "audio/AudioMusicTypes.h"

using namespace pixelroot32::audio;

// Define a simple melody
static const MusicNote MAIN_THEME[] = {
    makeNote(INSTR_PULSE_LEAD, Note::C, 0.25f),
    makeNote(INSTR_PULSE_LEAD, Note::E, 0.25f),
    makeNote(INSTR_PULSE_LEAD, Note::G, 0.25f),
    makeRest(0.1f),
    makeNote(INSTR_PULSE_LEAD, Note::C, 0.5f),
    makeRest(0.2f),
};

static const MusicTrack MAIN_THEME_TRACK = {
    MAIN_THEME,
    sizeof(MAIN_THEME) / sizeof(MusicNote),
    true,           // loop
    WaveType::PULSE,
    0.6f            // volume
};

class GameScene : public pixelroot32::core::Scene {
public:
    void init() override {
        // Start background music
        auto& music = engine.getMusicPlayer();
        music.play(MAIN_THEME_TRACK);
    }

    void update(unsigned long deltaTime) override {
        Scene::update(deltaTime);

        // Music updates automatically
    }

    void onPauseMenu() {
        auto& music = engine.getMusicPlayer();
        music.pause();
    }

    void onResumeGame() {
        auto& music = engine.getMusicPlayer();
        music.resume();
    }

    void onGameOver() {
        auto& music = engine.getMusicPlayer();
        music.stop();
    }
};

Performance Considerations

  • One channel: Music uses one channel, leaving others for sound effects
  • Update frequency: update() must be called every frame
  • Track size: Larger tracks use more memory (store in flash)
  • Tempo factor: Changing tempo is fast (just a multiplier)

ESP32 Considerations

  • Memory: Store tracks in flash (const/constexpr) to save RAM
  • CPU: Music playback is lightweight (simple sequencing)
  • Channel conflict: Music and sound effects share channels; plan accordingly

See Also