backend-infra-engineer: Post v0.3.9-hotfix7 snapshot (build cleanup)
This commit is contained in:
103
docs/internal/hand-off/HANDOFF_AUDIO.md
Normal file
103
docs/internal/hand-off/HANDOFF_AUDIO.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Audio System Handoff & Status Report
|
||||
|
||||
**Date:** November 30, 2025
|
||||
**Status:** Functional but Imperfect (Audio artifacts, speed/pitch accuracy issues)
|
||||
**Context:** Integration of `MusicPlayer` (Audio-only mode) with `Emulator` (Full system)
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
The audio system currently suffers from synchronization issues ("static/crackling", "fast playback") caused by drift between the emulated SNES clock (~32040 Hz) and the host audio device (48000 Hz). Recent attempts to implement Dynamic Rate Control (DRC) and fix Varispeed (playback speed) introduced regressions due to logic errors in rate calculation.
|
||||
|
||||
**Current Symptoms:**
|
||||
* **Static/Crackling:** Buffer underruns. The emulator isn't generating samples fast enough, or the host is consuming them too fast.
|
||||
* **Fast Playback:** At 1.0x speed, audio may drift faster than real-time to catch up with buffer underruns.
|
||||
* **Broken Varispeed:** At <1.0x speeds, audio is pitched down doubly (slower tempo + lower pitch) due to a math error in `RunAudioFrame`.
|
||||
|
||||
## 2. Technical Context
|
||||
|
||||
### 2.1. The "32040 Hz" Reality
|
||||
* **Nominal:** SNES APU documents often cite 32000 Hz.
|
||||
* **Actual:** Hardware measurements confirm the DSP output is ~32040 Hz.
|
||||
* **Implementation:** We updated `kNativeSampleRate` to `32040` in `emulator.cc`. This is correct and should remain.
|
||||
|
||||
### 2.2. Audio Pipeline
|
||||
1. **SPC700/DSP:** Generates 16-bit stereo samples at ~32040 Hz into a ring buffer (`dsp.cc`).
|
||||
2. **Emulator Loop:** `RunAudioFrame` (or `Run`) executes CPU/APU cycles until ~1 frame of time has passed.
|
||||
3. **Extraction:** `GetSampleCount` / `ReadRawSamples` drains the DSP ring buffer.
|
||||
4. **Resampling:** `SDL_AudioStream` (SDL2) handles 32040 -> 48000 Hz conversion.
|
||||
5. **Output:** `QueueSamples` pushes data to the OS driver.
|
||||
|
||||
### 2.3. The Logic Errors
|
||||
|
||||
#### A. Double-Applied Varispeed
|
||||
In `Emulator::RunAudioFrame` (used by Music Editor):
|
||||
```cpp
|
||||
// ERROR: playback_speed_ is used twice!
|
||||
// 1. To determine how much source data to generate (Correct for tempo)
|
||||
int samples_to_generate = wanted_samples_ / playback_speed_;
|
||||
|
||||
// 2. To determine the playback rate (Incorrect - Double Pitch Shift)
|
||||
int effective_rate = kNativeSampleRate * playback_speed_;
|
||||
```
|
||||
* **Effect:** If speed is 0.5x:
|
||||
* We generate 2x data (correct to fill time).
|
||||
* We tell SDL "This data is 16020 Hz" (instead of 32040 Hz).
|
||||
* SDL resamples 16k->48k (3x stretch) ON TOP of the 2x data generation.
|
||||
* Result: 0.25x speed / pitch drop.
|
||||
|
||||
#### B. Flawed DRC
|
||||
The current DRC implementation adjusts `effective_rate` based on buffer depth. While the *idea* is correct (buffer full -> play faster), it interacts poorly with the Varispeed bug above, leading to wild oscillations or "static" as it fights the double-speed factor.
|
||||
|
||||
## 3. Proposed Solutions
|
||||
|
||||
### Phase 1: The Quick Fix (Recommended First)
|
||||
Correct the Varispeed math in `src/app/emu/emulator.cc`.
|
||||
|
||||
**Logic:**
|
||||
* **Source Generation:** Continue scaling `samples_to_generate` by `1/speed` (to fill the time buffer).
|
||||
* **Playback Rate:** The `effective_rate` sent to SDL should **ALWAYS** be `kNativeSampleRate` (32040), regardless of playback speed. We are stretching the *content*, not changing the *clock*.
|
||||
* *Exception:* DRC adjustments (+/- 100 Hz) are applied to this 32040 base.
|
||||
|
||||
**Pseudocode Fix:**
|
||||
```cpp
|
||||
// Generate enough samples to fill the frame time at this speed
|
||||
snes_.SetSamples(native_buffer, samples_available);
|
||||
|
||||
// BASE rate is always native. Speed change happens because we generated
|
||||
// MORE/LESS data for the same real-time interval.
|
||||
int output_rate = kNativeSampleRate;
|
||||
|
||||
// Apply subtle DRC only for synchronization
|
||||
if (buffer_full) output_rate += 100;
|
||||
if (buffer_empty) output_rate -= 100;
|
||||
|
||||
queue_samples(native_buffer, output_rate);
|
||||
```
|
||||
|
||||
### Phase 2: Robust DRC (Mid-Term)
|
||||
Implement a PID controller or smoothed average for the DRC adjustment instead of the current +/- 100 Hz "bang-bang" control, which causes pitch wobble.
|
||||
|
||||
### Phase 3: Callback-Driven Audio (Long-Term)
|
||||
Switch from `SDL_QueueAudio` (Push) to `SDL_AudioCallback` (Pull).
|
||||
* **Mechanism:** SDL calls *us* when it needs data.
|
||||
* **Action:** We run the Emulator core *inside* the callback (or wait for a thread to produce it) until the buffer is full.
|
||||
* **Benefit:** Guaranteed synchronization with the audio clock. Impossible to have underruns if the emulation is fast enough.
|
||||
* **Cost:** Major refactor of the main loop.
|
||||
|
||||
## 4. Investigation References
|
||||
|
||||
### Key Files
|
||||
* `src/app/emu/emulator.cc`: Main audio loop, DRC logic, Varispeed math.
|
||||
* `src/app/emu/audio/dsp.cc`: Sample generation, interpolation (Gaussian).
|
||||
* `src/app/emu/audio/audio_backend.cc`: SDL2 stream management.
|
||||
|
||||
### External References
|
||||
* **bsnes/higan:** Uses "Dynamic Rate Control" (micro-resampling) to sync video (60.09Hz) and audio (32040Hz) to PC (60Hz/48000Hz).
|
||||
* **Snes9x:** Uses a similar buffer-based feedback loop.
|
||||
|
||||
## 5. Action Plan for Next Dev
|
||||
1. **Open `src/app/emu/emulator.cc`**.
|
||||
2. **Locate `RunAudioFrame` and `Run`**.
|
||||
3. **Fix Varispeed:** Change `int effective_rate = kNativeSampleRate * playback_speed_` to `int effective_rate = kNativeSampleRate`.
|
||||
4. **Retain DRC:** Keep the `if (queued > high) rate += delta` logic, but apply it to the fixed 32040 base.
|
||||
5. **Test:** Verify 1.0x speed is static-free, and 0.5x speed is actually half-speed, not quarter-speed.
|
||||
136
docs/internal/hand-off/HANDOFF_BG2_MASKING_FIX.md
Normal file
136
docs/internal/hand-off/HANDOFF_BG2_MASKING_FIX.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# BG2 Masking Fix Handoff
|
||||
|
||||
**Date:** 2025-12-07
|
||||
**Status:** Phase 1 Research Complete, Ready for Implementation
|
||||
**Priority:** High - 94 rooms affected
|
||||
|
||||
## Problem Summary
|
||||
|
||||
BG2 overlay content (platforms, statues, stairs) is invisible because BG1 floor tiles completely cover it. Example: Room 001's center platform is on BG2 but hidden under solid BG1 floor.
|
||||
|
||||
## Root Cause (Confirmed)
|
||||
|
||||
The SNES uses **pixel-level transparency** via color 0 in floor tiles. The editor's floor drawing correctly skips color 0 pixels, BUT the issue is that floor tiles may be entirely solid with no transparent pixels.
|
||||
|
||||
**SNES Behavior:**
|
||||
1. Floor drawn to both BG1 and BG2 tilemaps (identical)
|
||||
2. Layer 1 objects overwrite BG2 tilemap with platform graphics
|
||||
3. BG1 tilemap keeps floor tiles - but floor tiles have color 0 (transparent) pixels
|
||||
4. PPU composites: BG1 color 0 pixels reveal BG2 beneath
|
||||
|
||||
**Current Editor Behavior:**
|
||||
1. Floor drawn to both BG1 and BG2 bitmaps ✓
|
||||
2. Layer 1 objects drawn to BG2 bitmap ✓
|
||||
3. BG1 has solid floor everywhere (no holes) ✗
|
||||
4. Compositing works, but BG1 has no transparent pixels ✗
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/app/gfx/render/background_buffer.cc` | `DrawTile()` at line 161 - already skips pixel 0 correctly |
|
||||
| `src/zelda3/dungeon/room.cc` | Floor drawing at lines 597-608, object rendering at 973-977 |
|
||||
| `src/zelda3/dungeon/room_layer_manager.cc` | `CompositeToOutput()` - layer stacking order |
|
||||
| `src/zelda3/dungeon/object_drawer.cc` | `DrawObject()` line 40 - routes by layer |
|
||||
|
||||
## The Fix
|
||||
|
||||
**Option 1: Verify Floor Tile Transparency (Quick Check)**
|
||||
|
||||
Check if floor graphic 6 tiles actually have color 0 pixels:
|
||||
```cpp
|
||||
// In DrawFloor or DrawTile, log pixel distribution
|
||||
int transparent_count = 0;
|
||||
for (int i = 0; i < 64; i++) { // 8x8 tile
|
||||
if (pixel == 0) transparent_count++;
|
||||
}
|
||||
LOG_DEBUG("Floor tile has %d transparent pixels", transparent_count);
|
||||
```
|
||||
|
||||
If floor tiles ARE solid (no color 0), proceed to Option 2.
|
||||
|
||||
**Option 2: Layer 1 Object Mask Propagation (Recommended)**
|
||||
|
||||
When drawing Layer 1 (BG2) objects, also mark corresponding BG1 pixels as transparent:
|
||||
|
||||
```cpp
|
||||
// In ObjectDrawer::DrawObject(), after drawing to BG2:
|
||||
if (object.layer_ == RoomObject::LayerType::BG2) {
|
||||
// Draw object to BG2 normally
|
||||
draw_routines_[routine_id](this, object, bg2, tiles, state);
|
||||
|
||||
// ALSO mark BG1 pixels as transparent in the object's area
|
||||
MarkBG1Transparent(bg1, object.x_, object.y_, object_width, object_height);
|
||||
}
|
||||
|
||||
void ObjectDrawer::MarkBG1Transparent(BackgroundBuffer& bg1,
|
||||
int x, int y, int w, int h) {
|
||||
auto& bitmap = bg1.bitmap();
|
||||
for (int py = y * 8; py < (y + h) * 8; py++) {
|
||||
for (int px = x * 8; px < (x + w) * 8; px++) {
|
||||
bitmap.WriteToPixel(py * 512 + px, 255); // 255 = transparent
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Option 3: Two-Pass Floor Drawing**
|
||||
|
||||
1. First pass: Collect all Layer 1 object bounding boxes
|
||||
2. Second pass: Draw BG1 floor, skip pixels inside Layer 1 boxes
|
||||
|
||||
## Room 001 Test Case
|
||||
|
||||
```
|
||||
Layer 1 (BG2) objects that need masking:
|
||||
- 0x033 @ (22,13) size=4 - Floor 4x4 platform
|
||||
- 0x034 @ (23,16) size=14 - Solid 1x1 tiles
|
||||
- 0x071 @ (22,13), (41,13) - Vertical solid
|
||||
- 0x038 @ (24,12), (34,12) - Statues
|
||||
- 0x13B @ (30,10) - Inter-room stairs
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
Run the analysis script to find all affected rooms:
|
||||
```bash
|
||||
python scripts/analyze_room.py --list-bg2
|
||||
# Output: 94 rooms with BG2 overlay objects
|
||||
```
|
||||
|
||||
Test specific rooms:
|
||||
```bash
|
||||
python scripts/analyze_room.py 1 --compositing
|
||||
python scripts/analyze_room.py 64 --compositing # Has 29 BG2 objects
|
||||
```
|
||||
|
||||
## SNES 4-Pass Rendering Reference
|
||||
|
||||
From `bank_01.asm` lines 1104-1156:
|
||||
1. Layout objects → BG1 tilemap
|
||||
2. Layer 0 objects → BG1 tilemap
|
||||
3. Layer 1 objects → **BG2 tilemap only** (lower_layer pointers)
|
||||
4. Layer 2 objects → BG1 tilemap (upper_layer pointers)
|
||||
|
||||
The key insight: Layer 1 objects ONLY write to BG2. They do NOT clear BG1. Transparency comes from floor tile pixel colors, not explicit masking.
|
||||
|
||||
## Files Created During Research
|
||||
|
||||
- `scripts/analyze_room.py` - Room object analyzer
|
||||
- `docs/internal/plans/dungeon-layer-compositing-research.md` - Full research notes
|
||||
|
||||
## Quick Start for Next Agent
|
||||
|
||||
1. Read `docs/internal/plans/dungeon-layer-compositing-research.md` Section 8
|
||||
2. Check floor graphic 6 tile pixels for color 0 (debug `DrawTile()`)
|
||||
3. If solid, implement Option 2 (mask propagation)
|
||||
4. Test with Room 001 - platform should be visible with BG1 enabled
|
||||
5. Verify with `--list-bg2` rooms (94 total)
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] Room 001 center platform visible with BG1 layer ON
|
||||
- [ ] All 94 BG2 overlay rooms render correctly
|
||||
- [ ] Layer visibility toggles still work
|
||||
- [ ] No performance regression
|
||||
|
||||
251
docs/internal/hand-off/HANDOFF_CUSTOM_OBJECTS.md
Normal file
251
docs/internal/hand-off/HANDOFF_CUSTOM_OBJECTS.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# Custom Objects & Minecart System Handoff
|
||||
|
||||
**Status:** Partially Implemented
|
||||
**Created:** 2025-12-07
|
||||
**Owner:** dungeon-rendering-specialist
|
||||
**Priority:** Medium
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the custom dungeon object system for Oracle of Secrets and similar ROM hacks. Custom objects (IDs 0x31 and 0x32) are loaded from external binary files rather than vanilla ROM tile data.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
### What Works
|
||||
|
||||
| Component | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| Project configuration | ✅ Complete | `custom_objects_folder` in .yaze file |
|
||||
| Feature flag in UI | ✅ Complete | Checkbox in Dungeon Flags menu |
|
||||
| Feature flag sync | ✅ Complete | Project flags sync to global on load |
|
||||
| MinecartTrackEditorPanel | ✅ Complete | Loads/saves `minecart_tracks.asm` |
|
||||
| CustomObjectManager | ✅ Complete | Loads .bin files from project folder |
|
||||
| Panel registration | ✅ Complete | Panel available in Dungeon category |
|
||||
|
||||
### What Doesn't Work
|
||||
|
||||
| Component | Status | Issue |
|
||||
|-----------|--------|-------|
|
||||
| DrawCustomObject | ❌ Not Working | Draw routine not registered; tiles not rendering |
|
||||
| Object previews | ❌ Not Working | DungeonObjectSelector previews don't load custom objects |
|
||||
| Graphics editing | ❌ Not Started | No UI to edit custom object graphics |
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### File Structure (Oracle of Secrets)
|
||||
|
||||
```
|
||||
Oracle-of-Secrets/
|
||||
├── Oracle-of-Secrets.yaze # Project file
|
||||
├── Dungeons/Objects/Data/ # Custom object .bin files
|
||||
│ ├── track_LR.bin
|
||||
│ ├── track_UD.bin
|
||||
│ ├── track_corner_TL.bin
|
||||
│ ├── furnace.bin
|
||||
│ └── ...
|
||||
└── Sprites/Objects/data/
|
||||
└── minecart_tracks.asm # Track starting positions
|
||||
```
|
||||
|
||||
### Project Configuration
|
||||
|
||||
```ini
|
||||
[files]
|
||||
custom_objects_folder=/path/to/Dungeons/Objects/Data
|
||||
|
||||
[feature_flags]
|
||||
enable_custom_objects=true
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/core/project.h` | `custom_objects_folder` field |
|
||||
| `src/core/project.cc` | Serialization/parsing of field |
|
||||
| `src/core/features.h` | `kEnableCustomObjects` flag |
|
||||
| `src/zelda3/dungeon/custom_object.h` | `CustomObject` struct, `CustomObjectManager` |
|
||||
| `src/zelda3/dungeon/custom_object.cc` | Binary file loading and parsing |
|
||||
| `src/zelda3/dungeon/object_drawer.cc` | `DrawCustomObject` method |
|
||||
| `src/app/editor/dungeon/dungeon_editor_v2.cc` | Panel registration, manager init |
|
||||
| `src/app/editor/dungeon/panels/minecart_track_editor_panel.cc` | Track editor UI |
|
||||
| `src/app/gui/app/feature_flags_menu.h` | UI checkbox for flag |
|
||||
|
||||
---
|
||||
|
||||
## Custom Object Binary Format
|
||||
|
||||
Based on ZScream's object handler:
|
||||
|
||||
```
|
||||
Header (2 bytes):
|
||||
Low 5 bits: Tile count for this row
|
||||
High byte: Row stride (usually 0x80 = 1 tilemap row)
|
||||
|
||||
Tile Data (2 bytes per tile):
|
||||
Bits 0-9: Tile ID (10 bits)
|
||||
Bits 10-12: Palette (3 bits)
|
||||
Bit 13: Priority
|
||||
Bit 14: Horizontal flip
|
||||
Bit 15: Vertical flip
|
||||
|
||||
Repeat Header + Tiles until Header == 0x0000
|
||||
```
|
||||
|
||||
### Object ID Mapping
|
||||
|
||||
| Object ID | Subtype | Filename |
|
||||
|-----------|---------|----------|
|
||||
| 0x31 | 0 | track_LR.bin |
|
||||
| 0x31 | 1 | track_UD.bin |
|
||||
| 0x31 | 2 | track_corner_TL.bin |
|
||||
| 0x31 | 3 | track_corner_TR.bin |
|
||||
| 0x31 | 4 | track_corner_BL.bin |
|
||||
| 0x31 | 5 | track_corner_BR.bin |
|
||||
| 0x31 | 6-14 | track_floor_*.bin, track_any.bin |
|
||||
| 0x31 | 15 | small_statue.bin |
|
||||
| 0x32 | 0 | furnace.bin |
|
||||
| 0x32 | 1 | firewood.bin |
|
||||
| 0x32 | 2 | ice_chair.bin |
|
||||
|
||||
---
|
||||
|
||||
## Issues to Fix
|
||||
|
||||
### Issue 1: DrawCustomObject Not Registered
|
||||
|
||||
**Location:** `src/zelda3/dungeon/object_drawer.cc`
|
||||
|
||||
**Problem:** The draw routine for custom objects (routine ID 130+) is defined but not registered in `InitializeDrawRoutines()`. The object_to_routine_map_ doesn't have entries for 0x31 and 0x32.
|
||||
|
||||
**Fix Required:**
|
||||
```cpp
|
||||
// In InitializeDrawRoutines():
|
||||
object_to_routine_map_[0x31] = CUSTOM_OBJECT_ROUTINE_ID;
|
||||
object_to_routine_map_[0x32] = CUSTOM_OBJECT_ROUTINE_ID;
|
||||
|
||||
// Also need to register the routine itself:
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
|
||||
gfx::BackgroundBuffer& bg,
|
||||
std::span<const gfx::TileInfo> tiles,
|
||||
const DungeonState* state) {
|
||||
self->DrawCustomObject(obj, bg, tiles, state);
|
||||
});
|
||||
```
|
||||
|
||||
**Also:** The tiles passed to DrawCustomObject are from `object.tiles()` which are loaded from ROM. Custom objects should NOT use ROM tiles - they should use tiles from the .bin file. The current implementation gets tiles from CustomObjectManager but ignores the `tiles` parameter.
|
||||
|
||||
### Issue 2: CustomObjectManager Not Initialized Early Enough
|
||||
|
||||
**Location:** `src/app/editor/dungeon/dungeon_editor_v2.cc`
|
||||
|
||||
**Problem:** CustomObjectManager is initialized in `DungeonEditorV2::Load()` but objects may be drawn before this happens.
|
||||
|
||||
**Current Code:**
|
||||
```cpp
|
||||
if (!dependencies_.project->custom_objects_folder.empty()) {
|
||||
zelda3::CustomObjectManager::Get().Initialize(
|
||||
dependencies_.project->custom_objects_folder);
|
||||
}
|
||||
```
|
||||
|
||||
**Fix:** Ensure initialization happens before any room rendering.
|
||||
|
||||
### Issue 3: Object Previews in Selector
|
||||
|
||||
**Location:** `src/app/editor/dungeon/dungeon_object_selector.cc`
|
||||
|
||||
**Problem:** The custom objects section in `DrawObjectAssetBrowser()` attempts to show previews but:
|
||||
1. Uses `MakePreviewObject()` which loads ROM tiles
|
||||
2. Doesn't use CustomObjectManager to get the actual custom object data
|
||||
3. Preview rendering fails silently
|
||||
|
||||
**Fix Required:** Create a separate preview path for custom objects that:
|
||||
1. Loads binary data from CustomObjectManager
|
||||
2. Renders tiles using the binary tile data, not ROM tiles
|
||||
|
||||
---
|
||||
|
||||
## Minecart Track Editor
|
||||
|
||||
### Status: Complete
|
||||
|
||||
The MinecartTrackEditorPanel loads and saves `minecart_tracks.asm` which defines:
|
||||
- `.TrackStartingRooms` - Which room each track starts in
|
||||
- `.TrackStartingX` - X position within the room
|
||||
- `.TrackStartingY` - Y position within the room
|
||||
|
||||
### File Format
|
||||
|
||||
```asm
|
||||
.TrackStartingRooms
|
||||
dw $0098, $0088, $0087, ...
|
||||
|
||||
.TrackStartingX
|
||||
dw $1190, $1160, $1300, ...
|
||||
|
||||
.TrackStartingY
|
||||
dw $1380, $10C9, $1100, ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Priority 1: Make Custom Objects Render
|
||||
|
||||
1. Register routine for 0x31/0x32 in `InitializeDrawRoutines()`
|
||||
2. Verify CustomObjectManager is initialized before room load
|
||||
3. Test with Oracle of Secrets project
|
||||
|
||||
### Priority 2: Fix Previews
|
||||
|
||||
1. Add custom preview path in DungeonObjectSelector
|
||||
2. Use CustomObjectManager data instead of ROM tiles
|
||||
3. Handle case where project folder isn't set
|
||||
|
||||
### Priority 3: Graphics Editing (Future)
|
||||
|
||||
1. Create UI to view/edit custom object binary files
|
||||
2. Add export functionality for new objects
|
||||
3. Integrate with sprite editor or create dedicated panel
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### To Test Custom Objects:
|
||||
|
||||
1. Open YAZE
|
||||
2. Open Oracle of Secrets project file
|
||||
3. Navigate to Dungeon editor
|
||||
4. Open a room that contains custom objects (e.g., minecart tracks)
|
||||
5. Objects should render (currently: they don't)
|
||||
|
||||
### To Test Minecart Panel:
|
||||
|
||||
1. Open Oracle of Secrets project
|
||||
2. Go to Dungeon editor
|
||||
3. View > Panels > Minecart Tracks (or find in panel browser)
|
||||
4. Should show table of track starting positions
|
||||
5. Edit values and click "Save Tracks"
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [`draw_routine_tracker.md`](../agents/draw_routine_tracker.md) - Draw routine status
|
||||
- [`dungeon-object-rendering-spec.md`](../agents/dungeon-object-rendering-spec.md) - Object rendering details
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
For questions about this system, refer to the Oracle of Secrets project structure or check the custom object handler in the ASM source.
|
||||
|
||||
314
docs/internal/hand-off/HANDOFF_DUNGEON_RENDERING.md
Normal file
314
docs/internal/hand-off/HANDOFF_DUNGEON_RENDERING.md
Normal file
@@ -0,0 +1,314 @@
|
||||
# Dungeon Tile Rendering - Progress & Next Steps
|
||||
|
||||
**Last Updated**: 2025-12-01
|
||||
**Status**: Major Progress - Floor/Walls working, object mappings expanded
|
||||
|
||||
## Summary
|
||||
|
||||
Fixed multiple critical bugs in dungeon tile rendering:
|
||||
1. **Graphics sheet loading order** - Sheets now load before buffer copy
|
||||
2. **Missing object mappings** - Added 168 previously unmapped object IDs (0x50-0xF7)
|
||||
|
||||
Floor tiles, walls, and most objects now render with correct graphics. Some objects still have sizing or transparency issues that need attention.
|
||||
|
||||
---
|
||||
|
||||
## Completed Fixes
|
||||
|
||||
### Fix 1: Graphics Sheet Loading Order (984d3e02cd)
|
||||
**Root Cause**: `blocks_[]` array was read BEFORE `LoadRoomGraphics()` initialized it.
|
||||
|
||||
```cpp
|
||||
// Before (broken): CopyRoomGraphicsToBuffer used stale blocks_[]
|
||||
// After (fixed): LoadRoomGraphics(blockset) called FIRST
|
||||
void Room::RenderRoomGraphics() {
|
||||
if (graphics_dirty_) {
|
||||
LoadRoomGraphics(blockset); // Initialize blocks_[] FIRST
|
||||
CopyRoomGraphicsToBuffer(); // Now uses correct sheet IDs
|
||||
graphics_dirty_ = false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fix 2: Missing Object-to-Routine Mappings (1d77f34f99)
|
||||
**Root Cause**: Objects 0x50-0xF7 had no draw routine mappings, falling through to 1x1 fallback.
|
||||
|
||||
**Added mappings for 168 object IDs**:
|
||||
| Range | Count | Description |
|
||||
|-------|-------|-------------|
|
||||
| 0x50-0x5F | 16 | Floor/decoration objects |
|
||||
| 0x6E-0x6F | 2 | Edge objects |
|
||||
| 0x70-0x7F | 16 | Mixed 4x4, 2x2, 2x4 |
|
||||
| 0x80-0x8F | 16 | 4x2, 4x3, 2x3 objects |
|
||||
| 0x90-0x9F | 16 | 4x2, 2x2, 1x1 objects |
|
||||
| 0xA0-0xAF | 16 | Mostly 1x1 |
|
||||
| 0xB0-0xBF | 16 | Mixed sizes |
|
||||
| 0xC0-0xCF | 16 | 1x1, 4x2, 4x4 |
|
||||
| 0xD0-0xDF | 16 | 1x1, 4x2, 4x4, 2x2 |
|
||||
| 0xE0-0xF7 | 24 | 4x2 and 1x1 |
|
||||
|
||||
### Fix 3: MusicEditor Crash (984d3e02cd)
|
||||
**Root Cause**: `ImGui::GetID()` called without valid window context during initialization.
|
||||
**Fix**: Deferred `ClassId` initialization to first `Update()` call.
|
||||
|
||||
---
|
||||
|
||||
## Remaining Issues
|
||||
|
||||
### 1. Objects with Excessive Transparency
|
||||
**Symptoms**: Objects render with mostly transparent tiles, appearing as partial/broken shapes.
|
||||
|
||||
**Likely Causes**:
|
||||
- Tile data contains pixel value 0 (transparent) for most pixels
|
||||
- Palette index mismatch - pixels reference wrong sub-palette
|
||||
- Tile ID points to blank/unused tile in graphics buffer
|
||||
|
||||
**Debug Strategy**:
|
||||
```cpp
|
||||
// In DrawTileToBitmap, log pixel distribution
|
||||
int transparent_count = 0, opaque_count = 0;
|
||||
for (int py = 0; py < 8; py++) {
|
||||
for (int px = 0; px < 8; px++) {
|
||||
if (pixel == 0) transparent_count++;
|
||||
else opaque_count++;
|
||||
}
|
||||
}
|
||||
printf("[Tile %d] transparent=%d opaque=%d\n", tile_info.id_, transparent_count, opaque_count);
|
||||
```
|
||||
|
||||
### 2. Objects with Incorrect Sizes
|
||||
**Symptoms**: Objects appear smaller or larger than expected.
|
||||
|
||||
**Likely Causes**:
|
||||
- Draw routine assigned doesn't match object's tile layout
|
||||
- Tile count in `kSubtype1TileLengths` incorrect for object
|
||||
- Size repetition count wrong (size+1 vs size+7 etc.)
|
||||
|
||||
**Debug Strategy**:
|
||||
```cpp
|
||||
// Log object dimensions vs expected
|
||||
printf("[Object 0x%03X] routine=%d expected_tiles=%d actual=%zu size=%d\n",
|
||||
obj.id_, routine_id, expected_tile_count, tiles.size(), obj.size_);
|
||||
```
|
||||
|
||||
### 3. Subtype 2/3 Objects Need Attention
|
||||
Type 2 (0x100-0x13F) and Type 3 (0xF80-0xFFF) objects use different ROM tables and may have unique tile layouts.
|
||||
|
||||
**Note**: Type 2 `size = 0` is **intentional** - these are fixed-size objects (chests, stairs) that don't repeat.
|
||||
|
||||
### 4. Tile Count Table Accuracy
|
||||
The `kSubtype1TileLengths[0xF8]` table determines how many tiles to read per object. Some entries may be incorrect.
|
||||
|
||||
**Verification Method**: Compare with ZScream's `DungeonObjectData.cs` and ALTTP disassembly.
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Manual Testing Checklist
|
||||
1. **Room 000 (Ganon's Room)**: Verify floor pattern, walls, center platform
|
||||
2. **Room 104**: Check grass/garden tiles, walls, water features
|
||||
3. **Room with chests**: Verify Type 2 objects (chests render correctly)
|
||||
4. **Room with stairs**: Check spiral stairs, layer switching objects
|
||||
5. **Room with pots**: Verify pot objects (0x160-0x16F range)
|
||||
|
||||
### Systematic Testing Approach
|
||||
```bash
|
||||
# Test specific rooms via CLI
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon
|
||||
|
||||
# Add this to room.cc for batch testing
|
||||
for (int room_id = 0; room_id < 296; room_id++) {
|
||||
LoadRoom(room_id);
|
||||
int missing_objects = CountObjectsWithFallbackDrawing();
|
||||
if (missing_objects > 0) {
|
||||
printf("Room %d: %d objects using fallback\n", room_id, missing_objects);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Reference Rooms for Testing
|
||||
| Room ID | Description | Key Objects |
|
||||
|---------|-------------|-------------|
|
||||
| 0 | Ganon's Room | Floor tiles, walls, platform |
|
||||
| 2 | Sanctuary | Walls, altar, decoration |
|
||||
| 18 | Eastern Palace | Pillars, statues |
|
||||
| 89 | Desert Palace | Sand tiles, pillars |
|
||||
| 104 | Garden | Grass, bushes, walls |
|
||||
|
||||
---
|
||||
|
||||
## UI/UX Improvements for Dungeon Editor
|
||||
|
||||
### Object Selection Enhancements
|
||||
|
||||
#### 1. Object Palette Panel
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Object Palette [x] │
|
||||
├─────────────────────────────────────┤
|
||||
│ Category: [Walls ▼] │
|
||||
│ │
|
||||
│ ┌───┬───┬───┬───┐ │
|
||||
│ │0x0│0x1│0x2│0x3│ ← Visual tiles │
|
||||
│ └───┴───┴───┴───┘ │
|
||||
│ ┌───┬───┬───┬───┐ │
|
||||
│ │0x4│0x5│0x6│0x7│ │
|
||||
│ └───┴───┴───┴───┘ │
|
||||
│ │
|
||||
│ Selected: Wall Corner (0x07) │
|
||||
│ Size: 2x2 tiles, Repeatable: Yes │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 2. Object Categories
|
||||
- **Walls**: 0x00-0x20 (horizontal/vertical walls, corners)
|
||||
- **Floors**: 0x33, 0x49-0x4F (floor tiles, patterns)
|
||||
- **Decoration**: 0x36-0x3E (statues, pillars, tables)
|
||||
- **Interactive**: 0x100+ (chests, switches, stairs)
|
||||
- **Special**: 0xF80+ (water, Somaria paths)
|
||||
|
||||
#### 3. Object Inspector Panel
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Object Properties │
|
||||
├─────────────────────────────────────┤
|
||||
│ ID: 0x07 Type: Wall Corner │
|
||||
│ Position: (12, 8) Size: 3 │
|
||||
│ Layer: BG1 All BGs: No │
|
||||
│ │
|
||||
│ Tile Preview: │
|
||||
│ ┌───┬───┐ │
|
||||
│ │ A │ B │ A=0xC8, B=0xC2 │
|
||||
│ ├───┼───┤ C=0xCB, D=0xCE │
|
||||
│ │ C │ D │ │
|
||||
│ └───┴───┘ │
|
||||
│ │
|
||||
│ [Edit Tiles] [Copy] [Delete] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Canvas Improvements
|
||||
|
||||
#### 1. Object Highlighting
|
||||
- Hover: Light outline around object bounds
|
||||
- Selected: Solid highlight with resize handles
|
||||
- Multi-select: Dashed outline for group selection
|
||||
|
||||
#### 2. Grid Overlay Options
|
||||
- 8x8 tile grid (fine)
|
||||
- 16x16 block grid (standard)
|
||||
- 32x32 supertile grid (layout)
|
||||
|
||||
#### 3. Layer Visibility
|
||||
- BG1 toggle (walls, floors)
|
||||
- BG2 toggle (overlays, transparency)
|
||||
- Objects only view
|
||||
- Collision overlay
|
||||
|
||||
### Workflow Improvements
|
||||
|
||||
#### 1. Object Placement Mode
|
||||
```
|
||||
[Draw] [Select] [Move] [Resize] [Delete]
|
||||
│
|
||||
└── Click to select objects
|
||||
Drag to move
|
||||
Shift+drag to copy
|
||||
```
|
||||
|
||||
#### 2. Object Size Adjustment
|
||||
- Drag object edge to resize (increases size repetition)
|
||||
- Ctrl+scroll to adjust size value
|
||||
- Number keys 1-9 for quick size presets
|
||||
|
||||
#### 3. Undo/Redo System
|
||||
- Track object add/remove/move/resize
|
||||
- Snapshot-based for complex operations
|
||||
- 50-step undo history
|
||||
|
||||
---
|
||||
|
||||
## Architecture Reference
|
||||
|
||||
### Graphics Buffer Pipeline
|
||||
```
|
||||
ROM (3BPP compressed sheets)
|
||||
↓ SnesTo8bppSheet()
|
||||
gfx_sheets_ (8BPP, 16 base sheets × 128×32)
|
||||
↓ CopyRoomGraphicsToBuffer()
|
||||
current_gfx16_ (room-specific 64KB buffer)
|
||||
↓ ObjectDrawer::DrawTileToBitmap()
|
||||
bg1_bitmap / bg2_bitmap (512×512 room canvas)
|
||||
```
|
||||
|
||||
### Object Subtypes
|
||||
| Subtype | ID Range | ROM Table | Description |
|
||||
|---------|----------|-----------|-------------|
|
||||
| 1 | 0x00-0xF7 | $01:8000 | Standard objects (walls, floors) |
|
||||
| 2 | 0x100-0x13F | $01:83F0 | Special objects (chests, stairs) |
|
||||
| 3 | 0xF80-0xFFF | $01:84F0 | Complex objects (water, Somaria) |
|
||||
|
||||
### Draw Routine Reference
|
||||
| Routine | Pattern | Objects |
|
||||
|---------|---------|---------|
|
||||
| 0 | 2x2 rightwards (1-15 or 32) | 0x00 |
|
||||
| 1 | 2x4 rightwards | 0x01-0x02 |
|
||||
| 4 | 2x2 rightwards (1-16) | 0x07-0x08 |
|
||||
| 7 | 2x2 downwards (1-15 or 32) | 0x60 |
|
||||
| 8 | 4x2 downwards | 0x61-0x62 |
|
||||
| 16 | 4x4 block | 0x33, 0x4D-0x4F, 0x70+ |
|
||||
| 25 | 1x1 solid | Single-tile objects |
|
||||
|
||||
---
|
||||
|
||||
## Debug Commands
|
||||
|
||||
```bash
|
||||
# Run dungeon editor with debug output
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --debug
|
||||
|
||||
# Filter debug output for specific issues
|
||||
./yaze ... 2>&1 | grep -E "Object|DrawTile|ParseSubtype"
|
||||
|
||||
# Check for objects using fallback drawing
|
||||
./yaze ... 2>&1 | grep "fallback 1x1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Short-term (Next Sprint)
|
||||
1. Fix remaining transparent object issues
|
||||
2. Add object category filtering in UI
|
||||
3. Implement object copy/paste
|
||||
|
||||
### Medium-term
|
||||
1. Visual object palette with rendered previews
|
||||
2. Room template system (save/load object layouts)
|
||||
3. Object collision visualization
|
||||
|
||||
### Long-term
|
||||
1. Drag-and-drop object placement from palette
|
||||
2. Smart object snapping (align to grid, other objects)
|
||||
3. Room comparison tool (diff between ROMs)
|
||||
4. Batch object editing (multi-select properties)
|
||||
|
||||
---
|
||||
|
||||
## Files Reference
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `room.cc` | Room loading, graphics management |
|
||||
| `room_object.cc` | Object encoding/decoding |
|
||||
| `object_parser.cc` | Tile data lookup from ROM |
|
||||
| `object_drawer.cc` | Draw routine implementations |
|
||||
| `dungeon_editor_v2.cc` | Editor UI and interaction |
|
||||
| `dungeon_canvas_viewer.cc` | Canvas rendering |
|
||||
|
||||
## External References
|
||||
- ZScream's `DungeonObjectData.cs` - Object data reference
|
||||
- ALTTP disassembly `bank_00.asm` - RoomDrawObjectData at $00:9B52
|
||||
- ALTTP disassembly `bank_01.asm` - Draw routines at $01:8000+
|
||||
172
docs/internal/hand-off/HANDOFF_MUSIC_AUDIO_SPEED.md
Normal file
172
docs/internal/hand-off/HANDOFF_MUSIC_AUDIO_SPEED.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# MusicEditor 1.5x Audio Speed Bug - Handoff Document
|
||||
|
||||
**Date:** 2025-12-05
|
||||
**Status:** Unresolved
|
||||
**Priority:** High
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The MusicEditor plays audio at approximately 1.5x speed. The exact ratio (48000/32040 = 1.498) indicates that **samples generated at 32040 Hz are being played at 48000 Hz without proper resampling**.
|
||||
|
||||
Additionally, there's a "first play" issue where clicking Play produces no audio the first time, but stopping and playing again works (at 1.5x speed).
|
||||
|
||||
## Audio Pipeline Overview
|
||||
|
||||
```
|
||||
MusicPlayer::Update() [called at ~60 Hz]
|
||||
│
|
||||
▼
|
||||
Emulator::RunAudioFrame()
|
||||
│
|
||||
├─► Snes::RunAudioFrame()
|
||||
│ │
|
||||
│ ├─► cpu_.RunOpcode() loop until vblank
|
||||
│ │ └─► RunCycle() → CatchUpApu() → apu_.RunCycles()
|
||||
│ │ └─► DSP generates ~533 samples at 32040 Hz
|
||||
│ │
|
||||
│ └─► [At vblank] dsp.NewFrame() sets lastFrameBoundary
|
||||
│
|
||||
└─► snes_.SetSamples() → dsp.GetSamples()
|
||||
│
|
||||
└─► Reads ~533 samples from DSP ring buffer
|
||||
│
|
||||
▼
|
||||
audio_backend->QueueSamplesNative(samples, 533, 2, 32040)
|
||||
│
|
||||
├─► SDL_AudioStreamPut(samples) at 32040 Hz
|
||||
│
|
||||
└─► SDL_AudioStreamGet(resampled) → SDL_QueueAudio()
|
||||
└─► Output at 48000 Hz (resampled by SDL)
|
||||
```
|
||||
|
||||
## What Has Been Verified Working
|
||||
|
||||
### 1. APU Timing (VERIFIED CORRECT)
|
||||
- APU runs at ~1,024,000 Hz (tests pass)
|
||||
- DSP generates samples at ~32040 Hz (tests pass)
|
||||
- ~533 samples generated per NTSC frame
|
||||
|
||||
### 2. SDL_AudioStream Resampling (VERIFIED CORRECT)
|
||||
Diagnostic logs confirm correct resampling ratio:
|
||||
```
|
||||
QueueSamplesNative: In=2132 bytes (32040Hz) → Out=3192 bytes (48000Hz)
|
||||
Resampling ratio: 1.497 (expected: 1.498)
|
||||
```
|
||||
|
||||
### 3. Audio Backend Configuration (VERIFIED CORRECT)
|
||||
- SDL audio device opens at 48000 Hz
|
||||
- SDL_AudioStream created: 32040 Hz stereo → 48000 Hz stereo
|
||||
- `audio_stream_enabled_ = true` confirmed in logs
|
||||
|
||||
### 4. Shared Audio Backend (IMPLEMENTED)
|
||||
- MusicPlayer's `audio_emulator_` now uses external backend from main emulator
|
||||
- `Emulator::RunAudioFrame()` uses `audio_backend()` accessor (not direct member)
|
||||
- Single SDL device shared between main emulator and MusicPlayer
|
||||
|
||||
## What Has Been Tried and Ruled Out
|
||||
|
||||
### 1. Duplicate NewFrame() Calls - REMOVED
|
||||
Preview methods had explicit `dsp.NewFrame()` calls that conflicted with the internal call in `RunAudioFrame()`. These were removed but didn't fix the issue.
|
||||
|
||||
### 2. Audio Backend Member vs Accessor - FIXED
|
||||
`Emulator::RunAudioFrame()` was using `audio_backend_` directly instead of `audio_backend()` accessor. When external backend was set, `audio_backend_` was null, so no audio was queued. Fixed to use accessor.
|
||||
|
||||
### 3. Two SDL Audio Devices - FIXED
|
||||
Main emulator and MusicPlayer were creating separate SDL audio devices. Implemented `SetExternalAudioBackend()` to share a single device. Verified in logs that same device ID is used.
|
||||
|
||||
### 4. Initialization Order - VERIFIED CORRECT
|
||||
- `SetSharedAudioBackend()` called in `MusicEditor::Initialize()`
|
||||
- `EnsureAudioReady()` sets external backend before `EnsureInitialized()`
|
||||
- Resampling configured before playback starts
|
||||
|
||||
### 5. First Play Silence - PARTIALLY UNDERSTOOD
|
||||
Logs show the device is already "playing" with stale audio from main emulator when MusicPlayer starts. The exclusivity callback sets `running=false` on main emulator, but this may not immediately stop audio generation.
|
||||
|
||||
## Current Code State
|
||||
|
||||
### Key Files Modified
|
||||
- `src/app/emu/emulator.h` - Added `SetExternalAudioBackend()`, `audio_backend()` accessor
|
||||
- `src/app/emu/emulator.cc` - `RunAudioFrame()` and `ResetFrameTiming()` use accessor
|
||||
- `src/app/editor/music/music_player.h` - Added `SetSharedAudioBackend()`
|
||||
- `src/app/editor/music/music_player.cc` - Uses shared backend, removed duplicate NewFrame() calls
|
||||
- `src/app/editor/music/music_editor.cc` - Shares main emulator's backend with MusicPlayer
|
||||
- `src/app/emu/audio/audio_backend.cc` - Added diagnostic logging
|
||||
|
||||
### Diagnostic Logging Added
|
||||
- `QueueSamplesNative()` logs input/output byte counts and resampling ratio
|
||||
- `GetStatus()` logs device ID and queue state
|
||||
- `Clear()` logs device ID and queue before/after
|
||||
- `Play()` logs device status transitions
|
||||
- `RunAudioFrame()` logs which backend is being used (external vs owned)
|
||||
|
||||
## Remaining Hypotheses
|
||||
|
||||
### 1. SDL_AudioStream Not Actually Being Used
|
||||
**Theory:** Despite logs showing resampling, audio might be taking a different path.
|
||||
**Investigation:** Add logging at every audio queue call site to trace actual execution path.
|
||||
|
||||
### 2. Frame Timing Issue
|
||||
**Theory:** `MusicPlayer::Update()` might not be called at the expected rate, or `RunAudioFrame()` might be called multiple times per frame.
|
||||
**Investigation:** Add frame timing logs to verify Update() is called at ~60 Hz and RunAudioFrame() once per call.
|
||||
|
||||
### 3. DSP Sample Extraction Bug
|
||||
**Theory:** `dsp.GetSamples()` might return wrong number of samples or from wrong position.
|
||||
**Investigation:** Log actual sample counts returned by GetSamples() vs expected (~533).
|
||||
|
||||
### 4. Main Emulator Still Generating Audio
|
||||
**Theory:** Even with `running=false`, main emulator's Update() might still be called and generating audio.
|
||||
**Investigation:** Add logging to main emulator's audio generation path to verify it stops when MusicPlayer is active.
|
||||
|
||||
### 5. Audio Stream Bypass Path
|
||||
**Theory:** There might be a code path that calls `QueueSamples()` (direct, non-resampled) instead of `QueueSamplesNative()`.
|
||||
**Investigation:** Search for all `QueueSamples` calls and verify none are being hit during music playback.
|
||||
|
||||
### 6. Resampling Disabled Mid-Playback
|
||||
**Theory:** `audio_stream_config_dirty_` or another flag might disable resampling during playback.
|
||||
**Investigation:** Add logging to `SetAudioStreamResampling()` to catch any disable calls.
|
||||
|
||||
## Suggested Next Steps
|
||||
|
||||
1. **Add comprehensive tracing** to follow a single frame of audio from DSP generation through to SDL queue
|
||||
2. **Verify frame timing** - confirm Update() runs at expected rate
|
||||
3. **Check for bypass paths** - ensure all audio goes through QueueSamplesNative()
|
||||
4. **Monitor resampling state** - ensure it stays enabled throughout playback
|
||||
5. **Test with simpler case** - generate known test tone and verify output rate
|
||||
|
||||
## Test Commands
|
||||
|
||||
```bash
|
||||
# Build with debug
|
||||
cmake --preset mac-dbg && cmake --build build --target yaze -j8
|
||||
|
||||
# Run with logging visible
|
||||
./build/bin/Debug/yaze.app/Contents/MacOS/yaze 2>&1 | grep -E "(AudioBackend|MusicPlayer|Emulator)"
|
||||
|
||||
# Run audio timing tests
|
||||
ctest --test-dir build -R "apu_timing|dsp_sample" -V
|
||||
```
|
||||
|
||||
## Key Constants
|
||||
|
||||
| Value | Meaning |
|
||||
|-------|---------|
|
||||
| 32040 Hz | Native SNES DSP sample rate |
|
||||
| 48000 Hz | SDL audio device sample rate |
|
||||
| 1.498 | Correct resampling ratio (48000/32040) |
|
||||
| 533 | Samples per NTSC frame at 32040 Hz |
|
||||
| ~60.0988 Hz | NTSC frame rate |
|
||||
| 1,024,000 Hz | APU clock rate |
|
||||
|
||||
## Files to Investigate
|
||||
|
||||
| File | Relevance |
|
||||
|------|-----------|
|
||||
| `src/app/editor/music/music_player.cc` | Main playback logic, Update() loop |
|
||||
| `src/app/emu/emulator.cc` | RunAudioFrame(), audio queuing |
|
||||
| `src/app/emu/audio/audio_backend.cc` | SDL audio, resampling |
|
||||
| `src/app/emu/audio/dsp.cc` | Sample generation, GetSamples() |
|
||||
| `src/app/emu/snes.cc` | RunAudioFrame(), SetSamples() |
|
||||
|
||||
## Contact
|
||||
|
||||
Previous investigation done by Claude Code agents. See git history for detailed changes.
|
||||
Reference in New Issue
Block a user