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();
143 state.
warnings.push_back(absl::StrFormat(
"Line %d: Unrecognized: %s",
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);
247 if (note_result.ok()) {
250 track.is_empty =
false;
257 event.note.pitch = *note_result;
259 event.note.has_duration_prefix =
false;
261 track.events.push_back(event);
269 if (duration_result.ok()) {
277 if (hex_result.ok()) {
278 uint8_t byte_val = *hex_result;
283 track.is_empty =
false;
289 event.note.pitch = byte_val;
291 event.note.has_duration_prefix =
false;
292 track.events.push_back(event);
296 event.note.pitch = byte_val;
298 track.events.push_back(event);
302 track.events.push_back(event);
303 }
else if (byte_val >= 0xE0 && byte_val <= 0xFF) {
308 event.command.opcode = byte_val;
309 track.events.push_back(event);
310 }
else if (byte_val < 0x80) {
321 state.
warnings.push_back(absl::StrFormat(
"Line %d: Unknown value: %s",
326 return absl::OkStatus();
330 const std::string& macro_call,
ParseState& state,
332 std::vector<TrackEvent> events;
334 std::string macro_name;
335 std::vector<std::string> params;
338 return absl::InvalidArgumentError(
339 absl::StrFormat(
"Invalid macro call: %s", macro_call));
344 if (inst_result.ok()) {
347 event.command = *inst_result;
348 events.push_back(event);
354 if (macro_name ==
"SetDuration" && params.size() >= 1) {
364 if (macro_name ==
"SetDurationN" && params.size() >= 2) {
374 std::vector<uint8_t> byte_params;
375 for (
const auto& p : params) {
378 byte_params.push_back(*hex);
383 if (cmd_result.ok()) {
386 event.command = *cmd_result;
387 events.push_back(event);
393 return absl::InvalidArgumentError(
394 absl::StrFormat(
"Unknown macro: %s", macro_name));
397 state.
warnings.push_back(absl::StrFormat(
"Line %d: Unknown macro: %s",
403 const std::string& note_name) {
405 if (note_name == mapping.name) {
406 return mapping.pitch;
409 return absl::NotFoundError(
"Unknown note name");
413 const std::string& duration) {
415 std::string
name = duration;
416 if (!
name.empty() &&
name[0] ==
'!') {
421 std::string full_name =
"!" +
name;
424 if (full_name == dc.name) {
429 return absl::NotFoundError(
"Unknown duration constant");
433 const std::string& macro_name) {
435 if (macro_name == mapping.macro) {
438 cmd.
params[0] = mapping.id;
442 return absl::NotFoundError(
"Unknown instrument macro");
446 const std::string& macro_name,
const std::vector<uint8_t>& params) {
448 if (macro_name == mapping.name) {
450 cmd.
opcode = mapping.opcode;
451 for (
size_t i = 0; i < params.size() && i < 3; ++i) {
452 cmd.
params[i] = params[i];
457 return absl::NotFoundError(
"Unknown command macro");
461 std::string& macro_name,
462 std::vector<std::string>& params) {
464 if (call.empty() || call[0] !=
'%') {
468 size_t paren_start = call.find(
'(');
469 if (paren_start == std::string::npos) {
471 macro_name = call.substr(1);
475 macro_name = call.substr(1, paren_start - 1);
477 size_t paren_end = call.find(
')', paren_start);
478 if (paren_end == std::string::npos) {
482 std::string params_str =
483 call.substr(paren_start + 1, paren_end - paren_start - 1);
484 if (!params_str.empty()) {
485 std::vector<std::string> parts = absl::StrSplit(params_str,
',');
486 for (
const auto& p : parts) {
487 params.push_back(
Trim(p));
496 return absl::InvalidArgumentError(
"Empty value");
500 if (value[0] ==
'$') {
501 return static_cast<uint8_t
>(std::stoul(value.substr(1),
nullptr, 16));
502 }
else if (value.length() >= 2 && value.substr(0, 2) ==
"0x") {
503 return static_cast<uint8_t
>(std::stoul(value.substr(2),
nullptr, 16));
504 }
else if (std::isdigit(value[0])) {
505 return static_cast<uint8_t
>(std::stoul(value,
nullptr, 10));
508 return absl::InvalidArgumentError(
509 absl::StrFormat(
"Invalid hex value: %s", value));
512 return absl::InvalidArgumentError(
513 absl::StrFormat(
"Invalid hex value: %s", value));
517 size_t start = s.find_first_not_of(
" \t\r\n");
518 if (start == std::string::npos)
520 size_t end = s.find_last_not_of(
" \t\r\n");
521 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).