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
1 change: 1 addition & 0 deletions .vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ export default defineConfig({
{ text: 'Animated Tilemap', link: '/examples/animated-tilemap' },
{ text: 'Tilemaps (overview)', link: '/examples/tilemap-scene' },
{ text: 'Tic Tac Toe', link: '/examples/tic-tac-toe' },
{ text: '2048', link: '/examples/2048' },
{ text: 'UI layout (Tic Tac Toe)', link: '/examples/ui-layout' },
{ text: 'Flappy Bird', link: '/examples/flappy-bird' },
{ text: 'Music demo (audio)', link: '/examples/music-demo' },
Expand Down
5 changes: 3 additions & 2 deletions api/audio.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ void MyScene::init() {
## Architecture Notes

- **ApuCore**: Synthesis, non-linear mixing, HPF, master bitcrush, post-mix hooks, sequencing, and the SPSC command queue live here. `DefaultAudioScheduler` and platform variants decide when `generateSamples` runs.
- **ESP32 buffer notes**: I2S backends typically aggregate 1024 samples per DMA transaction. Internal DAC output uses I2S in `I2S_MODE_DAC_BUILT_IN`.
- On **no-FPU** ESP32, `ApuCore` uses an integer oscillator mirror and a precomputed mixer LUT so the inner loop avoids soft-float.
- **ESP32 buffer notes**: I2S backends use configurable block size (default 512 samples on dual-core, 128 on single-core). Internal DAC output uses I2S in `I2S_MODE_DAC_BUILT_IN`.
- On **no-FPU** ESP32 (e.g. ESP32-C3), `ApuCore` uses an integer oscillator mirror, fixed-point HPF, and integer LFO to avoid soft-float in the inner loop.
- **Noise / LFSR**: Deterministic everywhere (no `rand()`).

## Configuration
Expand All @@ -104,6 +104,7 @@ void MyScene::init() {
| `PIXELROOT32_NO_I2S_AUDIO` | - | Disable I2S audio backend |
| `ApuCore::MAX_VOICES` | `8` | Synthesis voice pool size |
| `AudioCommandQueue::CAPACITY` | `128` | SPSC ring capacity |
| `ESP32_I2S_AudioBackend::blockSize` | `512` / `128` (single-core) | DMA buffer block size in samples |

## Related Types

Expand Down
6 changes: 6 additions & 0 deletions api/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ This document covers global configuration options, build flags, and compile-time
| `PIXELROOT32_ENABLE_PROFILING` | Enable profiling hooks in physics pipeline. | Disabled |
| `PIXELROOT32_ENABLE_TOUCH` | Enable automatic touch processing. | `0` (disabled) |
| `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enable **`StaticTilemapLayerCache`** (4bpp FB snapshot). | `1` |
| `PIXELROOT32_ENABLE_DIRTY_REGIONS` | Enable dirty-cell selective framebuffer clear (`DirtyGrid`). Requires 64–226 B RAM. | `0` |
| `PIXELROOT32_ENABLE_DIRTY_REGION_PROFILING` | Enable dirty region profiling metrics. | `0` |
| `PIXELROOT32_TFT_ESPI_LINES_PER_BLOCK` | TFT_eSPI DMA line batch size. | `60` |
| `PIXELROOT32_TFT_ESPI_LINES_PER_BLOCK_FALLBACK` | Fallback DMA batch size if memory fails. | `30` |
| `PIXELROOT32_DEBUG_MODE` | Enable unified logging system. | Disabled |
Expand All @@ -52,16 +54,20 @@ This document covers global configuration options, build flags, and compile-time
| `PIXELROOT32_ENABLE_UI_SYSTEM=0` | ~4 KB | ~20 KB |
| `PIXELROOT32_ENABLE_PARTICLES=0` | ~6 KB | ~10 KB |
| `PIXELROOT32_ENABLE_TOUCH=0` | ~200 bytes | ~2 KB |
| `PIXELROOT32_ENABLE_DIRTY_REGIONS=0` | -64 to -226 bytes | ~1 KB |

## Build Profiles (platformio.ini)

> **Note:** `PIXELROOT32_ENABLE_DIRTY_REGIONS` can be added to any profile for selective framebuffer clearing. It is not enabled by default in any profile.

```ini
[profile_full] ; All features enabled
build_flags =
-D PIXELROOT32_ENABLE_AUDIO=1
-D PIXELROOT32_ENABLE_PHYSICS=1
-D PIXELROOT32_ENABLE_PARTICLES=1
-D PIXELROOT32_ENABLE_UI_SYSTEM=1
-D PIXELROOT32_ENABLE_DIRTY_REGIONS=1

[profile_arcade] ; Audio + Physics + Particles, no UI
build_flags =
Expand Down
114 changes: 113 additions & 1 deletion api/generated/audio/ApuCore.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,144 @@ Mixing curve:
On cores without an FPU (ESP32-C3) the integer-optimised path uses
`audio_mixer_lut` which is pre-fitted to the same curve.

## Properties

| Name | Type | Description |
|------|------|-------------|
| `audioTimeSamples` | `uint64_t` | Global sample counter at capture. |
| `peak` | `float` | Peak sample magnitude [0.0 - 1.0]. |
| `clipped` | `bool` | Whether any sample exceeded ±32767. |

## Methods

### `void init(int sampleRate)`

**Description:**

Configures the output sample rate.

**Parameters:**

- `sampleRate`: Sample rate in Hz (e.g., 22050, 44100).

Safe to call before start(). Pre-calculates internal timing values.

### `bool submitCommand(const AudioCommand& cmd)`

**Returns:** false if the SPSC queue was full.
**Description:**

Enqueues a command for processing.

**Parameters:**

- `cmd`: The audio command to enqueue.

**Returns:** true if the command was enqueued, false if the queue was full.

### `void generateSamples(int16_t* stream, int length)`

**Description:**

Generates audio samples.

**Parameters:**

- `stream`: Output buffer (mono, int16 samples).
- `length`: Number of samples to generate.

Processes all pending commands, updates music sequencer, and synthesizes
voices into the output buffer. Applies master volume, bitcrush, and DC blocking.

### `uint32_t getDroppedCommands() const`

**Description:**

Returns the total number of commands dropped since construction. @return Monotonic count.

**Returns:** Monotonic count.

### `void setSequencerNoteLimit(size_t limit)`

**Description:**

Sets the maximum notes processed per audio frame.

**Parameters:**

- `limit`: Max notes [1-1000], clamped to safe bounds internally.

Bounded processing prevents audio starvation when many notes queue up.

### `size_t getSequencerNoteLimit() const`

**Description:**

Gets current max notes per frame setting. @return Current limit.

**Returns:** Current limit.

### `size_t getDeferredNotes() const`

**Description:**

Returns notes deferred to next frame due to note limit. @return Deferred count.

**Returns:** Deferred count.

### `bool isMusicPlaying() const`

**Description:**

Reports if music is currently playing. @return true if playing.

**Returns:** true if playing.

### `bool isMusicPaused() const`

**Description:**

Reports if music is paused. @return true if paused.

**Returns:** true if paused.

### `int getSampleRate() const`

**Description:**

Gets the configured sample rate. @return Sample rate in Hz.

**Returns:** Sample rate in Hz.

### `void reset()`

**Description:**

Resets all state to initial values.

### `void setPostMixMono(void (*fn)(int16_t* mono, int length, void* user), void* user)`

**Description:**

Sets an optional post-mix callback.

**Parameters:**

- `fn`: Function pointer: void(int16_t* mono, int length, void* user).
- `user`: User data passed to the callback.

Called after bitcrush on the final mono buffer. Runs in audio thread context.

### `void getAndResetProfileStats(ProfileEntry* out, uint8_t& count)`

**Description:**

Reads and clears all profile entries.

**Parameters:**

- `out`: Array of ProfileEntry to fill.
- `count`: On input: max entries. On output: actual count written.

### `size_t countEnabledVoicesForTesting() const`

### `size_t getSequencerMainNoteIndexForTesting() const`
28 changes: 24 additions & 4 deletions api/generated/audio/AudioBackend.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,36 @@

Abstract interface for platform-specific audio drivers.

This class abstracts the underlying audio hardware or API (e.g., SDL2, I2S).
It is responsible for requesting audio samples from the AudioEngine and
pushing them to the output device.
This class abstracts the underlying audio hardware or API (e.g., SDL2 on native,
I2S on ESP32). It is responsible for requesting audio samples from the AudioEngine
and pushing them to the output device. Platforms implement this interface to
bridge between the engine's sample generation and the actual hardware.

Typical lifecycle:
1. init() is called with an AudioEngine pointer.
2. The backend sets up the audio output (I2S, SDL audio, etc.).
3. In its output callback, the backend calls engine->generateSamples().
4. On destruction, resources are cleaned up.

## Methods

### `virtual void init(AudioEngine* engine, const pixelroot32::platforms::PlatformCapabilities& caps = pixelroot32::platforms::PlatformCapabilities())`

**Description:**

Initializes the audio backend.

**Parameters:**

- `engine`: Pointer to the AudioEngine instance to request samples from.
Must not be nullptr.
- `caps`: Platform capabilities to guide backend initialization
(e.g., core pinning on ESP32, thread priorities).

### `virtual int getSampleRate() const`

**Description:**

Returns the configured sample rate of the backend.

**Returns:** Sample rate in Hz (e.g., 22050, 44100).
**Returns:** Sample rate in Hz (e.g., 22050, 44100, 48000).
15 changes: 15 additions & 0 deletions api/generated/audio/AudioChannel.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ Represents the internal state of a single audio channel.

Designed to be static and memory-efficient.

## Properties

| Name | Type | Description |
|------|------|-------------|
| `sweepSamplesTotal` | `uint32_t` | Total samples for the sweep. |
| `sweepSamplesRemaining` | `uint32_t` | Samples remaining in the sweep. |
| `sweepStartHz` | `float` | Starting frequency in Hz. |
| `sweepEndHz` | `float` | Ending frequency in Hz. |
| `sweepStartIncQ32` | `uint32_t` | Q32 phase increment at sweep start. |
| `sweepEndIncQ32` | `uint32_t` | Q32 phase increment at sweep end. |

## Methods

### `void reset()`

**Description:**

Resets the channel to a clean disabled state.
15 changes: 11 additions & 4 deletions api/generated/audio/AudioCommandQueue.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@

## Description

Multi-Producer Single-Consumer lock-free ring buffer for AudioCommands.
Multi-Producer Single-Consumer (MPSC) lock-free ring buffer for AudioCommands.

Fixed size, no allocation. Supports multiple concurrent producer threads.
If the queue is full, the newest command is dropped and droppedCommands is incremented.
Uses compare-and-swap (CAS) for atomic ring index advancement.
Fixed-size, zero-allocation queue designed for real-time audio thread communication.
Supports multiple concurrent producer threads (e.g., game logic, music sequencer)
and a single consumer thread (the audio thread).

Drop policy: When the queue is full, the newest command is silently dropped and
the droppedCommands counter is incremented. Callers can monitor this via
getDroppedCommands() for diagnostics.

Thread-safety: Uses compare-and-swap (CAS) for atomic ring index advancement.
The producer path is wait-free; the consumer path is lock-free.

## Methods

Expand Down
16 changes: 16 additions & 0 deletions api/generated/audio/AudioConfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,19 @@ Configuration for the Audio subsystem.
|------|------|-------------|
| `backend` | `AudioBackend*` | Pointer to the platform-specific audio backend. |
| `sampleRate` | `int` | Desired sample rate in Hz. |
| `blockSize` | `int` | Audio block size (samples). Must be multiple of 128. |

## Methods

### `: backend(backend), sampleRate(sampleRate), blockSize(blockSize)`

**Description:**

Constructs an AudioConfig with platform-adaptive block size.

**Parameters:**

- `backend`: Pointer to the audio backend implementation. May be nullptr for headless configs.
- `sampleRate`: Desired sample rate in Hz (default 22050 for retro feel).
- `blockSize`: Audio block size in samples. Defaults to 256 on FPU platforms, 128 on no-FPU platforms.
Must be a multiple of 128 for I2S DMA alignment.
Loading
Loading