Skip to content

MusicPlayer

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

Description

MusicPlayer is a simple sequencer that plays MusicTrack structures. It advances notes based on game time, converts MusicNote entries to AudioEvent calls, and manages playback state (play, stop, pause, resume).

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: - 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();
}

void update(unsigned long deltaTime)

Updates the player state. Should be called every frame.

Parameters: - deltaTime (unsigned long): Time elapsed since last frame in milliseconds

Returns: - void

Notes: - Must be called every frame for proper timing - Advances note playback based on elapsed time - Automatically plays next notes in sequence - Typically called automatically by Engine

Example:

// Called automatically by Engine, but can be called manually:
void update(unsigned long deltaTime) override {
    Scene::update(deltaTime);

    // Music player is updated automatically by Engine
    // No need to call manually
}

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