Files
yaze/docs/internal/plans/music_editor.md

15 KiB
Raw Blame History

Music Editor Development Guide

This is a living document tracking the development of yaze's SNES music editor.

Overview

The music editor enables editing ALTTP music data stored in ROM, composing new songs, and integrating with the Oracle of Secrets music_macros ASM format for custom music.

Sprint Plan

Sprint 1: Data Model

Status: Complete

  • Core data structures (Note, MusicCommand, TrackEvent, MusicSong)
  • MusicBank manager for ROM loading/saving
  • SpcParser and SpcSerializer
  • BrrCodec for sample handling

Sprint 2: Tracker View (Read-Only)

Status: Complete

  • TrackerView component with 8-channel grid
  • Song selector and playback integration
  • Visualization of notes and commands

Sprint 3: Tracker Editing

Status: Complete

  • Keyboard navigation and note entry
  • Selection system (cells and ranges)
  • Undo/Redo system
  • Clipboard placeholder

Sprint 4: APU Playback

Status: Complete

  • Mixer Panel with Mute/Solo
  • Real-time Volume Meters
  • Master Oscilloscope
  • DSP state integration

Sprint 5: Piano Roll

Status: Complete

  • PianoRollView component (Horizontal)
  • Mouse-based note entry/editing
  • Zoom controls
  • Integrated with MusicEditor state

Sprint 6: Instruments & Samples

Status: Complete

  • InstrumentEditorView: ADSR visualization (ImPlot), property editing.
  • SampleEditorView: Waveform visualization (ImPlot), loop point editing.
  • Basic WAV import placeholder (generates sine wave).
  • Integrated into MusicEditor tabs.

Sprint 7: Project Management & Song Browser

Status: Complete

  • Song Browser:
    • Tree view separating "Vanilla Songs" (IDs 1-34) and "Custom Songs" (IDs 35+).
    • Dockable "Song Browser" card in Activity Bar.
    • Search filtering and context menus (Duplicate/Delete).
  • Integration:
    • Replaced legacy dropdown selector with robust browser selection.
    • Linked MusicEditor state to browser actions.

Sprint 8: Export & Polish

Status: In Progress

  • Integrated SpcParser::ParseSong() into MusicBank::LoadSongTable() so the editor now reflects real ROM song data (vanilla + custom slots).
  • ROM patching finalization (ensure SaveToRom works robustly).
  • ASM export (music_macros format generation).
  • SPC file export (standalone).
  • Full WAV import/resampling logic (replace dummy sine wave).

Quality Improvements (Review Findings)

Tracked fixes identified during the agent swarm code review.

High Priority (Blockers)

  • Replace hardcoded colors with theme system - tracker_view.cc (4 instances), music_editor.cc (3 instances)
  • Integrate SpcParser into MusicBank - MusicBank::LoadSongTable() now uses SpcParser::ParseSong()
  • Fix serializer relocation bug - Track address calculation in SerializeSong is incorrect
  • Implement SaveToRom() - Currently returns UnimplementedError

Medium Priority (Quality)

  • Add undo stack size limit - Capped at 50 states with FIFO eviction (music_editor.cc)
  • Fix oscilloscope ring buffer wrap-around - Proper mask & (kBufferSize - 1) (music_editor.cc)
  • Add VU meter smoothing/peak-hold - Currently uses instantaneous sample values
  • Change Copy()/Paste() to return UnimplementedError - Honest API reporting (music_editor.cc)

Low Priority (Nice to Have)

  • Add stereo oscilloscope toggle
  • Implement range deletion in TrackerView
  • Add visual octave indicator (F1/F2 feedback)
  • Per-song undo stacks

Test Gaps

  • ROM-dependent integration tests (test/e2e/rom_dependent/music_rom_test.cc)
  • Error handling tests for parse failures
  • Parse → Serialize → Parse roundtrip validation

Sprint 9: Expanded Music Banks (Oracle of Secrets Integration)

Status: Planned

The Oracle of Secrets ROM hack demonstrates expanded music bank support, allowing custom songs beyond vanilla ROM limits. This sprint brings those capabilities to yaze.

Goals:

  • Support for expanded bank detection (SongBank_OverworldExpanded_Main)
  • Dynamic bank allocation for custom songs (slots 35+)
  • Auxiliary bank support (SONG_POINTERS_AUX at $2B00)
  • Bank space visualization with overflow warnings
  • Auto-relocate songs when bank space exceeded

Bank Architecture (from Oracle of Secrets):

Symbol Address Purpose
SPC_ENGINE $0800 Sound driver code
SFX_DATA $17C0 Sound effects
SAMPLE_POINTERS $3C00 Sample directory
INSTRUMENT_DATA $3D00 Instrument definitions
SAMPLE_DATA $4000 BRR sample data
SONG_POINTERS $D000 Main song pointer table
SONG_POINTERS_AUX $2B00 Auxiliary song pointers

Sprint 10: ASM Editor Integration

Status: Planned

Full integration with Oracle of Secrets music_macros.asm format for bidirectional editing.

Goals:

  • Import .asm song files directly
  • Export songs to music_macros syntax
  • Syntax highlighting for N-SPC commands
  • Live preview compilation (ASM → binary → playback)
  • Subroutine deduplication detection

Export Format Example:

MySong:
!ARAMAddr = $D86A
dw !ARAMAddr+$0A      ; Intro section
dw !ARAMAddr+$1A      ; Looping section
dw $00FF              ; Fade-in
dw !ARAMAddr+$02      ; Loop start
dw $0000

.Channels
!ARAMC = !ARAMAddr-MySong
dw .Channel0+!ARAMC
dw .Channel1+!ARAMC
; ... 8 channels

.Channel0
  %SetMasterVolume($DA)
  %SetTempo(62)
  %Piano()
  %SetDurationN(!4th, $7F)
  %CallSubroutine(.MelodyA+!ARAMC, 3)
  db End

Sprint 11: Common Patterns Library

Status: Planned

Inspired by Oracle of Secrets documentation proposals for reusable musical patterns.

Goals:

  • Built-in pattern library (drum loops, arpeggios, basslines)
  • User-defined pattern saving/loading
  • Pattern browser with preview
  • Auto-convert repeated sections to subroutines

Pattern Categories:

  • Percussion: Standard 4/4 beats, fills, breaks
  • Basslines: Walking bass, pedal tones, arpeggiated
  • Arpeggios: Major, minor, diminished, suspended
  • Effects: Risers, sweeps, transitions

Oracle of Secrets Integration

music_macros.asm Reference

The Oracle of Secrets project uses a comprehensive macro system for music composition. Key macros that yaze should support for import/export:

Duration Constants:

Macro Value Description
!4th $48 Quarter note (72 ticks)
!4thD $6C Dotted quarter (108 ticks)
!4thT $30 Quarter triplet (48 ticks)

Plain-English Reference (Tracker/ASM)

Tracker Cell Cheatsheet

  • Tick column: absolute tick where the event starts (0-based). Quarter note = 72 ticks, eighth = 36, sixteenth = 18.
  • Pitch: C1 = $80, B6 = $C7; $C8 = tie, $C9 = rest.
  • Duration: stored per-note; tracker shows the event at the start tick, piano roll shows its full width.
  • Channel: 8 channels run in parallel; channel-local commands (transpose/volume/pan) affect only that lane.

Common N-SPC Commands (0xE00xFF)

  • $E0 SetInstrument i — pick instrument index i (024 vanilla ALTTP).
  • $E1 SetPan p — pan left/right ($00 hard L, $10 center, $1F hard R).
  • $E5 SetMasterVolume v — global volume.
  • $E7 SetTempo t — tempo; higher = faster.
  • $E9 GlobalTranspose s — shift all channels by semitones (signed byte).
  • $EA ChannelTranspose s — shift this channel only.
  • $ED SetChannelVolume v — per-channel volume.
  • $EF CallSubroutine addr, reps — jump to a subroutine reps times.
  • $F5 EchoVBits mask / $F6 EchoOff — enable/disable echo.
  • $F7 EchoParams vL vR delay — echo volume/delay.
  • $F9 PitchSlide — slide toward target pitch.
  • $FA PercussionPatch — switch to percussion set.
  • $FF (unused), $00 marks track end.

Instruments (ALTTP table at $3D00)

  • Bytes: [sample][AD][SR][gain][pitch_hi][pitch_lo]
    • sample = BRR slot (024 in vanilla).
    • AD = Attack (bits0-3, 0=slow, F=fast) + Decay (bits4-6).
    • SR = Sustain rate (bits0-4, higher = faster decay) + Sustain level (bits5-7, 0=quiet, 7=loud).
    • gain = raw gain byte (rarely used in ALTTP; ADSR preferred).
    • pitch_hi/lo = 16-bit pitch multiplier (0x1000 = normal; >0x1000 raises pitch).
  • In the tracker, a SetInstrument command changes these fields for the channel until another $E0 appears.

music_macros Quick Map

  • %SetInstrument(i)$E0 i
  • %SetPan(p)$E1 p
  • %SetTempo(t)$E7 t
  • %SetChannelVolume(v)$ED v
  • %CallSubroutine(label, reps)$EF + 16-bit pointer + reps
  • %SetDurationN(!4th, vel) → duration prefix + note byte; duration constants map to the table above.
  • Note names in macros (e.g., %C4, %F#3) map directly to tracker pitches (C1 = $80).

Durations and Snapping

  • Quarter = 72 ticks, Eighth = 36, Sixteenth = 18, Triplets use the T constants (e.g., !4thT = 48).
  • Piano roll snap defaults to sixteenth (18 ticks); turn snap off to place micro-timing. | !8th | $24 | Eighth note (36 ticks) | | !8thD | $36 | Dotted eighth (54 ticks) | | !8thT | $18 | Eighth triplet (24 ticks) | | !16th | $12 | Sixteenth (18 ticks) | | !32nd | $09 | Thirty-second (9 ticks) |

Instrument Helpers:

%Piano()      ; SetInstrument($18)
%Strings()    ; SetInstrument($09)
%Trumpet()    ; SetInstrument($11)
%Flute()      ; SetInstrument($16)
%Choir()      ; SetInstrument($15)
%Tympani()    ; SetInstrument($02)
%Harp()       ; SetInstrument($0F)
%Snare()      ; SetInstrument($13)

Note Constants (Octaves 1-6):

  • Format: C4, C4s (sharp), D4, etc.
  • Range: C1 ($80) through B6 ($C7)
  • Special: Tie ($C8), Rest ($C9), End ($00)

Expanded Bank Hooking

Oracle of Secrets expands music by hooking the LoadOverworldSongs routine:

org $008919 ; LoadOverworldSongs
  JSL LoadOverworldSongsExpanded

LoadOverworldSongsExpanded:
  LDA.w $0FFF : BEQ .light_world
    ; Load expanded bank for Dark World
    LDA.b #SongBank_OverworldExpanded_Main>>0
    STA.b $00
    ; ... set bank pointer
    RTL
  .light_world
    ; Load vanilla bank
    ; ...

yaze Implementation Strategy:

  1. Detect if ROM has expanded music patch applied
  2. Parse expanded song table at SongBank_OverworldExpanded_Main
  3. Allow editing both vanilla and expanded songs
  4. Generate proper ASM patches for new songs

Proposed Advanced Macros

The Oracle of Secrets documentation proposes these macros for cleaner composition (not yet implemented there, but yaze could pioneer):

; Define a reusable measure
%DefineMeasure(VerseMelody, !8th, C4, D4, E4, F4, G4, A4, B4, C5)
%DefineMeasure(VerseBass, !4th, C2, G2, A2, F2)

; Use in channel data
.Channel0
  %PlayMeasure(VerseMelody, 4)  ; Play 4 times
  db End

.Channel1
  %PlayMeasure(VerseBass, 4)
  db End

Benefits:

  • Channel data reads like a high-level arrangement
  • Automatic subroutine address calculation
  • Reduced code duplication

Technical Reference

N-SPC Command Bytes

Byte Macro Params Description
$E0 SetInstrument 1 Select instrument (0-24)
$E1 SetPan 1 Stereo pan (0-20, 10=center)
$E2 PanFade 2 Fade pan over time
$E3 VibratoOn 3 Enable pitch vibrato (delay, rate, depth)
$E4 VibratoOff 0 Disable vibrato
$E5 SetMasterVolume 1 Global volume
$E6 MasterVolumeFade 2 Fade master volume
$E7 SetTempo 1 Song tempo
$E8 TempoFade 2 Gradual tempo change
$E9 GlobalTranspose 1 Transpose all channels
$EA ChannelTranspose 1 Transpose single channel
$EB TremoloOn 3 Volume oscillation
$EC TremoloOff 0 Disable tremolo
$ED SetChannelVolume 1 Per-channel volume
$EE ChannelVolumeFade 2 Fade channel volume
$EF CallSubroutine 3 Call music subroutine (addr_lo, addr_hi, repeat)
$F0 VibratoFade 1 Fade vibrato depth
$F1 PitchEnvelopeTo 3 Pitch slide to note
$F2 PitchEnvelopeFrom 3 Pitch slide from note
$F3 PitchEnvelopeOff 0 Disable pitch envelope
$F4 Tuning 1 Fine pitch adjustment
$F5 EchoVBits 3 Echo enable + volumes
$F6 EchoOff 0 Disable echo
$F7 EchoParams 3 Echo delay/feedback/filter
$F8 EchoVolumeFade 3 Fade echo volumes
$F9 PitchSlide 3 Direct pitch slide
$FA PercussionPatchBass 1 Set percussion base instrument

ARAM Layout (Extended)

Address Contents
$0000-$00EF Zero Page
$00F0-$00FF APU Registers
$0100-$01FF Stack
$0200-$07FF Sound driver code
$0800-$17BF SPC Engine (expanded)
$17C0-$2AFF SFX Data
$2B00-$3BFF Auxiliary song pointers
$3C00-$3CFF Sample pointers
$3D00-$3DFF Instrument definitions
$3E00-$3FFF SFX instrument data
$4000-$CFFF Sample data (~36KB)
$D000-$FFBF Song data (~12KB)
$FFC0-$FFFF IPL ROM

Instrument IDs (Extended)

ID Name Notes
$00 Noise White noise generator
$01 Rain Rain/ambient
$02 Tympani Timpani drums
$03 Square wave 8-bit synth
$04 Saw wave Sawtooth synth
$05 Sine wave Pure tone
$06 Wobbly lead Modulated synth
$07 Compound saw Rich saw
$08 Tweet Bird/high chirp
$09 Strings A Orchestral strings
$0A Strings B Alternate strings
$0B Trombone Brass
$0C Cymbal Crash/ride
$0D Ocarina Wind instrument
$0E Chime Bell/chime
$0F Harp Plucked strings
$10 Splash Water/impact
$11 Trumpet Brass lead
$12 Horn French horn
$13 Snare A Snare drum
$14 Snare B Alternate snare
$15 Choir Vocal pad
$16 Flute Wind melody
$17 Oof Voice/grunt
$18 Piano Keyboard

Implementation Notes

ASM Export Strategy

When exporting to music_macros format:

  1. Header Generation:

    • Calculate !ARAMAddr based on target bank position
    • Generate intro/loop section pointers
    • Create channel pointer table with !ARAMC offsets
  2. Channel Data:

    • Convert TrackEvent objects to macro calls
    • Use instrument helper macros where applicable
    • Apply duration optimization (only emit when changed)
  3. Subroutine Extraction:

    • Detect repeated note patterns across channels
    • Extract to .subXXX labels
    • Replace inline data with %CallSubroutine() calls
  4. Naming Convention:

    • Prefer semantic names: .MelodyVerseA, .BasslineIntro, .PercussionFill1
    • Fall back to numbered: .sub001, .sub002 if semantic unclear

Bank Space Management

Constraints:

  • Main bank: ~12KB at $D000-$FFBF
  • Echo buffer can consume song space if delay > 2
  • Subroutines shared across all songs in bank

Overflow Handling:

  1. Calculate total song size before save
  2. Warn user if approaching limit (>90% used)
  3. Suggest moving songs to auxiliary bank
  4. Auto-suggest subroutine deduplication

References