diff --git a/api/audio/audio-engine.md b/api/audio/audio-engine.md index 7365ca0..d07e364 100644 --- a/api/audio/audio-engine.md +++ b/api/audio/audio-engine.md @@ -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) @@ -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) diff --git a/api/audio/music-player.md b/api/audio/music-player.md index 2fb6d67..f80a764 100644 --- a/api/audio/music-player.md +++ b/api/audio/music-player.md @@ -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` @@ -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) @@ -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)**. --- diff --git a/architecture/audio-architecture.md b/architecture/audio-architecture.md index 23f8fea..cac3bf9 100644 --- a/architecture/audio-architecture.md +++ b/architecture/audio-architecture.md @@ -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 }; ``` @@ -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: @@ -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. @@ -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) @@ -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 diff --git a/guide/music-player.md b/guide/music-player.md index 0551ab1..536cfb5 100644 --- a/guide/music-player.md +++ b/guide/music-player.md @@ -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