Skip to content

Audio music section

Music and Background Tracks

PixelRoot32 includes a complete music sequencing system through the MusicPlayer class. This allows you to create background music, adaptive soundtracks, and complex musical arrangements using the same NES-style audio channels.

🎵 Complete MusicPlayer Guide

For comprehensive MusicPlayer documentation with advanced examples, see the MusicPlayer Integration Guide.

Quick Music Example

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

using namespace pixelroot32::audio;

// Define a simple melody
static const MusicNote SIMPLE_MELODY[] = {
    makeNote(INSTR_PULSE_LEAD, Note::C, 4, 0.25f),  // C4 quarter note
    makeNote(INSTR_PULSE_LEAD, Note::E, 4, 0.25f),  // E4 quarter note  
    makeNote(INSTR_PULSE_LEAD, Note::G, 4, 0.25f),  // G4 quarter note
    makeNote(INSTR_PULSE_LEAD, Note::C, 5, 0.5f),  // C5 half note
};

static const MusicTrack SIMPLE_TRACK = {
    SIMPLE_MELODY,
    sizeof(SIMPLE_MELODY) / sizeof(MusicNote),
    true,                    // Loop enabled
    WaveType::PULSE,         // Use pulse wave
    0.5f                     // 50% duty cycle
};

// Play the music
void MyScene::init() {
    auto& musicPlayer = engine.getMusicPlayer();
    musicPlayer.play(SIMPLE_TRACK);
    musicPlayer.setTempoFactor(1.0f); // Normal speed
}

Music Integration Patterns

Adaptive Music System

class GameScene : public Scene {
private:
    enum class MusicState { MENU, EXPLORATION, COMBAT, VICTORY };
    MusicState currentMusic = MusicState::MENU;

public:
    void updateMusic(float threatLevel) {
        auto& musicPlayer = engine.getMusicPlayer();

        MusicState targetState = MusicState::EXPLORATION;
        if (threatLevel > 0.7f) targetState = MusicState::COMBAT;
        if (playerWon) targetState = MusicState::VICTORY;

        if (currentMusic != targetState) {
            switch (targetState) {
                case MusicState::COMBAT:
                    musicPlayer.setTempoFactor(1.3f); // Faster tempo
                    musicPlayer.play(COMBAT_MUSIC);
                    break;
                case MusicState::VICTORY:
                    musicPlayer.setTempoFactor(1.0f);
                    musicPlayer.play(VICTORY_MUSIC);
                    break;
            }
            currentMusic = targetState;
        }
    }
};

Music with Sound Effects Mixing

void GameScene::playAttackSound() {
    auto& musicPlayer = engine.getMusicPlayer();
    auto& audioEngine = engine.getAudioEngine();

    // Slight tempo reduction for dramatic effect
    float originalTempo = musicPlayer.getTempoFactor();
    musicPlayer.setTempoFactor(originalTempo * 0.95f);

    // Play attack sound effect
    audioEngine.playEvent({
        WaveType::NOISE,
        200.0f,    // Low frequency for impact
        0.8f,      // High volume
        0.1f       // Short duration
    });

    // Restore tempo after delay
    delay(100);
    musicPlayer.setTempoFactor(originalTempo);
}

Advanced Music Features

Tempo Control

// Speed up music as difficulty increases
void GameScene::updateDifficulty(int score) {
    auto& musicPlayer = engine.getMusicPlayer();

    float tempo = 1.0f + (score / 1000.0f) * 0.5f; // 1.0x to 1.5x
    musicPlayer.setTempoFactor(tempo);
}

Music Synchronization

void RhythmGameScene::update(unsigned long deltaTime) {
    // Check if we're on a beat (every 0.5 seconds)
    static float beatTimer = 0.0f;
    beatTimer += deltaTime * 0.001f;

    if (beatTimer >= 0.5f) {
        beatTimer = 0.0f;

        // Spawn enemy on beat
        spawnEnemy();

        // Flash screen on beat
        screenFlash = 255;
    }
}

Music Best Practices

  1. Memory Efficiency: Define music tracks as static const to store in flash memory
  2. Performance: Keep music tracks reasonably short (under 100 notes) and use looping
  3. Platform Considerations:
  4. ESP32: Music timing is sample-accurate, runs on Core 0
  5. Native: Full SDL2 audio backend with higher quality mixing
  6. User Experience: Provide volume controls and allow separate music/SFX muting

Common Music Patterns

8-Bar Blues Progression

static const MusicNote BLUES_PROGRESSION[] = {
    // Bar 1-2: C7
    makeNote(INSTR_PULSE_LEAD, Note::C, 4, 1.0f),
    makeNote(INSTR_PULSE_LEAD, Note::E, 4, 1.0f),
    makeNote(INSTR_PULSE_LEAD, Note::G, 4, 1.0f),
    makeNote(INSTR_PULSE_LEAD, Note::Bb, 4, 1.0f),
    // Continue with F7, G7, etc.
};

Arpeggio Pattern

static const MusicNote ARPEGGIO[] = {
    makeNote(INSTR_TRIANGLE, Note::C, 4, 0.25f),
    makeNote(INSTR_TRIANGLE, Note::E, 4, 0.25f),
    makeNote(INSTR_TRIANGLE, Note::G, 4, 0.25f),
    makeNote(INSTR_TRIANGLE, Note::C, 5, 0.25f),
    // Descending
    makeNote(INSTR_TRIANGLE, Note::G, 4, 0.25f),
    makeNote(INSTR_TRIANGLE, Note::E, 4, 0.25f),
    makeNote(INSTR_TRIANGLE, Note::C, 4, 0.25f),
    makeRest(0.25f),
};

MusicPlayer API Reference

Key Methods

  • play(const MusicTrack& track) - Start playing a track
  • stop() - Stop playback and silence the channel
  • pause() - Pause playback (time does not advance)
  • resume() - Resume playback after pause
  • isPlaying() const - Check if track is currently playing
  • setTempoFactor(float factor) - Set global tempo (1.0 = normal, 2.0 = double speed)
  • getTempoFactor() const - Get current tempo factor

MusicTrack Structure

struct MusicTrack {
    const MusicNote* notes;      // Array of notes
    size_t count;                // Number of notes
    bool loop;                   // Whether to loop
    WaveType channelType;        // Wave type (PULSE, TRIANGLE, NOISE)
    float duty;                  // Duty cycle for pulse waves
};

For complete MusicPlayer documentation, advanced examples, and troubleshooting, see the MusicPlayer Integration Guide.