6#include "absl/strings/str_format.h"
14 std::ostringstream out;
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";
34 for (
size_t seg_idx = 0; seg_idx < song.
segments.size(); ++seg_idx) {
35 const auto& segment = song.
segments[seg_idx];
38 out <<
"; --- Segment " << seg_idx <<
" ---\n";
41 for (
int ch = 0; ch < 8; ++ch) {
42 const auto& track = segment.tracks[ch];
43 if (track.is_empty)
continue;
54 const std::string& path,
58 return result.status();
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));
67 file << result.value();
70 return absl::OkStatus();
75 std::ostringstream out;
85 uint16_t intro_offset = 0x0A;
86 uint16_t loop_offset = 0x1A;
88 out << absl::StrFormat(
"dw !ARAMAddr+$%02X ; Intro section\n",
91 out << absl::StrFormat(
"dw !ARAMAddr+$%02X ; Loop section\n",
94 out <<
"dw $00FF ; Default fade\n";
96 out <<
"dw !ARAMAddr+$02 ; Loop start\n";
97 out <<
"dw $0000 ; Reserved\n";
104 std::ostringstream out;
106 out <<
".Channels\n";
107 out << absl::StrFormat(
"!ARAMC = !ARAMAddr-%s\n", options.
label_prefix);
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) {
120 out << absl::StrFormat(
"dw .Channel%d+!ARAMC\n", ch);
122 out <<
"dw $0000 ; Unused\n";
132 std::ostringstream out;
134 out << absl::StrFormat(
".Channel%d\n", channel_index);
136 uint8_t current_duration = 0;
138 for (
const auto& event : track.
events) {
140 if (!event_asm.empty()) {
141 out <<
" " << event_asm <<
"\n";
153 uint8_t& current_duration) {
154 switch (event.
type) {
169 uint8_t& current_duration) {
170 std::ostringstream out;
175 if (duration_const) {
177 out << absl::StrFormat(
"%%SetDurationN(%s, $%02X)\n ", duration_const,
180 out << absl::StrFormat(
"%%SetDuration(%s)\n ", duration_const);
184 out << absl::StrFormat(
"db $%02X\n ", note.
duration);
191 out <<
"db " << note_name;
198 std::ostringstream out;
212 out << absl::StrFormat(
"db $%02X", cmd.
opcode);
214 for (
int i = 0; i < param_count; ++i) {
215 out << absl::StrFormat(
", $%02X", cmd.
params[i]);
222 out <<
"%" << macro_name <<
"(";
224 for (
int i = 0; i < param_count; ++i) {
225 if (i > 0) out <<
", ";
226 out << absl::StrFormat(
"$%02X", cmd.
params[i]);
235 if (dc.value == duration) {
244 if (pitch ==
kNoteTie)
return "Tie";
250 return absl::StrFormat(
"$%02X", pitch);
254 int octave = (offset / 12) + 1;
255 int semitone = offset % 12;
257 static const char*
const note_names[] = {
"C",
"Cs",
"D",
"Ds",
"E",
"F",
258 "Fs",
"G",
"Gs",
"A",
"As",
"B"};
260 return absl::StrFormat(
"%s%d", note_names[semitone], octave);
265 if (im.id == instrument_id) {
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;
306 if (opcode < 0xE0)
return 0;
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 ¬e, const AsmExportOptions &options, uint8_t ¤t_duration)
std::string GenerateHeader(const MusicSong &song, const AsmExportOptions &options)
std::string ConvertEventToAsm(const TrackEvent &event, const AsmExportOptions &options, uint8_t ¤t_duration)
constexpr uint8_t kNoteRest
constexpr DurationConstant kDurationConstants[]
constexpr uint8_t kTrackEnd
constexpr InstrumentMacro kInstrumentMacros[]
constexpr uint8_t kNoteMinPitch
constexpr int kCommandParamCount[32]
constexpr uint8_t kNoteTie
constexpr uint8_t kNoteMaxPitch
Options for ASM export in music_macros.asm format.
bool use_instrument_macros
uint16_t base_aram_address
Represents an N-SPC command (opcodes 0xE0-0xFF).
int GetParamCount() const
std::array< uint8_t, 3 > params
A complete song composed of segments.
std::vector< MusicSegment > segments
One of 8 channels in a music segment.
std::vector< TrackEvent > events
Represents a single musical note.
A single event in a music track (note, command, or control).