Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions api/audio/audio-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ audio.playEvent(evt);

- **`PULSE`** — Square wave; **`AudioEvent::duty`** active.
- **`TRIANGLE`** — Triangle wave.
- **`NOISE`** — LFSR-based noise; **`frequency`** meaning depends on scheduler (see below).
- **`NOISE`** — LFSR-based noise in **`ApuCore`** (same polynomial on every platform). **`frequency`** / **`noisePeriod`** semantics are those of the **`AudioEvent`** fields below (noise **clock**, not musical pitch). Schedulers only control **when** `generateSamples` runs, not how noise is clocked.

### `AudioEvent` (struct)

Expand All @@ -83,10 +83,10 @@ audio.playEvent(evt);

### `AudioChannel` (struct, scheduler-owned)

Relevant fields for noise on ESP32 (see engine `AudioTypes.h`):
Relevant noise fields on **all** platforms (see engine `AudioTypes.h`):

- **`lfsrState`** — 15-bit NES-style LFSR.
- **`noisePeriodSamples`**, **`noiseCountdown`** — Clock the LFSR on **ESP32** (sub-sample-rate stepping for percussion-like noise).
- **`noisePeriodSamples`**, **`noiseCountdown`** — Step the LFSR at sub-sample-rate intervals for percussion-like noise (deterministic; no `rand()` in the audio path).

### `AudioConfig` (struct)

Expand Down
27 changes: 21 additions & 6 deletions api/audio/music-player.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Defined in **[`AudioMusicTypes.h`](https://github.com/PixelRoot32-Game-Engine/Pi
- **`uint8_t octave`**: octave (0–8). For authored percussion without a per-note preset, octave can act as a drum selector; prefer **`InstrumentPreset`** on the note when possible.
- **`float duration`**: length in **seconds** (unless a percussion preset fixes duration via **`defaultDuration`**).
- **`float volume`**: 0.0–1.0.
- **`const InstrumentPreset* preset`**: optional; **`nullptr`** uses the parent track’s defaults / legacy path. When set, **`makeNote`** stores the preset pointer on each note.
- **`const InstrumentPreset* preset`**: optional; **`nullptr`** uses the **`MusicTrack`** layer’s **`channelType`** / **`duty`** with legacy envelope behavior (no per-note preset). When set, **`makeNote`** stores the pointer and the sequencer uses that preset’s ADSR/LFO/wave options; for **percussion** (**`duty == 0`**), the preset drives hit duration (**`defaultDuration`**), **`noisePeriod`**, and the frequency passed into the noise path via **`instrumentToFrequency`**.

### `MusicTrack`

Expand Down Expand Up @@ -55,19 +55,32 @@ Complete instrument preset with ADSR envelope, LFO modulation, and waveform refi

#### Built-in presets

**Melodic**

| Preset | Role |
|--------|------|
| **`INSTR_PULSE_LEAD`** | Main lead pulse, octave 4, duty 0.5 |
| **`INSTR_PULSE_HARMONY`** | Harmony pulse, octave 5, duty 0.125 |
| **`INSTR_PULSE_PAD`** | Atmospheric pad pulse, octave 4, duty 0.25, slow pitch drift |
| **`INSTR_PULSE_BASS`** | Punchy bass pulse, octave 2, duty 0.25 |
| **`INSTR_TRIANGLE_LEAD`** | Smooth triangle lead, octave 5, gentle vibrato |
| **`INSTR_TRIANGLE_PAD`** | Soft atmospheric triangle pad, octave 4, tremolo |
| **`INSTR_TRIANGLE_BASS`** | Triangle bass, octave 3, duty 0.5 |

**Percussion** (use with **`WaveType::NOISE`**; tuned **`defaultDuration`** / **`noisePeriod`**)

| Preset | Role |
|--------|------|
| **`INSTR_PULSE_LEAD`** | Lead pulse |
| **`INSTR_PULSE_HARMONY`** | Thinner / higher pulse |
| **`INSTR_TRIANGLE_BASS`** | Triangle bass |
| **`INSTR_KICK`**, **`INSTR_SNARE`**, **`INSTR_HIHAT`** | Short noise hits with tuned **`defaultDuration`** / **`noisePeriod`** |
| **`INSTR_KICK`** | Kick: defaultOctave 1, duration 0.12s, noisePeriod 25 |
| **`INSTR_SNARE`** | Snare: defaultOctave 2, duration 0.15s, noisePeriod 50 |
| **`INSTR_HIHAT`** | Hi-hat: defaultOctave 3, duration 0.05s, noisePeriod 12 |

#### Helpers

- **`MusicNote makeNote(const InstrumentPreset& preset, Note note, float duration)`**
- **`MusicNote makeNote(const InstrumentPreset& preset, Note note, uint8_t octave, float duration)`**
- **`MusicNote makeRest(float duration)`**
- **`float instrumentToFrequency(const InstrumentPreset& preset, Note, uint8_t)`** — melodic vs percussion routing.
- **`float instrumentToFrequency(const InstrumentPreset& preset, Note, uint8_t)`** — If **`preset.duty == 0`** (percussion), returns a **fixed noise-clock rate in Hz** for the NOISE channel (**Kick ≈ 80**, **Snare ≈ 150**, **Hi-hat ≈ 3000** from **`defaultOctave`** 1 / 2 / 3+); these control **density/brightness**, not musical pitch like **`noteToFrequency`**. **`Note`** / **`octave`** are ignored for that path. If **`duty > 0`**, the helper is not used for normal melodic sequencing (implementation falls back to **440 Hz**; melodic pitch comes from **`noteToFrequency`** in the music path).

### `AudioCommand` (multi-track)

Expand Down Expand Up @@ -164,6 +177,8 @@ static const MusicTrack FULL = {
// size_t layers = musicPlayer.getActiveTrackCount(); // 3
```

On **`DRUMS`**, **`Note::C`** is only a placeholder; **`Note::Rest`** is equally valid for percussion presets—the noise clock comes from **`instrumentToFrequency`** and the preset.

For a full sample project, see **[`examples/music_demo`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo)**.

---
27 changes: 12 additions & 15 deletions architecture/audio-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ struct AudioEvent {
float volume; // 0.0 - 1.0
float duty; // only for PULSE
uint8_t noisePeriod = 0; // NOISE: 0 = from frequency, >0 = fixed LFSR period
const struct InstrumentPreset* preset = nullptr; // static/constexpr/global; nullptr = legacy ADSR
};
```

Expand Down Expand Up @@ -116,7 +117,7 @@ The audio system no longer uses `deltaTime` or frame-based updates. Instead, it

- **Audio Time**: Internal unit is samples (e.g., 1 second = 44100 samples at 44.1kHz).
- **Decoupled Logic**: The `AudioScheduler` runs in a separate thread (SDL2) or core (ESP32).
- **Lifetime**: For each active `AudioChannel`, the scheduler subtracts 1 from `remainingSamples` for every sample generated.
- **Lifetime**: For each active `AudioChannel`, **`ApuCore::generateSamples`** subtracts 1 from `remainingSamples` for every PCM sample produced (whichever thread/task invokes **`AudioEngine::generateSamples`** through the scheduler).
- When `remainingSamples` reaches 0, the channel is automatically disabled.

Important:
Expand All @@ -127,7 +128,7 @@ Important:

### 3.3 Per-channel sample generation

Oscillators and per-channel volume ramps are implemented in **`ApuCore::generateSampleForChannel`** (float path) and in an **integer inner loop** inside **`ApuCore::generateSamples`** on no-FPU ESP32 builds. **`NOISE`** uses the same **15-bit LFSR** everywhere, clocked with **`noisePeriodSamples`** / **`noiseCountdown`**.
Oscillators are implemented in **`ApuCore::generateSampleForChannel`** (float path) and in an **integer inner loop** inside **`ApuCore::generateSamples`** on no-FPU ESP32 builds. After the raw waveform sample, the **float path** applies an **ADSR envelope** (from **`InstrumentPreset`** when **`AudioEvent::preset`** is set, else short legacy attack/release), then **LFO** modulation on pitch or volume when enabled. **`NOISE`** uses the same **15-bit LFSR** everywhere, clocked with **`noisePeriodSamples`** / **`noiseCountdown`**.

Per-channel samples are scaled by **`MIXER_SCALE` (0.4)** before the global **compressor** `mixed = sum / (1 + |sum| * 0.5)` (FPU) or the equivalent **`audio_mixer_lut`** path (C3). **Master volume** applies after that. The old **`12000.0f`** scaling is **not** used in current code.

Expand Down Expand Up @@ -211,12 +212,13 @@ The audio system behavior can be customized via `platforms/PlatformDefaults.h` o

#### 4.2.2 Build Flags

| Flag | Description |
|------|-------------|
| `PIXELROOT32_NO_DAC_AUDIO` | Disables the Internal DAC backend on classic ESP32. |
| `PIXELROOT32_NO_I2S_AUDIO` | Disables the I2S audio backend. |
| `PIXELROOT32_USE_U8G2` | Enables support for the U8G2 display driver (future support). |
| `PIXELROOT32_NO_TFT_ESPI` | Disables the default TFT_eSPI display driver. |
| Flag | Default | Description |
|------|---------|-------------|
| `PIXELROOT32_ENABLE_AUDIO` | `1` | Master switch: when `0`, the whole audio subsystem (engine APIs, **`ApuCore`**, backends used by audio) is excluded from the build. |
| `PIXELROOT32_NO_DAC_AUDIO` | — | Disables the internal DAC backend on classic ESP32. |
| `PIXELROOT32_NO_I2S_AUDIO` | — | Disables the I2S audio backend. |

Display-related compile flags (e.g. U8G2, TFT_eSPI) live with platform docs: [Platform configuration](../guide/platform-config.md).

### 4.3 Audio Backends (interface)

Expand Down Expand Up @@ -517,18 +519,13 @@ struct InstrumentPreset {
float dutySweep = 0.0f; // Duty cycle change per second (PWM)
};

inline constexpr InstrumentPreset INSTR_PULSE_LEAD{0.35f, 0.5f, 4};
inline constexpr InstrumentPreset INSTR_PULSE_HARMONY{0.22f, 0.125f, 5};
inline constexpr InstrumentPreset INSTR_TRIANGLE_BASS{0.30f, 0.5f, 3};
inline constexpr InstrumentPreset INSTR_KICK{0.40f, 0.0f, 1, 0.12f, 25};
inline constexpr InstrumentPreset INSTR_SNARE{0.30f, 0.0f, 2, 0.15f, 50};
inline constexpr InstrumentPreset INSTR_HIHAT{0.20f, 0.0f, 3, 0.05f, 12};

inline MusicNote makeNote(const InstrumentPreset& preset, Note note, float duration);
inline MusicNote makeNote(const InstrumentPreset& preset, Note note, uint8_t octave, float duration);
inline MusicNote makeRest(float duration);
```

**Built-in `constexpr` presets** — full initializer lists live only in the header (do not rely on abbreviated copies). Melodic: **`INSTR_PULSE_LEAD`**, **`INSTR_PULSE_HARMONY`**, **`INSTR_PULSE_PAD`**, **`INSTR_PULSE_BASS`**, **`INSTR_TRIANGLE_LEAD`**, **`INSTR_TRIANGLE_PAD`**, **`INSTR_TRIANGLE_BASS`**. Percussion (use with **`WaveType::NOISE`**, **`duty == 0`**): **`INSTR_KICK`**, **`INSTR_SNARE`**, **`INSTR_HIHAT`**. See [`AudioMusicTypes.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/AudioMusicTypes.h) and the engine [API audio reference](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/docs/api/API_AUDIO.md#predefined-presets) for roles and parameters.

These helpers reduce boilerplate when defining tracks and keep note volumes and octaves consistent per instrument.

#### 7.1.1 NES-style tick timing
Expand Down
2 changes: 2 additions & 0 deletions guide/music-player.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ struct MusicNote {

Authoritative definitions live in **`AudioMusicTypes.h`** (`constexpr` presets such as **`INSTR_PULSE_LEAD`**, **`INSTR_TRIANGLE_BASS`**, **`INSTR_KICK`**, **`INSTR_SNARE`**, **`INSTR_HIHAT`**). Percussion presets use **`duty == 0`** and optional **`defaultDuration`** / **`noisePeriod`** for noise hits—do not redefine them locally unless you are experimenting.

For **drum rows** authored with those percussion presets, **`Note::C`** (as in examples below) and **`Note::Rest`** (as in the engine’s **`MUSIC_PLAYER_GUIDE.md`**) are both valid placeholders: pitch is driven by **`instrumentToFrequency`** and the preset, not by the nominal note name.

---

## Creating Music
Expand Down
Loading