9#include "absl/strings/str_format.h"
10#include "absl/strings/str_split.h"
23 for (
int i = 0; i < 8; ++i) {
28 std::istringstream stream(asm_source);
31 while (std::getline(stream, line)) {
41 absl::StrFormat(
"Line %d: %s", state.
line_number, status.message()));
59 std::ifstream file(path);
60 if (!file.is_open()) {
61 return absl::FailedPreconditionError(
62 absl::StrFormat(
"Failed to open file: %s", path));
65 std::ostringstream buffer;
66 buffer << file.rdbuf();
69 auto result =
ImportSong(buffer.str(), options);
72 if (result->song.name.empty()) {
73 size_t pos = path.find_last_of(
"/\\");
74 std::string filename =
75 (pos == std::string::npos) ? path : path.substr(pos + 1);
77 pos = filename.find_last_of(
'.');
78 if (pos != std::string::npos) {
79 filename = filename.substr(0, pos);
81 result->song.name = filename;
91 std::string trimmed =
Trim(line);
94 if (trimmed.empty() || trimmed[0] ==
';') {
95 return absl::OkStatus();
99 size_t comment_pos = trimmed.find(
';');
100 if (comment_pos != std::string::npos) {
101 trimmed =
Trim(trimmed.substr(0, comment_pos));
104 if (trimmed.empty()) {
105 return absl::OkStatus();
110 return absl::OkStatus();
115 return absl::OkStatus();
119 if (trimmed.substr(0, 2) ==
"db" || trimmed.substr(0, 2) ==
"dw") {
124 if (trimmed[0] ==
'%') {
125 auto events_result =
ParseMacro(trimmed, state, options);
126 if (!events_result.ok()) {
127 return events_result.status();
133 track.is_empty =
false;
134 for (
const auto& event : *events_result) {
135 track.events.push_back(event);
138 return absl::OkStatus();
144 absl::StrFormat(
"Line %d: Unrecognized: %s", state.
line_number, trimmed));
147 return absl::OkStatus();
152 if (line.back() ==
':') {
153 std::string label = line.substr(0, line.length() - 1);
156 if (label.substr(0, 8) ==
".Channel" && label.length() == 9) {
157 int ch = label[8] -
'0';
158 if (ch >= 0 && ch < 8) {
166 if (label[0] ==
'.') {
180 if (line[0] ==
'.') {
181 std::string label = line;
182 if (label.substr(0, 8) ==
".Channel" && label.length() >= 9) {
183 int ch = label[8] -
'0';
184 if (ch >= 0 && ch < 8) {
198 if (line[0] ==
'!') {
199 size_t eq_pos = line.find(
'=');
200 if (eq_pos != std::string::npos) {
201 std::string
name =
Trim(line.substr(1, eq_pos - 1));
202 std::string value =
Trim(line.substr(eq_pos + 1));
204 if (
name ==
"ARAMAddr") {
208 if (value.length() > 3) {
210 if (value[0] ==
'$') {
211 addr = std::stoul(value.substr(1),
nullptr, 16);
212 }
else if (value.substr(0, 2) ==
"0x") {
213 addr = std::stoul(value.substr(2),
nullptr, 16);
232 std::string data =
Trim(line.substr(2));
234 return absl::OkStatus();
238 std::vector<std::string> parts = absl::StrSplit(data,
',');
240 for (
const auto& part : parts) {
241 std::string value =
Trim(part);
242 if (value.empty())
continue;
246 if (note_result.ok()) {
249 track.is_empty =
false;
256 event.note.pitch = *note_result;
258 event.note.has_duration_prefix =
false;
260 track.events.push_back(event);
268 if (duration_result.ok()) {
276 if (hex_result.ok()) {
277 uint8_t byte_val = *hex_result;
282 track.is_empty =
false;
288 event.note.pitch = byte_val;
290 event.note.has_duration_prefix =
false;
291 track.events.push_back(event);
295 event.note.pitch = byte_val;
297 track.events.push_back(event);
301 track.events.push_back(event);
302 }
else if (byte_val >= 0xE0 && byte_val <= 0xFF) {
307 event.command.opcode = byte_val;
308 track.events.push_back(event);
309 }
else if (byte_val < 0x80) {
321 absl::StrFormat(
"Line %d: Unknown value: %s", state.
line_number, value));
325 return absl::OkStatus();
329 const std::string& macro_call,
ParseState& state,
331 std::vector<TrackEvent> events;
333 std::string macro_name;
334 std::vector<std::string> params;
337 return absl::InvalidArgumentError(
338 absl::StrFormat(
"Invalid macro call: %s", macro_call));
343 if (inst_result.ok()) {
346 event.command = *inst_result;
347 events.push_back(event);
353 if (macro_name ==
"SetDuration" && params.size() >= 1) {
363 if (macro_name ==
"SetDurationN" && params.size() >= 2) {
373 std::vector<uint8_t> byte_params;
374 for (
const auto& p : params) {
377 byte_params.push_back(*hex);
382 if (cmd_result.ok()) {
385 event.command = *cmd_result;
386 events.push_back(event);
392 return absl::InvalidArgumentError(
393 absl::StrFormat(
"Unknown macro: %s", macro_name));
397 absl::StrFormat(
"Line %d: Unknown macro: %s", state.
line_number, macro_name));
403 if (note_name == mapping.name) {
404 return mapping.pitch;
407 return absl::NotFoundError(
"Unknown note name");
411 const std::string& duration) {
413 std::string
name = duration;
414 if (!
name.empty() &&
name[0] ==
'!') {
419 std::string full_name =
"!" +
name;
422 if (full_name == dc.name) {
427 return absl::NotFoundError(
"Unknown duration constant");
431 const std::string& macro_name) {
433 if (macro_name == mapping.macro) {
436 cmd.
params[0] = mapping.id;
440 return absl::NotFoundError(
"Unknown instrument macro");
444 const std::string& macro_name,
const std::vector<uint8_t>& params) {
446 if (macro_name == mapping.name) {
448 cmd.
opcode = mapping.opcode;
449 for (
size_t i = 0; i < params.size() && i < 3; ++i) {
450 cmd.
params[i] = params[i];
455 return absl::NotFoundError(
"Unknown command macro");
459 std::string& macro_name,
460 std::vector<std::string>& params) {
462 if (call.empty() || call[0] !=
'%') {
466 size_t paren_start = call.find(
'(');
467 if (paren_start == std::string::npos) {
469 macro_name = call.substr(1);
473 macro_name = call.substr(1, paren_start - 1);
475 size_t paren_end = call.find(
')', paren_start);
476 if (paren_end == std::string::npos) {
480 std::string params_str = call.substr(paren_start + 1, paren_end - paren_start - 1);
481 if (!params_str.empty()) {
482 std::vector<std::string> parts = absl::StrSplit(params_str,
',');
483 for (
const auto& p : parts) {
484 params.push_back(
Trim(p));
493 return absl::InvalidArgumentError(
"Empty value");
497 if (value[0] ==
'$') {
498 return static_cast<uint8_t
>(std::stoul(value.substr(1),
nullptr, 16));
499 }
else if (value.length() >= 2 && value.substr(0, 2) ==
"0x") {
500 return static_cast<uint8_t
>(std::stoul(value.substr(2),
nullptr, 16));
501 }
else if (std::isdigit(value[0])) {
502 return static_cast<uint8_t
>(std::stoul(value,
nullptr, 10));
505 return absl::InvalidArgumentError(
506 absl::StrFormat(
"Invalid hex value: %s", value));
509 return absl::InvalidArgumentError(
510 absl::StrFormat(
"Invalid hex value: %s", value));
514 size_t start = s.find_first_not_of(
" \t\r\n");
515 if (start == std::string::npos)
return "";
516 size_t end = s.find_last_not_of(
" \t\r\n");
517 return s.substr(start, end - start + 1);
absl::StatusOr< AsmParseResult > ImportSong(const std::string &asm_source, const AsmImportOptions &options)
Import a song from ASM string.
bool ParseLabel(const std::string &line, ParseState &state)
absl::StatusOr< uint8_t > ParseDurationConstant(const std::string &duration)
bool ParseDirective(const std::string &line, ParseState &state)
absl::StatusOr< MusicCommand > ResolveCommandMacro(const std::string ¯o_name, const std::vector< uint8_t > ¶ms)
absl::Status ParseLine(const std::string &line, MusicSong &song, ParseState &state, const AsmImportOptions &options)
absl::StatusOr< uint8_t > ParseNoteName(const std::string ¬e_name)
static std::string Trim(const std::string &s)
absl::Status ParseDataBytes(const std::string &line, MusicSong &song, ParseState &state, const AsmImportOptions &options)
bool ParseMacroCall(const std::string &call, std::string ¯o_name, std::vector< std::string > ¶ms)
absl::StatusOr< uint8_t > ParseHexValue(const std::string &value)
absl::StatusOr< AsmParseResult > ImportFromFile(const std::string &path, const AsmImportOptions &options)
Import a song from a file.
absl::StatusOr< MusicCommand > ResolveInstrumentMacro(const std::string ¯o_name)
absl::StatusOr< std::vector< TrackEvent > > ParseMacro(const std::string ¯o_call, ParseState &state, const AsmImportOptions &options)
constexpr uint8_t kNoteRest
constexpr DurationConstant kDurationConstants[]
constexpr uint8_t kTrackEnd
constexpr CommandMacroMapping kCommandMacros[]
constexpr NoteNameMapping kAsmNoteNames[]
constexpr InstrumentMacroMapping kInstrumentMacroImport[]
constexpr uint8_t kNoteMinPitch
constexpr uint8_t kNoteTie
constexpr uint8_t kNoteMaxPitch
Options for ASM import from music_macros.asm format.
std::vector< std::string > errors
std::vector< std::string > warnings
std::unordered_map< std::string, int > label_to_channel
Parse result with diagnostics.
std::vector< std::string > errors
std::vector< std::string > warnings
Represents an N-SPC command (opcodes 0xE0-0xFF).
std::array< uint8_t, 3 > params
A segment containing 8 parallel tracks.
A complete song composed of segments.
std::vector< MusicSegment > segments
A single event in a music track (note, command, or control).