yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
spc_serializer.cc
Go to the documentation of this file.
2
3#include "absl/strings/str_format.h"
4
5namespace yaze {
6namespace zelda3 {
7namespace music {
8
9absl::StatusOr<SpcSerializer::SerializeResult> SpcSerializer::SerializeSong(
10 const MusicSong& song, uint16_t base_address) {
11 SerializeResult result;
12 result.base_address = base_address;
13
14 if (song.segments.empty()) {
15 return absl::InvalidArgumentError("Song has no segments");
16 }
17
18 const size_t segment_count = song.segments.size();
19 const uint16_t header_size =
20 static_cast<uint16_t>(segment_count * 2 + 2 + (song.HasLoop() ? 2 : 0));
21 const uint16_t segment_table_base = header_size;
22 const uint16_t track_data_base =
23 static_cast<uint16_t>(segment_table_base + segment_count * 16);
24
25 struct TrackBlob {
26 uint16_t offset = 0;
27 std::vector<uint8_t> data;
28 };
29
30 std::vector<std::array<uint16_t, 8>> segment_track_ptrs(segment_count);
31 std::vector<TrackBlob> track_blobs;
32 track_blobs.reserve(segment_count * 2);
33
34 uint32_t next_track_offset = track_data_base;
35
36 for (size_t seg_index = 0; seg_index < segment_count; ++seg_index) {
37 const auto& segment = song.segments[seg_index];
38 auto& ptrs = segment_track_ptrs[seg_index];
39 ptrs.fill(0);
40
41 for (int ch = 0; ch < 8; ++ch) {
42 const auto& track = segment.tracks[ch];
43 if (track.is_empty || track.events.empty()) {
44 ptrs[ch] = 0;
45 continue;
46 }
47
48 auto track_bytes = SerializeTrack(track);
49 if (track_bytes.empty()) {
50 ptrs[ch] = 0;
51 continue;
52 }
53
54 if (next_track_offset + track_bytes.size() > 0x10000) {
55 return absl::ResourceExhaustedError(
56 "Serialized track exceeds SPC address space");
57 }
58
59 ptrs[ch] = static_cast<uint16_t>(next_track_offset);
60
61 TrackBlob blob;
62 blob.offset = ptrs[ch];
63 blob.data = std::move(track_bytes);
64 track_blobs.push_back(std::move(blob));
65
66 next_track_offset += static_cast<uint32_t>(track_blobs.back().data.size());
67 }
68 }
69
70 const uint32_t track_data_size = next_track_offset - track_data_base;
71 const uint32_t total_size =
72 header_size + segment_count * 16 + track_data_size;
73 result.data.reserve(total_size);
74
75 auto write_pointer = [&](uint16_t value, bool record) {
76 result.data.push_back(value & 0xFF);
77 result.data.push_back((value >> 8) & 0xFF);
78 if (record && value != 0) {
79 result.relocations.push_back(
80 static_cast<uint16_t>(result.data.size() - 2));
81 }
82 };
83
84 // Write segment pointers
85 uint16_t segment_offset = segment_table_base + base_address;
86 for (size_t i = 0; i < segment_count; ++i) {
87 write_pointer(segment_offset, true);
88 segment_offset = static_cast<uint16_t>(segment_offset + 16);
89 }
90
91 // End marker
92 if (song.HasLoop()) {
93 result.data.push_back(0xFF);
94 result.data.push_back(0x00);
95 uint16_t loop_target =
96 static_cast<uint16_t>(segment_table_base + base_address +
97 song.loop_point * 16);
98 write_pointer(loop_target, true);
99 } else {
100 result.data.push_back(0x00);
101 result.data.push_back(0x00);
102 }
103
104 // Segment track pointer tables
105 for (const auto& ptrs : segment_track_ptrs) {
106 for (int ch = 0; ch < 8; ++ch) {
107 uint16_t pointer_value =
108 (ptrs[ch] == 0) ? 0
109 : static_cast<uint16_t>(ptrs[ch] + base_address);
110 write_pointer(pointer_value, ptrs[ch] != 0);
111 }
112 }
113
114 // Track data
115 for (const auto& blob : track_blobs) {
116 result.data.insert(result.data.end(), blob.data.begin(), blob.data.end());
117 }
118
119 return result;
120}
121
122absl::StatusOr<SpcSerializer::SerializeResult>
124 uint16_t base_address) {
125 // Validate start segment
126 if (start_segment < 0 ||
127 start_segment >= static_cast<int>(song.segments.size())) {
128 return absl::InvalidArgumentError(
129 absl::StrFormat("Invalid start segment: %d (song has %zu segments)",
130 start_segment, song.segments.size()));
131 }
132
133 // If starting from segment 0, use the normal serialization
134 if (start_segment == 0) {
135 return SerializeSong(song, base_address);
136 }
137
138 // Create a temporary song with segments from start_segment onwards
139 MusicSong partial_song;
140 partial_song.name = song.name;
141 partial_song.bank = song.bank;
142
143 // Copy segments from start_segment to end
144 for (size_t i = start_segment; i < song.segments.size(); ++i) {
145 partial_song.segments.push_back(song.segments[i]);
146 }
147
148 // Adjust loop point if song has one
149 if (song.HasLoop() && song.loop_point >= start_segment) {
150 partial_song.loop_point = song.loop_point - start_segment;
151 } else {
152 partial_song.loop_point = -1; // No loop or loop before start
153 }
154
155 // Serialize the partial song
156 return SerializeSong(partial_song, base_address);
157}
158
159std::vector<uint8_t> SpcSerializer::SerializeTrack(const MusicTrack& track) {
160 std::vector<uint8_t> data;
161 uint8_t current_duration = 0;
162
163 for (const auto& event : track.events) {
164 switch (event.type) {
166 auto note_bytes = SerializeNote(event.note, &current_duration);
167 data.insert(data.end(), note_bytes.begin(), note_bytes.end());
168 break;
169 }
170
172 auto cmd_bytes = SerializeCommand(event.command);
173 data.insert(data.end(), cmd_bytes.begin(), cmd_bytes.end());
174 break;
175 }
176
178 data.push_back(kTrackEnd);
179 break;
180
181 default:
182 break;
183 }
184 }
185
186 // Ensure track ends with end marker
187 if (data.empty() || data.back() != kTrackEnd) {
188 data.push_back(kTrackEnd);
189 }
190
191 return data;
192}
193
195 int total = 0;
196
197 // Song header
198 total += static_cast<int>(song.segments.size()) * 2 + 2;
199 if (song.HasLoop()) total += 2;
200
201 // Segments and tracks
202 for (const auto& segment : song.segments) {
203 total += 16; // Track pointers
204
205 for (const auto& track : segment.tracks) {
206 if (!track.is_empty) {
207 total += static_cast<int>(SerializeTrack(track).size());
208 }
209 }
210 }
211
212 return total;
213}
214
215std::vector<uint8_t> SpcSerializer::SerializeNote(const Note& note,
216 uint8_t* current_duration) {
217 std::vector<uint8_t> data;
218
219 // Output duration if different from current
220 if (note.has_duration_prefix && note.duration != *current_duration) {
221 data.push_back(note.duration);
222 *current_duration = note.duration;
223
224 // Output velocity if non-zero
225 if (note.velocity != 0) {
226 data.push_back(note.velocity);
227 }
228 }
229
230 // Output pitch
231 data.push_back(note.pitch);
232
233 return data;
234}
235
236std::vector<uint8_t> SpcSerializer::SerializeCommand(const MusicCommand& command) {
237 std::vector<uint8_t> data;
238
239 data.push_back(command.opcode);
240
241 int param_count = command.GetParamCount();
242 for (int i = 0; i < param_count; ++i) {
243 data.push_back(command.params[i]);
244 }
245
246 return data;
247}
248
250 uint16_t new_base_address) {
251 if (!result) return;
252 const int32_t delta =
253 static_cast<int32_t>(new_base_address) -
254 static_cast<int32_t>(result->base_address);
255 if (delta == 0) {
256 return;
257 }
258
259 for (uint16_t offset : result->relocations) {
260 if (offset + 1 >= result->data.size()) continue;
261 uint16_t value =
262 static_cast<uint16_t>(result->data[offset] |
263 (result->data[offset + 1] << 8));
264 value = static_cast<uint16_t>(value + delta);
265 result->data[offset] = value & 0xFF;
266 result->data[offset + 1] = (value >> 8) & 0xFF;
267 }
268
269 result->base_address = new_base_address;
270}
271
272} // namespace music
273} // namespace zelda3
274} // namespace yaze
static int CalculateRequiredSpace(const MusicSong &song)
Calculate the space required for a song.
static std::vector< uint8_t > SerializeNote(const Note &note, uint8_t *current_duration)
Serialize a note event.
static std::vector< uint8_t > SerializeCommand(const MusicCommand &command)
Serialize a command event.
static absl::StatusOr< SerializeResult > SerializeSong(const MusicSong &song, uint16_t base_address)
Serialize a complete song to binary format.
static absl::StatusOr< SerializeResult > SerializeSongFromSegment(const MusicSong &song, int start_segment, uint16_t base_address)
Serialize a song starting from a specific segment (for seeking).
static void ApplyBaseAddress(SerializeResult *result, uint16_t new_base_address)
Adjust all serialized pointers to a new base address.
static std::vector< uint8_t > SerializeTrack(const MusicTrack &track)
Serialize a single track to binary format.
constexpr uint8_t kTrackEnd
Definition song_data.h:59
Represents an N-SPC command (opcodes 0xE0-0xFF).
Definition song_data.h:222
std::array< uint8_t, 3 > params
Definition song_data.h:224
A complete song composed of segments.
Definition song_data.h:334
std::vector< MusicSegment > segments
Definition song_data.h:336
One of 8 channels in a music segment.
Definition song_data.h:291
std::vector< TrackEvent > events
Definition song_data.h:292
Represents a single musical note.
Definition song_data.h:192
Result of serialization with relocation info.
Definition spc_parser.h:180