yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
asm_exporter.cc
Go to the documentation of this file.
2
3#include <fstream>
4#include <sstream>
5
6#include "absl/strings/str_format.h"
7
8namespace yaze {
9namespace zelda3 {
10namespace music {
11
12absl::StatusOr<std::string> AsmExporter::ExportSong(
13 const MusicSong& song, const AsmExportOptions& options) {
14 std::ostringstream out;
15
16 // File header comment
17 if (options.include_comments) {
18 out << "; "
19 "==================================================================="
20 "==========\n";
21 out << "; " << song.name << "\n";
22 out << "; Exported from yaze music editor\n";
23 out << "; Format: Oracle of Secrets music_macros.asm compatible\n";
24 out << "; "
25 "==================================================================="
26 "==========\n\n";
27 }
28
29 // Generate song header
30 out << GenerateHeader(song, options);
31 out << "\n";
32
33 // Generate channel pointer table
34 out << GenerateChannelPointers(song, options);
35 out << "\n";
36
37 // Generate channel data for each segment
38 for (size_t seg_idx = 0; seg_idx < song.segments.size(); ++seg_idx) {
39 const auto& segment = song.segments[seg_idx];
40
41 if (options.include_comments && song.segments.size() > 1) {
42 out << "; --- Segment " << seg_idx << " ---\n";
43 }
44
45 for (int ch = 0; ch < 8; ++ch) {
46 const auto& track = segment.tracks[ch];
47 if (track.is_empty)
48 continue;
49
50 out << GenerateChannelData(track, ch, options);
51 out << "\n";
52 }
53 }
54
55 return out.str();
56}
57
58absl::Status AsmExporter::ExportToFile(const MusicSong& song,
59 const std::string& path,
60 const AsmExportOptions& options) {
61 auto result = ExportSong(song, options);
62 if (!result.ok()) {
63 return result.status();
64 }
65
66 std::ofstream file(path);
67 if (!file.is_open()) {
68 return absl::FailedPreconditionError(
69 absl::StrFormat("Failed to open file for writing: %s", path));
70 }
71
72 file << result.value();
73 file.close();
74
75 return absl::OkStatus();
76}
77
78std::string AsmExporter::GenerateHeader(const MusicSong& song,
79 const AsmExportOptions& options) {
80 std::ostringstream out;
81
82 // Song label
83 out << options.label_prefix << ":\n";
84
85 // ARAM address constant
86 out << absl::StrFormat("!ARAMAddr = $%04X\n", options.base_aram_address);
87
88 // Section pointers (simplified - intro and loop point to same for now)
89 // In a full implementation, we'd calculate actual segment offsets
90 uint16_t intro_offset = 0x0A; // After header
91 uint16_t loop_offset = 0x1A; // After intro
92
93 out << absl::StrFormat("dw !ARAMAddr+$%02X ; Intro section\n",
94 intro_offset);
95 if (song.HasLoop()) {
96 out << absl::StrFormat("dw !ARAMAddr+$%02X ; Loop section\n",
97 loop_offset);
98 } else {
99 out << "dw $00FF ; Default fade\n";
100 }
101 out << "dw !ARAMAddr+$02 ; Loop start\n";
102 out << "dw $0000 ; Reserved\n";
103
104 return out.str();
105}
106
108 const MusicSong& song, const AsmExportOptions& options) {
109 std::ostringstream out;
110
111 out << ".Channels\n";
112 out << absl::StrFormat("!ARAMC = !ARAMAddr-%s\n", options.label_prefix);
113
114 // Generate 8 channel pointers
115 for (int ch = 0; ch < 8; ++ch) {
116 bool has_data = false;
117 for (const auto& segment : song.segments) {
118 if (!segment.tracks[ch].is_empty) {
119 has_data = true;
120 break;
121 }
122 }
123
124 if (has_data) {
125 out << absl::StrFormat("dw .Channel%d+!ARAMC\n", ch);
126 } else {
127 out << "dw $0000 ; Unused\n";
128 }
129 }
130
131 return out.str();
132}
133
135 int channel_index,
136 const AsmExportOptions& options) {
137 std::ostringstream out;
138
139 out << absl::StrFormat(".Channel%d\n", channel_index);
140
141 uint8_t current_duration = 0;
142
143 for (const auto& event : track.events) {
144 std::string event_asm = ConvertEventToAsm(event, options, current_duration);
145 if (!event_asm.empty()) {
146 out << " " << event_asm << "\n";
147 }
148 }
149
150 // End marker
151 out << " db End\n";
152
153 return out.str();
154}
155
157 const AsmExportOptions& options,
158 uint8_t& current_duration) {
159 switch (event.type) {
161 return ConvertNoteToAsm(event.note, options, current_duration);
164 return ConvertCommandToAsm(event.command, options);
166 return ""; // Will be added by GenerateChannelData
167 default:
168 return "";
169 }
170}
171
172std::string AsmExporter::ConvertNoteToAsm(const Note& note,
173 const AsmExportOptions& options,
174 uint8_t& current_duration) {
175 std::ostringstream out;
176
177 // Check if duration changed
178 if (note.has_duration_prefix && note.duration != current_duration) {
179 const char* duration_const = GetDurationConstant(note.duration);
180 if (duration_const) {
181 if (note.velocity != 0) {
182 out << absl::StrFormat("%%SetDurationN(%s, $%02X)\n ", duration_const,
183 note.velocity);
184 } else {
185 out << absl::StrFormat("%%SetDuration(%s)\n ", duration_const);
186 }
187 } else {
188 // Raw duration byte
189 out << absl::StrFormat("db $%02X\n ", note.duration);
190 }
191 current_duration = note.duration;
192 }
193
194 // Note name
195 std::string note_name = GetNoteName(note.pitch);
196 out << "db " << note_name;
197
198 return out.str();
199}
200
202 const AsmExportOptions& options) {
203 std::ostringstream out;
204
205 // Special handling for SetInstrument with macro support
206 if (options.use_instrument_macros && cmd.opcode == 0xE0) {
207 const char* macro = GetInstrumentMacro(cmd.params[0]);
208 if (macro) {
209 return macro;
210 }
211 }
212
213 // Get the command macro name
214 const char* macro_name = GetCommandMacro(cmd.opcode);
215 if (!macro_name) {
216 // Unknown command, output raw bytes
217 out << absl::StrFormat("db $%02X", cmd.opcode);
218 int param_count = cmd.GetParamCount();
219 for (int i = 0; i < param_count; ++i) {
220 out << absl::StrFormat(", $%02X", cmd.params[i]);
221 }
222 return out.str();
223 }
224
225 // Format the macro call
226 int param_count = GetCommandParamCount(cmd.opcode);
227 out << "%" << macro_name << "(";
228
229 for (int i = 0; i < param_count; ++i) {
230 if (i > 0)
231 out << ", ";
232 out << absl::StrFormat("$%02X", cmd.params[i]);
233 }
234
235 out << ")";
236 return out.str();
237}
238
239const char* AsmExporter::GetDurationConstant(uint8_t duration) {
240 for (const auto& dc : kDurationConstants) {
241 if (dc.value == duration) {
242 return dc.name;
243 }
244 }
245 return nullptr;
246}
247
248std::string AsmExporter::GetNoteName(uint8_t pitch) {
249 // Special values
250 if (pitch == kNoteTie)
251 return "Tie";
252 if (pitch == kNoteRest)
253 return "Rest";
254 if (pitch == kTrackEnd)
255 return "End";
256
257 // Regular notes: C1 = 0x80, B6 = 0xC7
258 if (pitch < kNoteMinPitch || pitch > kNoteMaxPitch) {
259 return absl::StrFormat("$%02X", pitch); // Unknown, output raw
260 }
261
262 int offset = pitch - kNoteMinPitch;
263 int octave = (offset / 12) + 1;
264 int semitone = offset % 12;
265
266 static const char* const note_names[] = {"C", "Cs", "D", "Ds", "E", "F",
267 "Fs", "G", "Gs", "A", "As", "B"};
268
269 return absl::StrFormat("%s%d", note_names[semitone], octave);
270}
271
272const char* AsmExporter::GetInstrumentMacro(uint8_t instrument_id) {
273 for (const auto& im : kInstrumentMacros) {
274 if (im.id == instrument_id) {
275 return im.macro;
276 }
277 }
278 return nullptr;
279}
280
281const char* AsmExporter::GetCommandMacro(uint8_t opcode) {
282 switch (opcode) {
283 case 0xE0:
284 return "SetInstrument";
285 case 0xE1:
286 return "SetPan";
287 case 0xE2:
288 return "PanFade";
289 case 0xE3:
290 return "VibratoOn";
291 case 0xE4:
292 return "VibratoOff";
293 case 0xE5:
294 return "SetMasterVolume";
295 case 0xE6:
296 return "MasterVolumeFade";
297 case 0xE7:
298 return "SetTempo";
299 case 0xE8:
300 return "TempoFade";
301 case 0xE9:
302 return "GlobalTranspose";
303 case 0xEA:
304 return "ChannelTranspose";
305 case 0xEB:
306 return "TremoloOn";
307 case 0xEC:
308 return "TremoloOff";
309 case 0xED:
310 return "SetChannelVolume";
311 case 0xEE:
312 return "ChannelVolumeFade";
313 case 0xEF:
314 return "CallSubroutine";
315 case 0xF0:
316 return "VibratoFade";
317 case 0xF1:
318 return "PitchEnvelopeTo";
319 case 0xF2:
320 return "PitchEnvelopeFrom";
321 case 0xF3:
322 return "PitchEnvelopeOff";
323 case 0xF4:
324 return "Tuning";
325 case 0xF5:
326 return "EchoVBits";
327 case 0xF6:
328 return "EchoOff";
329 case 0xF7:
330 return "EchoParams";
331 case 0xF8:
332 return "EchoVolumeFade";
333 case 0xF9:
334 return "PitchSlide";
335 case 0xFA:
336 return "PercussionPatch";
337 default:
338 return nullptr;
339 }
340}
341
343 if (opcode < 0xE0)
344 return 0;
345 return kCommandParamCount[opcode - 0xE0];
346}
347
348} // namespace music
349} // namespace zelda3
350} // namespace yaze
std::string ConvertCommandToAsm(const MusicCommand &cmd, const AsmExportOptions &options)
static int GetCommandParamCount(uint8_t opcode)
std::string GenerateChannelData(const MusicTrack &track, int channel_index, const AsmExportOptions &options)
static const char * GetDurationConstant(uint8_t duration)
static const char * GetInstrumentMacro(uint8_t instrument_id)
static std::string GetNoteName(uint8_t pitch)
absl::Status ExportToFile(const MusicSong &song, const std::string &path, const AsmExportOptions &options)
Export a song to a file.
std::string GenerateChannelPointers(const MusicSong &song, const AsmExportOptions &options)
static const char * GetCommandMacro(uint8_t opcode)
absl::StatusOr< std::string > ExportSong(const MusicSong &song, const AsmExportOptions &options)
Export a song to ASM string.
std::string ConvertNoteToAsm(const Note &note, const AsmExportOptions &options, uint8_t &current_duration)
std::string GenerateHeader(const MusicSong &song, const AsmExportOptions &options)
std::string ConvertEventToAsm(const TrackEvent &event, const AsmExportOptions &options, uint8_t &current_duration)
constexpr uint8_t kNoteRest
Definition song_data.h:58
constexpr DurationConstant kDurationConstants[]
constexpr uint8_t kTrackEnd
Definition song_data.h:59
constexpr InstrumentMacro kInstrumentMacros[]
constexpr uint8_t kNoteMinPitch
Definition song_data.h:55
constexpr int kCommandParamCount[32]
Definition song_data.h:19
constexpr uint8_t kNoteTie
Definition song_data.h:57
constexpr uint8_t kNoteMaxPitch
Definition song_data.h:56
Options for ASM export in music_macros.asm format.
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
A single event in a music track (note, command, or control).
Definition song_data.h:247