3#include "absl/status/status.h"
4#include "absl/status/statusor.h"
16#include "absl/strings/str_format.h"
18#include "nlohmann/json.hpp"
91 if (metadata.bank == bank) {
108 return static_cast<uint8_t
>((pc_offset >> 15) & 0xFF);
112 uint16_t addr =
static_cast<uint16_t
>(pc_offset & 0x7FFF);
113 return static_cast<uint8_t
>((addr >> 8) & 0x7F);
117 return static_cast<uint8_t
>(pc_offset & 0xFF);
122 uint32_t pc_offset) {
123 uint8_t preserved_mid = 0;
126 preserved_mid = mid_read.value() & 0x80;
130 if (!status.ok())
return status;
133 rom.
WriteByte(regs.
mid,
static_cast<uint8_t
>(preserved_mid |
135 if (!status.ok())
return status;
142 uint32_t pc_offset) {
149 return absl::OkStatus();
157 "Noise",
"Rain",
"Timpani",
"Square wave",
"Saw wave",
158 "Clink",
"Wobbly lead",
"Compound saw",
"Tweet",
"Strings A",
159 "Strings B",
"Trombone",
"Cymbal",
"Ocarina",
"Chimes",
160 "Harp",
"Splash",
"Trumpet",
"Horn",
"Snare A",
161 "Snare B",
"Choir",
"Flute",
"Oof",
"Piano"};
171 return absl::FailedPreconditionError(
"ROM is not loaded");
181 std::vector<MusicSong> custom_songs;
182 custom_songs.reserve(8);
186 if (!status.ok())
return status;
190 if (!status.ok())
return status;
193 if (!status.ok())
return status;
196 if (!status.ok())
return status;
201 if (!status.ok())
return status;
204 for (
auto& song : custom_songs) {
205 songs_.push_back(std::move(song));
210 if (!status.ok())
return status;
214 if (!status.ok())
return status;
217 return absl::OkStatus();
222 return absl::FailedPreconditionError(
"No music data loaded");
226 return absl::FailedPreconditionError(
"ROM is not loaded");
231 return absl::ResourceExhaustedError(
232 "Songs do not fit in ROM banks. Reduce song size or remove songs.");
237 if (!status.ok())
return status;
240 if (!status.ok())
return status;
243 if (!status.ok())
return status;
248 if (!status.ok())
return status;
254 if (!status.ok())
return status;
258 return absl::OkStatus();
262 if (index < 0 || index >=
static_cast<int>(
songs_.size())) {
269 if (index < 0 || index >=
static_cast<int>(
songs_.size())) {
277 if (song_id <= 0 || song_id >
static_cast<int>(
songs_.size())) {
280 return &
songs_[song_id - 1];
286 song.
bank =
static_cast<uint8_t
>(bank);
291 for (
auto& track : segment.
tracks) {
292 track.is_empty =
true;
295 song.
segments.push_back(std::move(segment));
297 songs_.push_back(std::move(song));
298 return static_cast<int>(
songs_.size()) - 1;
303 if (!source)
return -1;
306 new_song.
name +=
" (Copy)";
309 songs_.push_back(std::move(new_song));
310 return static_cast<int>(
songs_.size()) - 1;
314 return index >= 0 && index < kVanillaSongCount;
318 if (index < 0 || index >=
static_cast<int>(
songs_.size())) {
319 return absl::InvalidArgumentError(
"Invalid song index");
323 return absl::InvalidArgumentError(
"Cannot delete vanilla songs");
327 return absl::OkStatus();
331 std::vector<MusicSong*> result;
332 for (
auto& song :
songs_) {
333 if (
static_cast<Bank>(song.bank) == bank) {
334 result.push_back(&song);
341 if (index < 0 || index >=
static_cast<int>(
instruments_.size())) {
348 if (index < 0 || index >=
static_cast<int>(
instruments_.size())) {
370 if (index < 0 || index >=
static_cast<int>(
samples_.size())) {
377 if (index < 0 || index >=
static_cast<int>(
samples_.size())) {
384 const std::string&
name) {
392 for (
int i = 0; i < 1000; ++i) {
393 sample.
pcm_data[i] =
static_cast<int16_t
>(32040.0 * std::sin(i * 0.1));
396 samples_.push_back(std::move(sample));
399 return static_cast<int>(
samples_.size()) - 1;
407 for (
const auto& song :
songs_) {
408 if (
static_cast<Bank>(song.bank) == bank) {
470 for (
const auto& song :
songs_) {
481 for (
auto& song :
songs_) {
482 song.modified =
false;
498 return absl::OkStatus();
502 if (!opcode_result.ok()) {
503 return absl::OkStatus();
507 return absl::OkStatus();
515 if (!addr_low.ok() || !addr_mid.ok() || !addr_bank.ok()) {
516 return absl::OkStatus();
520 uint32_t jsl_target =
static_cast<uint32_t
>(addr_low.value()) |
521 (
static_cast<uint32_t
>(addr_mid.value()) << 8) |
522 (
static_cast<uint32_t
>(addr_bank.value()) << 16);
526 uint8_t target_bank = (jsl_target >> 16) & 0xFF;
527 if (target_bank > 0x3F && target_bank < 0x80) {
528 return absl::OkStatus();
541 return absl::OkStatus();
545 Rom& rom, std::vector<MusicSong>* custom_songs) {
547 return absl::OkStatus();
555 if (expanded_rom_offset + 4 >= rom.
size()) {
556 return absl::OkStatus();
560 if (!header_result.ok()) {
561 return absl::OkStatus();
564 const auto& header = header_result.value();
565 uint16_t block_size =
static_cast<uint16_t
>(header[0]) |
566 (
static_cast<uint16_t
>(header[1]) << 8);
567 uint16_t aram_dest =
static_cast<uint16_t
>(header[2]) |
568 (
static_cast<uint16_t
>(header[3]) << 8);
573 return absl::OkStatus();
577 const uint8_t expanded_spc_bank = 4;
581 const int max_songs = 16;
585 if (!pointer_result.ok()) {
587 return absl::OkStatus();
590 std::vector<uint16_t> song_addresses = std::move(pointer_result.value());
593 int expanded_index = 0;
594 for (
const uint16_t spc_address : song_addresses) {
595 if (spc_address == 0)
continue;
600 if (parsed_song.ok()) {
601 song = std::move(parsed_song.value());
605 for (
auto& track : segment.
tracks) {
606 track.is_empty =
true;
609 song.
segments.push_back(std::move(segment));
612 song.
name = absl::StrFormat(
"Expanded Song %d", ++expanded_index);
617 custom_songs->push_back(std::move(song));
619 songs_.push_back(std::move(song));
628 return absl::OkStatus();
632 if (index < 0 || index >=
static_cast<int>(
songs_.size())) {
635 const auto& song =
songs_[index];
641 std::vector<MusicSong>* custom_songs) {
643 const size_t vanilla_slots =
static_cast<size_t>(range.
Count());
651 if (!pointer_result.ok()) {
653 pointer_result.status().code(),
654 absl::StrFormat(
"Failed to read song table for bank %d: %s",
655 static_cast<int>(bank),
656 pointer_result.status().message()));
659 std::vector<uint16_t> song_addresses = std::move(pointer_result.value());
660 if (song_addresses.empty()) {
661 return absl::InvalidArgumentError(
662 absl::StrFormat(
"Song table for bank %d is empty",
663 static_cast<int>(bank)));
666 auto make_empty_song = []() ->
MusicSong {
669 for (
auto& track : segment.
tracks) {
670 track.is_empty =
true;
671 track.events.clear();
674 song.
segments.push_back(std::move(segment));
678 auto emit_song = [&](
MusicSong&& song,
bool is_custom) {
680 if (is_custom && custom_songs) {
681 custom_songs->push_back(std::move(song));
683 songs_.push_back(std::move(song));
687 auto parse_and_emit = [&](uint16_t spc_address,
688 const std::string& display_name,
689 bool is_custom) -> absl::Status {
691 if (spc_address == 0) {
692 song = make_empty_song();
696 if (!parsed_song.ok()) {
698 parsed_song.status().code(),
700 "Failed to parse song '%s' at $%04X (SPC bank %d): %s",
701 display_name, spc_address, spc_bank,
702 parsed_song.status().message()));
704 song = std::move(parsed_song.value());
707 song.
name = display_name;
708 song.
bank =
static_cast<uint8_t
>(bank);
709 emit_song(std::move(song), is_custom);
710 return absl::OkStatus();
720 for (
size_t i = 0; i < vanilla_slots; ++i) {
721 const uint16_t spc_address =
722 (i < song_addresses.size()) ? song_addresses[i] : 0;
723 const int song_id = range.
start_id +
static_cast<int>(i);
724 const std::string display_name = (song_id > 0 && song_id <= kVanillaSongCount)
726 : absl::StrFormat(
"Vanilla Song %d",
729 parse_and_emit(spc_address, display_name,
false);
730 if (!status.ok())
return status;
735 int custom_counter = 1;
736 for (
size_t table_index = vanilla_slots;
737 table_index < song_addresses.size(); ++table_index) {
738 const uint16_t spc_address = song_addresses[table_index];
740 if (spc_address == 0)
continue;
742 const std::string display_name =
743 absl::StrFormat(
"Custom Song %d", custom_counter++);
746 parse_and_emit(spc_address, display_name,
true);
747 if (!status.ok())
return status;
750 const int total_songs =
static_cast<int>(song_addresses.size());
763 return absl::OkStatus();
768 if (songs_in_bank.empty()) {
769 return absl::OkStatus();
772 const uint16_t pointer_entry_count =
773 static_cast<uint16_t
>(songs_in_bank.size());
774 const uint16_t pointer_table_size =
775 static_cast<uint16_t
>((pointer_entry_count + 1) * 2);
778 uint32_t current_spc_address =
779 static_cast<uint32_t
>(bank_base) + pointer_table_size;
780 const uint32_t bank_limit =
783 std::vector<uint8_t> payload;
784 payload.resize(pointer_table_size, 0);
786 auto write_pointer_entry = [&](
size_t index, uint16_t address) {
787 payload[index * 2] = address & 0xFF;
788 payload[index * 2 + 1] =
static_cast<uint8_t
>((address >> 8) & 0xFF);
791 size_t pointer_index = 0;
793 for (
auto* song : songs_in_bank) {
795 if (!serialized_or.ok()) {
796 return serialized_or.status();
798 auto serialized = std::move(serialized_or.value());
800 const uint32_t song_size = serialized.data.size();
801 if (current_spc_address + song_size > bank_limit) {
802 return absl::ResourceExhaustedError(
803 absl::StrFormat(
"Bank %d overflow (%u bytes needed, limit %u)",
804 static_cast<int>(bank),
805 current_spc_address + song_size - bank_base,
809 const uint16_t song_base =
static_cast<uint16_t
>(current_spc_address);
812 write_pointer_entry(pointer_index++, song_base);
813 payload.insert(payload.end(), serialized.data.begin(),
814 serialized.data.end());
816 song->rom_address = song_base;
817 song->modified =
false;
818 current_spc_address += song_size;
821 write_pointer_entry(pointer_index, 0);
824 return absl::ResourceExhaustedError(
825 absl::StrFormat(
"Bank %d payload size %zu exceeds limit %d",
826 static_cast<int>(bank), payload.size(),
830 const uint16_t block_size =
static_cast<uint16_t
>(payload.size());
831 std::vector<uint8_t> block_data;
832 block_data.reserve(
static_cast<size_t>(block_size) + 8);
833 block_data.push_back(block_size & 0xFF);
834 block_data.push_back(
static_cast<uint8_t
>((block_size >> 8) & 0xFF));
835 block_data.push_back(bank_base & 0xFF);
836 block_data.push_back(
static_cast<uint8_t
>((bank_base >> 8) & 0xFF));
837 block_data.insert(block_data.end(), payload.begin(), payload.end());
838 block_data.push_back(0x00);
839 block_data.push_back(0x00);
840 block_data.push_back(0x00);
841 block_data.push_back(0x00);
844 if (rom_offset + block_data.size() > rom.
size()) {
845 return absl::OutOfRangeError(
846 absl::StrFormat(
"Bank %d ROM write exceeds image size (offset=%u, "
847 "size=%zu, rom_size=%zu)",
848 static_cast<int>(bank), rom_offset,
849 block_data.size(), rom.
size()));
853 rom.
WriteVector(
static_cast<int>(rom_offset), std::move(block_data));
854 if (!status.ok())
return status;
856 status = UpdateDynamicBankPointer(rom, bank, rom_offset);
857 if (!status.ok())
return status;
859 return absl::OkStatus();
865 const uint32_t rom_offset =
867 if (rom_offset == 0) {
868 return absl::InvalidArgumentError(
869 "Unable to resolve instrument table address in ROM");
872 const size_t table_size = kVanillaInstrumentCount * kInstrumentEntrySize;
873 if (rom_offset + table_size > rom.
size()) {
874 return absl::OutOfRangeError(
"Instrument table exceeds ROM bounds");
878 rom.
ReadByteVector(rom_offset,
static_cast<uint32_t
>(table_size)));
881 for (
int i = 0; i < kVanillaInstrumentCount; ++i) {
882 const size_t base =
static_cast<size_t>(i) * kInstrumentEntrySize;
886 inst.
gain = bytes[base + 3];
888 (
static_cast<uint16_t
>(bytes[base + 4]) << 8) | bytes[base + 5]);
889 inst.
name = kAltTpInstrumentNames[i];
893 return absl::OkStatus();
898 return absl::UnimplementedError(
"SaveInstruments not yet implemented");
908 const uint8_t* dir_data =
912 return absl::InternalError(
"Failed to locate sample directory in ROM");
917 const int max_samples = std::min(64, dir_length / 4);
919 for (
int i = 0; i < max_samples; ++i) {
920 uint16_t start_addr = dir_data[i * 4] | (dir_data[i * 4 + 1] << 8);
921 uint16_t loop_addr = dir_data[i * 4 + 2] | (dir_data[i * 4 + 3] << 8);
924 sample.
name = absl::StrFormat(
"Sample %02X", i);
926 sample.
loop_point = (loop_addr >= start_addr) ? (loop_addr - start_addr) : 0;
931 if (rom_offset == 0 || rom_offset >= rom.
size()) {
933 samples_.push_back(std::move(sample));
938 const uint8_t* rom_ptr = rom.
data() + rom_offset;
939 size_t remaining = rom.
size() - rom_offset;
941 while (remaining >= 9) {
946 if (rom_ptr[0] & 0x01) {
947 sample.
loops = (rom_ptr[0] & 0x02) != 0;
960 samples_.push_back(std::move(sample));
963 return absl::OkStatus();
968 return absl::UnimplementedError(
"SaveSamples not yet implemented");
975 for (
const auto& segment : song.
segments) {
978 for (
const auto& track : segment.tracks) {
979 if (track.is_empty) {
983 size +=
static_cast<int>(track.events.size()) * 3;
998 nlohmann::json songs = nlohmann::json::array();
999 for (
const auto& song :
songs_) {
1001 js[
"name"] = song.name;
1002 js[
"bank"] = song.bank;
1003 js[
"loop_point"] = song.loop_point;
1004 js[
"rom_address"] = song.rom_address;
1005 js[
"modified"] = song.modified;
1007 nlohmann::json segments = nlohmann::json::array();
1008 for (
const auto& segment : song.segments) {
1009 nlohmann::json jseg;
1010 jseg[
"rom_address"] = segment.rom_address;
1011 nlohmann::json tracks = nlohmann::json::array();
1012 for (
const auto& track : segment.tracks) {
1014 jt[
"rom_address"] = track.rom_address;
1015 jt[
"duration_ticks"] = track.duration_ticks;
1016 jt[
"is_empty"] = track.is_empty;
1017 nlohmann::json events = nlohmann::json::array();
1018 for (
const auto& evt : track.events) {
1019 nlohmann::json jevt;
1020 jevt[
"tick"] = evt.tick;
1021 jevt[
"rom_offset"] = evt.rom_offset;
1024 jevt[
"type"] =
"note";
1025 jevt[
"note"][
"pitch"] = evt.note.pitch;
1026 jevt[
"note"][
"duration"] = evt.note.duration;
1027 jevt[
"note"][
"velocity"] = evt.note.velocity;
1028 jevt[
"note"][
"has_duration_prefix"] =
1029 evt.note.has_duration_prefix;
1032 jevt[
"type"] =
"command";
1033 jevt[
"command"][
"opcode"] = evt.command.opcode;
1034 jevt[
"command"][
"params"] = evt.command.params;
1037 jevt[
"type"] =
"subroutine";
1038 jevt[
"command"][
"opcode"] = evt.command.opcode;
1039 jevt[
"command"][
"params"] = evt.command.params;
1043 jevt[
"type"] =
"end";
1046 events.push_back(std::move(jevt));
1048 jt[
"events"] = std::move(events);
1049 tracks.push_back(std::move(jt));
1051 jseg[
"tracks"] = std::move(tracks);
1052 segments.push_back(std::move(jseg));
1054 js[
"segments"] = std::move(segments);
1055 songs.push_back(std::move(js));
1058 nlohmann::json instruments = nlohmann::json::array();
1061 ji[
"name"] = inst.name;
1062 ji[
"sample_index"] = inst.sample_index;
1063 ji[
"attack"] = inst.attack;
1064 ji[
"decay"] = inst.decay;
1065 ji[
"sustain_level"] = inst.sustain_level;
1066 ji[
"sustain_rate"] = inst.sustain_rate;
1067 ji[
"gain"] = inst.gain;
1068 ji[
"pitch_mult"] = inst.pitch_mult;
1069 instruments.push_back(std::move(ji));
1072 nlohmann::json samples = nlohmann::json::array();
1073 for (
const auto& sample :
samples_) {
1074 nlohmann::json jsample;
1075 jsample[
"name"] = sample.name;
1076 jsample[
"loop_point"] = sample.loop_point;
1077 jsample[
"loops"] = sample.loops;
1078 jsample[
"pcm_data"] = sample.pcm_data;
1079 jsample[
"brr_data"] = sample.brr_data;
1080 samples.push_back(std::move(jsample));
1083 root[
"songs"] = std::move(songs);
1084 root[
"instruments"] = std::move(instruments);
1085 root[
"samples"] = std::move(samples);
1098 if (j.contains(
"songs") && j[
"songs"].is_array()) {
1099 for (
const auto& js : j[
"songs"]) {
1101 song.
name = js.value(
"name",
"");
1102 song.
bank = js.value(
"bank", 0);
1103 song.
loop_point = js.value(
"loop_point", -1);
1105 song.
modified = js.value(
"modified",
false);
1107 if (js.contains(
"segments") && js[
"segments"].is_array()) {
1108 for (
const auto& jseg : js[
"segments"]) {
1111 if (jseg.contains(
"tracks") && jseg[
"tracks"].is_array()) {
1113 for (
const auto& jt : jseg[
"tracks"]) {
1114 if (track_idx >= 8)
break;
1115 auto& track = seg.
tracks[track_idx++];
1116 track.rom_address = jt.value(
"rom_address", 0);
1117 track.duration_ticks = jt.value(
"duration_ticks", 0);
1118 track.is_empty = jt.value(
"is_empty",
false);
1119 if (jt.contains(
"events") && jt[
"events"].is_array()) {
1120 track.events.clear();
1121 for (
const auto& jevt : jt[
"events"]) {
1123 evt.
tick = jevt.value(
"tick", 0);
1124 evt.
rom_offset = jevt.value(
"rom_offset", 0);
1125 std::string type = jevt.value(
"type",
"end");
1126 if (type ==
"note" && jevt.contains(
"note")) {
1130 jevt[
"note"].value(
"duration", uint8_t{0});
1132 jevt[
"note"].value(
"velocity", uint8_t{0});
1134 jevt[
"note"].value(
"has_duration_prefix",
false);
1135 }
else if (type ==
"command" || type ==
"subroutine") {
1136 evt.
type = (type ==
"subroutine")
1140 jevt[
"command"].value(
"opcode", uint8_t{0});
1141 auto params = jevt[
"command"].value(
1142 "params", std::array<uint8_t, 3>{0, 0, 0});
1147 track.events.push_back(std::move(evt));
1152 song.
segments.push_back(std::move(seg));
1155 songs_.push_back(std::move(song));
1159 if (j.contains(
"instruments") && j[
"instruments"].is_array()) {
1160 for (
const auto& ji : j[
"instruments"]) {
1162 inst.
name = ji.value(
"name",
"");
1164 inst.
attack = ji.value(
"attack", 0);
1165 inst.
decay = ji.value(
"decay", 0);
1168 inst.
gain = ji.value(
"gain", 0);
1174 if (j.contains(
"samples") && j[
"samples"].is_array()) {
1175 for (
const auto& jsample : j[
"samples"]) {
1177 sample.
name = jsample.value(
"name",
"");
1178 sample.
loop_point = jsample.value(
"loop_point", 0);
1179 sample.
loops = jsample.value(
"loops",
false);
1180 sample.
pcm_data = jsample.value(
"pcm_data", std::vector<int16_t>{});
1181 sample.
brr_data = jsample.value(
"brr_data", std::vector<uint8_t>{});
1182 samples_.push_back(std::move(sample));
1191 return absl::OkStatus();
1192 }
catch (
const std::exception& e) {
1193 return absl::InvalidArgumentError(
1194 absl::StrFormat(
"Failed to parse music state: %s", e.what()));
1203 if (song_id <= 0 || song_id > kVanillaSongCount) {
1206 return kVanillaSongs[song_id].
name;
1210 if (song_id <= 0 || song_id > kVanillaSongCount) {
1213 return kVanillaSongs[song_id].
bank;
1217 if (
const auto* metadata = GetMetadataForBank(bank)) {
1219 metadata->vanilla_end_id};
1225 if (
const auto* metadata = GetMetadataForBank(bank)) {
1226 return metadata->spc_bank;
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
absl::Status WriteByte(int addr, uint8_t value)
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
absl::StatusOr< uint8_t > ReadByte(int offset)
static std::vector< int16_t > Decode(const std::vector< uint8_t > &brr_data, int *loop_start=nullptr)
Decode BRR data to PCM samples.
static uint8_t GetSpcBankId(Bank bank)
static uint32_t GetBankRomAddress(Bank bank)
Get the ROM address for a bank.
bool HasModifications() const
Check if any music data has been modified.
MusicInstrument * GetInstrument(int index)
Get an instrument by index.
int CreateNewInstrument(const std::string &name)
Create a new instrument.
static constexpr uint16_t GetSongTableAddress()
nlohmann::json ToJson() const
bool IsVanilla(int index) const
Check if a song is a vanilla (original) song.
absl::Status LoadFromJson(const nlohmann::json &j)
absl::Status LoadSamples(Rom &rom)
MusicSong * GetSongById(int song_id)
Get a song by vanilla ID (1-based).
MusicSong * GetSong(int index)
Get a song by index.
absl::Status DetectExpandedMusicPatch(Rom &rom)
absl::Status LoadInstruments(Rom &rom)
static int GetBankMaxSize(Bank bank)
Get the maximum size for a bank.
ExpandedBankInfo expanded_bank_info_
std::vector< MusicSample > samples_
absl::Status LoadSongTable(Rom &rom, Bank bank, std::vector< MusicSong > *custom_songs)
bool AllSongsFit() const
Check if all songs fit in their banks.
static BankSongRange GetBankSongRange(Bank bank)
absl::Status SaveInstruments(Rom &rom)
MusicSample * GetSample(int index)
Get a sample by index.
absl::Status DeleteSong(int index)
Delete a song by index.
std::vector< MusicSong > songs_
SpaceInfo CalculateSpaceUsage(Bank bank) const
Calculate space usage for a bank.
absl::StatusOr< int > ImportSampleFromWav(const std::string &filepath, const std::string &name)
Import a WAV file as a new sample.
absl::Status SaveSamples(Rom &rom)
absl::Status SaveToRom(Rom &rom)
Save all modified music data back to ROM.
absl::Status SaveSongTable(Rom &rom, Bank bank)
int auxiliary_song_count_
int CalculateSongSize(const MusicSong &song) const
bool IsExpandedSong(int index) const
Check if a song is from an expanded bank.
int overworld_song_count_
void ClearModifications()
Mark all data as unmodified (after save).
absl::Status LoadExpandedSongTable(Rom &rom, std::vector< MusicSong > *custom_songs)
bool instruments_modified_
std::vector< MusicInstrument > instruments_
int CreateNewSong(const std::string &name, Bank bank)
Create a new empty song.
absl::Status LoadFromRom(Rom &rom)
Load all music data from a ROM.
std::vector< MusicSong * > GetSongsInBank(Bank bank)
Get all songs in a specific bank.
int DuplicateSong(int index)
Duplicate a song.
static absl::StatusOr< MusicSong > ParseSong(Rom &rom, uint16_t address, uint8_t bank)
Parse a complete song from ROM.
static absl::StatusOr< std::vector< uint16_t > > ReadSongPointerTable(Rom &rom, uint16_t table_address, uint8_t bank, int max_entries=40)
Read the song pointer table for a given SPC bank.
static const uint8_t * GetSpcData(Rom &rom, uint16_t spc_address, uint8_t bank, int *out_length=nullptr)
Get a pointer to ROM data at an SPC address.
static uint32_t SpcAddressToRomOffset(Rom &rom, uint16_t spc_address, uint8_t bank)
Convert an SPC address to a ROM offset.
static absl::StatusOr< SerializeResult > SerializeSong(const MusicSong &song, uint16_t base_address)
Serialize a complete song to binary format.
static void ApplyBaseAddress(SerializeResult *result, uint16_t new_base_address)
Adjust all serialized pointers to a new base address.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
constexpr BankPointerRegisters kCreditsPointerRegs
constexpr int kVanillaSongCount
uint8_t EncodeLoRomMid(uint32_t pc_offset)
constexpr VanillaSongInfo kVanillaSongs[]
constexpr const char * kAltTpInstrumentNames[kVanillaInstrumentCount]
absl::Status UpdateBankPointerRegisters(Rom &rom, const BankPointerRegisters ®s, uint32_t pc_offset)
constexpr BankMetadata kBankMetadata[]
uint8_t EncodeLoRomBank(uint32_t pc_offset)
absl::Status UpdateDynamicBankPointer(Rom &rom, MusicBank::Bank bank, uint32_t pc_offset)
constexpr int kVanillaInstrumentCount
constexpr int kInstrumentEntrySize
const BankMetadata * GetMetadataForBank(MusicBank::Bank bank)
uint8_t EncodeLoRomLow(uint32_t pc_offset)
constexpr BankPointerRegisters kOverworldPointerRegs
constexpr uint8_t kNoteRest
constexpr uint32_t kExpandedAuxBankRom
MusicBank::Bank GetVanillaSongBank(int song_id)
Get the bank for a vanilla song ID.
constexpr uint16_t kAuxSongTableAram
constexpr uint16_t kSampleTableAram
constexpr uint32_t kExpandedOverworldBankRom
constexpr uint8_t kJslOpcode
constexpr uint16_t kInstrumentTableAram
constexpr uint32_t kOverworldBankRom
constexpr uint16_t kSongTableAram
const char * GetVanillaSongName(int song_id)
Get the vanilla name for a song ID.
constexpr int kCreditsBankMaxSize
constexpr int kExpandedOverworldBankMaxSize
constexpr uint32_t kCreditsBankRom
constexpr int kDungeonBankMaxSize
constexpr uint32_t kDungeonBankRom
constexpr int kOverworldBankMaxSize
constexpr int kAuxBankMaxSize
constexpr uint32_t kExpandedMusicHookAddress
uint16_t aux_aram_address
std::string recommendation
std::array< uint8_t, 3 > params
An instrument definition with ADSR envelope.
void SetFromBytes(uint8_t ad, uint8_t sr)
A BRR-encoded audio sample.
std::vector< uint8_t > brr_data
std::vector< int16_t > pcm_data
A segment containing 8 parallel tracks.
std::array< MusicTrack, 8 > tracks
A complete song composed of segments.
std::vector< MusicSegment > segments
A single event in a music track (note, command, or control).
static TrackEvent MakeEnd(uint16_t tick)