yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
song_data.h
Go to the documentation of this file.
1#ifndef YAZE_ZELDA3_MUSIC_SONG_DATA_H
2#define YAZE_ZELDA3_MUSIC_SONG_DATA_H
3
4#include <array>
5#include <cstdint>
6#include <string>
7#include <vector>
8
9namespace yaze {
10namespace zelda3 {
11namespace music {
12
13// =============================================================================
14// Constants
15// =============================================================================
16
17// N-SPC command parameter counts (opcodes 0xE0-0xFF)
18// Index = opcode - 0xE0
19constexpr int kCommandParamCount[32] = {
20 1, // $E0 SetInstrument
21 1, // $E1 SetPan
22 2, // $E2 PanFade
23 3, // $E3 VibratoOn
24 0, // $E4 VibratoOff
25 1, // $E5 SetMasterVolume
26 2, // $E6 MasterVolumeFade
27 1, // $E7 SetTempo
28 2, // $E8 TempoFade
29 1, // $E9 GlobalTranspose
30 1, // $EA ChannelTranspose
31 3, // $EB TremoloOn
32 0, // $EC TremoloOff
33 1, // $ED SetChannelVolume
34 2, // $EE ChannelVolumeFade
35 3, // $EF CallSubroutine
36 1, // $F0 VibratoFade
37 3, // $F1 PitchEnvelopeTo
38 3, // $F2 PitchEnvelopeFrom
39 0, // $F3 PitchEnvelopeOff
40 1, // $F4 Tuning
41 3, // $F5 EchoVBits
42 0, // $F6 EchoOff
43 3, // $F7 EchoParams
44 3, // $F8 EchoVolumeFade
45 3, // $F9 PitchSlide
46 1, // $FA PercussionPatch
47 2, // $FB (unused)
48 0, // $FC (unused)
49 0, // $FD (unused)
50 0, // $FE (unused)
51 0 // $FF (unused)
52};
53
54// Note pitch range: C1 = 0x80, B6 = 0xC7
55constexpr uint8_t kNoteMinPitch = 0x80; // C1
56constexpr uint8_t kNoteMaxPitch = 0xC7; // B6
57constexpr uint8_t kNoteTie = 0xC8;
58constexpr uint8_t kNoteRest = 0xC9;
59constexpr uint8_t kTrackEnd = 0x00;
60
61// Duration constants (ticks, quarter note = 72)
62constexpr uint8_t kDurationQuarter = 0x48; // 72 ticks
63constexpr uint8_t kDurationQuarterDot = 0x6C; // 108 ticks
64constexpr uint8_t kDurationQuarterTrip = 0x30; // 48 ticks
65constexpr uint8_t kDurationEighth = 0x24; // 36 ticks
66constexpr uint8_t kDurationEighthDot = 0x36; // 54 ticks
67constexpr uint8_t kDurationEighthTrip = 0x18; // 24 ticks
68constexpr uint8_t kDurationSixteenth = 0x12; // 18 ticks
69constexpr uint8_t kDurationSixteenthDot = 0x1B; // 27 ticks
70constexpr uint8_t kDurationThirtySecond = 0x09; // 9 ticks
71
72// ROM addresses for song table blocks (PC file offsets for headerless ROM)
73// From usdasm disassembly:
74// - Banks 0, 1, 3 are contiguous starting at $19:8000 (PC 0xC8000)
75// - Bank 2 (dungeon) is separate at $1B:8000 (PC 0xD8000)
76// Song table blocks (loading to ARAM $D000):
77constexpr uint32_t kOverworldBankRom = 0xD1EF5; // $1A:9EF5
78constexpr uint32_t kDungeonBankRom = 0xD8000; // $1B:8000
79constexpr uint32_t kCreditsBankRom = 0xD5380; // $1A:D380
80
81// ARAM addresses
82constexpr uint16_t kSongTableAram = 0xD000;
83constexpr uint16_t kInstrumentTableAram = 0x3D00;
84constexpr uint16_t kSampleTableAram = 0x3C00;
85constexpr uint16_t kSampleDataAram = 0x4000;
86
87// Bank size limits
88constexpr int kOverworldBankMaxSize = 12032; // 0x2F00
89constexpr int kDungeonBankMaxSize = 11200;
90constexpr int kCreditsBankMaxSize = 4200;
91
92// =============================================================================
93// N-SPC Pitch Table
94// =============================================================================
95
96// N-SPC pitch table - maps note values 0x80-0xC7 to DSP pitch register values.
97// The DSP pitch is a 14-bit value where 0x1000 = base sample rate (32kHz).
98// Values derived from ALTTP N-SPC driver analysis.
99// Notes: C1 ($80) through B6 ($C7) = 72 entries, each octave doubles.
100constexpr uint16_t kNSpcPitchTable[72] = {
101 // Octave 1: C1-B1 (note $80-$8B)
102 0x0086, 0x008E, 0x0096, 0x009F, 0x00A9, 0x00B3,
103 0x00BE, 0x00C9, 0x00D6, 0x00E3, 0x00F1, 0x00FF,
104 // Octave 2: C2-B2 (note $8C-$97)
105 0x010C, 0x011C, 0x012C, 0x013E, 0x0152, 0x0166,
106 0x017C, 0x0192, 0x01AC, 0x01C6, 0x01E2, 0x01FE,
107 // Octave 3: C3-B3 (note $98-$A3)
108 0x0218, 0x0238, 0x0258, 0x027C, 0x02A4, 0x02CC,
109 0x02F8, 0x0324, 0x0358, 0x038C, 0x03C4, 0x03FC,
110 // Octave 4: C4-B4 (note $A4-$AF)
111 0x0430, 0x0470, 0x04B0, 0x04F8, 0x0548, 0x0598,
112 0x05F0, 0x0648, 0x06B0, 0x0718, 0x0788, 0x07F8,
113 // Octave 5: C5-B5 (note $B0-$BB)
114 0x0860, 0x08E0, 0x0960, 0x09F0, 0x0A90, 0x0B30,
115 0x0BE0, 0x0C90, 0x0D60, 0x0E30, 0x0F10, 0x0FF0,
116 // Octave 6: C6-B6 (note $BC-$C7)
117 0x10C0, 0x11C0, 0x12C0, 0x13E0, 0x1520, 0x1660,
118 0x17C0, 0x1920, 0x1AC0, 0x1C60, 0x1E20, 0x1FE0,
119};
120
126inline uint16_t LookupNSpcPitch(uint8_t note_byte) {
127 if (note_byte < kNoteMinPitch || note_byte > kNoteMaxPitch) {
128 return 0x1000; // Base pitch for invalid notes
129 }
130 return kNSpcPitchTable[note_byte - kNoteMinPitch];
131}
132
133// =============================================================================
134// Expanded Bank Constants (Oracle of Secrets format)
135// =============================================================================
136
137// Oracle of Secrets expanded music system addresses
138constexpr uint32_t kExpandedOverworldBankRom = 0x1A9EF5; // SongBank_OverworldExpanded_Main
139constexpr uint32_t kExpandedAuxBankRom = 0x1ACCA7; // SongBank_Overworld_Auxiliary
140constexpr uint16_t kAuxSongTableAram = 0x2B00; // SONG_POINTERS_AUX
141constexpr int kExpandedOverworldBankMaxSize = 0x2DAE; // ~11KB
142constexpr int kAuxBankMaxSize = 0x0688; // ~1.6KB
143
144// Hook point for expanded music detection
145constexpr uint32_t kExpandedMusicHookAddress = 0x008919; // LoadOverworldSongs
146constexpr uint8_t kJslOpcode = 0x22; // JSL instruction
147
148// =============================================================================
149// Command Types
150// =============================================================================
151
152enum class CommandType : uint8_t {
153 SetInstrument = 0xE0,
154 SetPan = 0xE1,
155 PanFade = 0xE2,
156 VibratoOn = 0xE3,
157 VibratoOff = 0xE4,
158 SetMasterVolume = 0xE5,
159 MasterVolumeFade = 0xE6,
160 SetTempo = 0xE7,
161 TempoFade = 0xE8,
162 GlobalTranspose = 0xE9,
163 ChannelTranspose = 0xEA,
164 TremoloOn = 0xEB,
165 TremoloOff = 0xEC,
166 SetChannelVolume = 0xED,
167 ChannelVolumeFade = 0xEE,
168 CallSubroutine = 0xEF,
169 VibratoFade = 0xF0,
170 PitchEnvelopeTo = 0xF1,
171 PitchEnvelopeFrom = 0xF2,
172 PitchEnvelopeOff = 0xF3,
173 Tuning = 0xF4,
174 EchoVBits = 0xF5,
175 EchoOff = 0xF6,
176 EchoParams = 0xF7,
177 EchoVolumeFade = 0xF8,
178 PitchSlide = 0xF9,
179 PercussionPatch = 0xFA
180};
181
182// =============================================================================
183// Data Structures
184// =============================================================================
185
192struct Note {
193 uint8_t pitch = kNoteRest; // 0x80-0xC7 for notes, 0xC8=tie, 0xC9=rest
194 uint8_t duration = 0; // Duration in ticks (quarter = 72)
195 uint8_t velocity = 0; // Optional articulation byte
196 bool has_duration_prefix = false; // True if duration byte precedes note
197
198 // Helper methods
199 bool IsNote() const { return pitch >= kNoteMinPitch && pitch <= kNoteMaxPitch; }
200 bool IsTie() const { return pitch == kNoteTie; }
201 bool IsRest() const { return pitch == kNoteRest; }
202
203 // Convert pitch to note name (e.g., "C4", "F#3")
204 std::string GetNoteName() const;
205
206 // Convert pitch to octave (1-6)
207 int GetOctave() const {
208 if (!IsNote()) return 0;
209 return ((pitch - kNoteMinPitch) / 12) + 1;
210 }
211
212 // Convert pitch to semitone within octave (0-11)
213 int GetSemitone() const {
214 if (!IsNote()) return 0;
215 return (pitch - kNoteMinPitch) % 12;
216 }
217};
218
223 uint8_t opcode = 0;
224 std::array<uint8_t, 3> params = {0, 0, 0};
225
226 // Get parameter count for this command
227 int GetParamCount() const {
228 if (opcode < 0xE0) return 0;
229 return kCommandParamCount[opcode - 0xE0];
230 }
231
232 // Get command type
233 CommandType GetType() const { return static_cast<CommandType>(opcode); }
234
235 // Helper for subroutine commands
236 bool IsSubroutine() const { return opcode == 0xEF; }
237 uint16_t GetSubroutineAddress() const {
238 return static_cast<uint16_t>(params[0]) |
239 (static_cast<uint16_t>(params[1]) << 8);
240 }
241 uint8_t GetSubroutineRepeatCount() const { return params[2]; }
242};
243
248 enum class Type { Note, Command, SubroutineCall, End };
249
251 uint16_t tick = 0; // Absolute position in ticks
252 uint16_t rom_offset = 0; // Original ROM address (for round-trip)
253
254 // Data (only one is valid based on type)
257
258 // Factory methods
259 static TrackEvent MakeNote(uint16_t tick, uint8_t pitch, uint8_t duration,
260 uint8_t velocity = 0) {
261 TrackEvent event;
262 event.type = Type::Note;
263 event.tick = tick;
264 event.note.pitch = pitch;
265 event.note.duration = duration;
266 event.note.velocity = velocity;
267 return event;
268 }
269
270 static TrackEvent MakeCommand(uint16_t tick, uint8_t opcode,
271 uint8_t p1 = 0, uint8_t p2 = 0, uint8_t p3 = 0) {
272 TrackEvent event;
273 event.type = Type::Command;
274 event.tick = tick;
275 event.command.opcode = opcode;
276 event.command.params = {p1, p2, p3};
277 return event;
278 }
279
280 static TrackEvent MakeEnd(uint16_t tick) {
281 TrackEvent event;
282 event.type = Type::End;
283 event.tick = tick;
284 return event;
285 }
286};
287
292 std::vector<TrackEvent> events;
293 uint16_t rom_address = 0;
294 uint16_t duration_ticks = 0;
295 bool is_empty = true;
296
297 // Calculate total duration from events
298 void CalculateDuration();
299
300 // Get event at tick position (or nullptr)
301 const TrackEvent* GetEventAtTick(uint16_t tick) const;
302
303 // Insert event maintaining tick order
304 void InsertEvent(TrackEvent event);
305
306 // Remove event at index
307 void RemoveEvent(size_t index);
308};
309
316 std::array<MusicTrack, 8> tracks;
317 uint16_t rom_address = 0;
318
319 // Get the longest track duration
320 uint16_t GetDuration() const {
321 uint16_t max_duration = 0;
322 for (const auto& track : tracks) {
323 if (track.duration_ticks > max_duration) {
324 max_duration = track.duration_ticks;
325 }
326 }
327 return max_duration;
328 }
329};
330
334struct MusicSong {
335 std::string name;
336 std::vector<MusicSegment> segments;
337 int loop_point = -1; // Segment index to loop to, -1 = no loop
338 uint8_t bank = 0; // 0=overworld, 1=dungeon, 2=credits
339 uint16_t rom_address = 0;
340 bool modified = false;
341
342 // Get total song duration in ticks
343 uint32_t GetTotalDuration() const {
344 uint32_t total = 0;
345 for (const auto& segment : segments) {
346 total += segment.GetDuration();
347 }
348 return total;
349 }
350
351 // Check if song loops
352 bool HasLoop() const { return loop_point >= 0; }
353};
354
359 uint8_t sample_index = 0;
360 uint8_t attack = 0; // Attack rate (0-15)
361 uint8_t decay = 0; // Decay rate (0-7)
362 uint8_t sustain_level = 0; // Sustain level (0-7)
363 uint8_t sustain_rate = 0; // Sustain rate (0-31)
364 uint8_t gain = 0; // Gain mode/value
365 uint16_t pitch_mult = 0; // Pitch multiplier
366 std::string name;
367
368 // Pack ADSR bytes (AD, SR format)
369 uint8_t GetADByte() const {
370 return (attack & 0x0F) | ((decay & 0x07) << 4) | 0x80;
371 }
372 uint8_t GetSRByte() const {
373 return (sustain_rate & 0x1F) | ((sustain_level & 0x07) << 5);
374 }
375
376 // Unpack ADSR from bytes
377 void SetFromBytes(uint8_t ad, uint8_t sr) {
378 attack = ad & 0x0F;
379 decay = (ad >> 4) & 0x07;
380 sustain_rate = sr & 0x1F;
381 sustain_level = (sr >> 5) & 0x07;
382 }
383};
384
389 std::vector<int16_t> pcm_data; // Decoded PCM (for display/editing)
390 std::vector<uint8_t> brr_data; // Encoded BRR (for ROM)
391 uint16_t loop_point = 0; // Loop start in samples
392 bool loops = false;
393 std::string name;
394
395 // Get duration in samples
396 size_t GetSampleCount() const { return pcm_data.size(); }
397
398 // Check if sample is loaded
399 bool IsLoaded() const { return !pcm_data.empty() || !brr_data.empty(); }
400};
401
402// =============================================================================
403// Default Instrument Names
404// =============================================================================
405
406constexpr const char* kDefaultInstrumentNames[] = {
407 "Noise", // $00
408 "Tympani", // $01
409 "Trombone", // $02
410 "Ocarina", // $03
411 "Harp", // $04
412 "Splash", // $05
413 "Trumpet", // $06
414 "Horn", // $07
415 "Snare", // $08
416 "Choir", // $09
417 "Flute", // $0A
418 "Piano", // $0B
419 "Cymbal", // $0C
420 "Strings", // $0D
421 "Sawtooth", // $0E
422 "Sine" // $0F
423};
424
425constexpr const char* kNoteNames[] = {
426 "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
427};
428
429// =============================================================================
430// Inline Implementations
431// =============================================================================
432
433inline std::string Note::GetNoteName() const {
434 if (IsTie()) return "---";
435 if (IsRest()) return "...";
436 if (!IsNote()) return "???";
437
438 int semitone = GetSemitone();
439 int octave = GetOctave();
440 return std::string(kNoteNames[semitone]) + std::to_string(octave);
441}
442
444 duration_ticks = 0;
445 for (const auto& event : events) {
446 if (event.type == TrackEvent::Type::Note) {
447 uint16_t end_tick = event.tick + event.note.duration;
448 if (end_tick > duration_ticks) {
449 duration_ticks = end_tick;
450 }
451 } else if (event.type == TrackEvent::Type::End) {
452 if (event.tick > duration_ticks) {
453 duration_ticks = event.tick;
454 }
455 }
456 }
457 is_empty = events.empty();
458}
459
460inline const TrackEvent* MusicTrack::GetEventAtTick(uint16_t tick) const {
461 for (const auto& event : events) {
462 if (event.tick == tick) return &event;
463 }
464 return nullptr;
465}
466
468 auto it = events.begin();
469 while (it != events.end() && it->tick <= event.tick) {
470 ++it;
471 }
472 events.insert(it, event);
473 is_empty = false;
475}
476
477inline void MusicTrack::RemoveEvent(size_t index) {
478 if (index < events.size()) {
479 events.erase(events.begin() + static_cast<ptrdiff_t>(index));
480 is_empty = events.empty();
482 }
483}
484
485} // namespace music
486} // namespace zelda3
487} // namespace yaze
488
489#endif // YAZE_ZELDA3_MUSIC_SONG_DATA_H
constexpr uint8_t kNoteRest
Definition song_data.h:58
constexpr uint32_t kExpandedAuxBankRom
Definition song_data.h:139
constexpr uint16_t kNSpcPitchTable[72]
Definition song_data.h:100
constexpr uint8_t kTrackEnd
Definition song_data.h:59
constexpr uint8_t kDurationThirtySecond
Definition song_data.h:70
constexpr uint8_t kDurationQuarterDot
Definition song_data.h:63
constexpr uint16_t kAuxSongTableAram
Definition song_data.h:140
constexpr const char * kNoteNames[]
Definition song_data.h:425
constexpr uint8_t kDurationSixteenth
Definition song_data.h:68
constexpr uint16_t kSampleTableAram
Definition song_data.h:84
constexpr uint16_t kSampleDataAram
Definition song_data.h:85
constexpr uint8_t kDurationEighth
Definition song_data.h:65
constexpr uint8_t kNoteMinPitch
Definition song_data.h:55
constexpr const char * kDefaultInstrumentNames[]
Definition song_data.h:406
constexpr uint32_t kExpandedOverworldBankRom
Definition song_data.h:138
constexpr int kCommandParamCount[32]
Definition song_data.h:19
constexpr uint8_t kJslOpcode
Definition song_data.h:146
constexpr uint8_t kDurationQuarter
Definition song_data.h:62
constexpr uint16_t kInstrumentTableAram
Definition song_data.h:83
constexpr uint32_t kOverworldBankRom
Definition song_data.h:77
constexpr uint16_t kSongTableAram
Definition song_data.h:82
constexpr int kCreditsBankMaxSize
Definition song_data.h:90
constexpr int kExpandedOverworldBankMaxSize
Definition song_data.h:141
constexpr uint32_t kCreditsBankRom
Definition song_data.h:79
constexpr int kDungeonBankMaxSize
Definition song_data.h:89
constexpr uint8_t kDurationEighthTrip
Definition song_data.h:67
constexpr uint8_t kDurationQuarterTrip
Definition song_data.h:64
constexpr uint8_t kNoteTie
Definition song_data.h:57
constexpr uint8_t kDurationSixteenthDot
Definition song_data.h:69
uint16_t LookupNSpcPitch(uint8_t note_byte)
Look up the DSP pitch value for an N-SPC note byte.
Definition song_data.h:126
constexpr uint8_t kDurationEighthDot
Definition song_data.h:66
constexpr uint8_t kNoteMaxPitch
Definition song_data.h:56
constexpr uint32_t kDungeonBankRom
Definition song_data.h:78
constexpr int kOverworldBankMaxSize
Definition song_data.h:88
constexpr int kAuxBankMaxSize
Definition song_data.h:142
constexpr uint32_t kExpandedMusicHookAddress
Definition song_data.h:145
Represents an N-SPC command (opcodes 0xE0-0xFF).
Definition song_data.h:222
uint16_t GetSubroutineAddress() const
Definition song_data.h:237
uint8_t GetSubroutineRepeatCount() const
Definition song_data.h:241
std::array< uint8_t, 3 > params
Definition song_data.h:224
CommandType GetType() const
Definition song_data.h:233
An instrument definition with ADSR envelope.
Definition song_data.h:358
void SetFromBytes(uint8_t ad, uint8_t sr)
Definition song_data.h:377
A BRR-encoded audio sample.
Definition song_data.h:388
std::vector< uint8_t > brr_data
Definition song_data.h:390
std::vector< int16_t > pcm_data
Definition song_data.h:389
A segment containing 8 parallel tracks.
Definition song_data.h:315
std::array< MusicTrack, 8 > tracks
Definition song_data.h:316
A complete song composed of segments.
Definition song_data.h:334
std::vector< MusicSegment > segments
Definition song_data.h:336
uint32_t GetTotalDuration() const
Definition song_data.h:343
One of 8 channels in a music segment.
Definition song_data.h:291
const TrackEvent * GetEventAtTick(uint16_t tick) const
Definition song_data.h:460
void RemoveEvent(size_t index)
Definition song_data.h:477
void InsertEvent(TrackEvent event)
Definition song_data.h:467
std::vector< TrackEvent > events
Definition song_data.h:292
Represents a single musical note.
Definition song_data.h:192
std::string GetNoteName() const
Definition song_data.h:433
A single event in a music track (note, command, or control).
Definition song_data.h:247
static TrackEvent MakeNote(uint16_t tick, uint8_t pitch, uint8_t duration, uint8_t velocity=0)
Definition song_data.h:259
static TrackEvent MakeCommand(uint16_t tick, uint8_t opcode, uint8_t p1=0, uint8_t p2=0, uint8_t p3=0)
Definition song_data.h:270
static TrackEvent MakeEnd(uint16_t tick)
Definition song_data.h:280