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