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 +=
67 static_cast<uint32_t>(track_blobs.back().data.size());
68 }
69 }
70
71 const uint32_t track_data_size = next_track_offset - track_data_base;
72 const uint32_t total_size =
73 header_size + segment_count * 16 + track_data_size;
74 result.data.reserve(total_size);
75
76 auto write_pointer = [&](uint16_t value, bool record) {
77 result.data.push_back(value & 0xFF);
78 result.data.push_back((value >> 8) & 0xFF);
79 if (record && value != 0) {
80 result.relocations.push_back(
81 static_cast<uint16_t>(result.data.size() - 2));
82 }
83 };
84
85 // Write segment pointers
86 uint16_t segment_offset = segment_table_base + base_address;
87 for (size_t i = 0; i < segment_count; ++i) {
88 write_pointer(segment_offset, true);
89 segment_offset = static_cast<uint16_t>(segment_offset + 16);
90 }
91
92 // End marker
93 if (song.HasLoop()) {
94 result.data.push_back(0xFF);
95 result.data.push_back(0x00);
96 uint16_t loop_target = static_cast<uint16_t>(
97 segment_table_base + base_address + 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 : static_cast<uint16_t>(ptrs[ch] + base_address);
109 write_pointer(pointer_value, ptrs[ch] != 0);
110 }
111 }
112
113 // Track data
114 for (const auto& blob : track_blobs) {
115 result.data.insert(result.data.end(), blob.data.begin(), blob.data.end());
116 }
117
118 return result;
119}
120
121absl::StatusOr<SpcSerializer::SerializeResult>
123 int start_segment,
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())
200 total += 2;
201
202 // Segments and tracks
203 for (const auto& segment : song.segments) {
204 total += 16; // Track pointers
205
206 for (const auto& track : segment.tracks) {
207 if (!track.is_empty) {
208 total += static_cast<int>(SerializeTrack(track).size());
209 }
210 }
211 }
212
213 return total;
214}
215
216std::vector<uint8_t> SpcSerializer::SerializeNote(const Note& note,
217 uint8_t* current_duration) {
218 std::vector<uint8_t> data;
219
220 // Output duration if different from current
221 if (note.has_duration_prefix && note.duration != *current_duration) {
222 data.push_back(note.duration);
223 *current_duration = note.duration;
224
225 // Output velocity if non-zero
226 if (note.velocity != 0) {
227 data.push_back(note.velocity);
228 }
229 }
230
231 // Output pitch
232 data.push_back(note.pitch);
233
234 return data;
235}
236
238 const MusicCommand& command) {
239 std::vector<uint8_t> data;
240
241 data.push_back(command.opcode);
242
243 int param_count = command.GetParamCount();
244 for (int i = 0; i < param_count; ++i) {
245 data.push_back(command.params[i]);
246 }
247
248 return data;
249}
250
252 uint16_t new_base_address) {
253 if (!result)
254 return;
255 const int32_t delta = static_cast<int32_t>(new_base_address) -
256 static_cast<int32_t>(result->base_address);
257 if (delta == 0) {
258 return;
259 }
260
261 for (uint16_t offset : result->relocations) {
262 if (offset + 1 >= result->data.size())
263 continue;
264 uint16_t value = static_cast<uint16_t>(result->data[offset] |
265 (result->data[offset + 1] << 8));
266 value = static_cast<uint16_t>(value + delta);
267 result->data[offset] = value & 0xFF;
268 result->data[offset + 1] = (value >> 8) & 0xFF;
269 }
270
271 result->base_address = new_base_address;
272}
273
274} // namespace music
275} // namespace zelda3
276} // namespace yaze
static int CalculateRequiredSpace(const MusicSong &song)
static std::vector< uint8_t > SerializeNote(const Note &note, uint8_t *current_duration)
static std::vector< uint8_t > SerializeCommand(const MusicCommand &command)
static absl::StatusOr< SerializeResult > SerializeSong(const MusicSong &song, uint16_t base_address)
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)
static std::vector< uint8_t > SerializeTrack(const MusicTrack &track)
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