3#include "absl/status/status.h"
4#include "absl/status/statusor.h"
16#include "absl/strings/str_format.h"
18#include "nlohmann/json.hpp"
107 if (metadata.bank == bank) {
124 return static_cast<uint8_t
>((pc_offset >> 15) & 0xFF);
128 uint16_t addr =
static_cast<uint16_t
>(pc_offset & 0x7FFF);
129 return static_cast<uint8_t
>((addr >> 8) & 0x7F);
133 return static_cast<uint8_t
>(pc_offset & 0xFF);
138 uint32_t pc_offset) {
139 uint8_t preserved_mid = 0;
142 preserved_mid = mid_read.value() & 0x80;
160 uint32_t pc_offset) {
167 return absl::OkStatus();
175 "Noise",
"Rain",
"Timpani",
"Square wave",
"Saw wave",
176 "Clink",
"Wobbly lead",
"Compound saw",
"Tweet",
"Strings A",
177 "Strings B",
"Trombone",
"Cymbal",
"Ocarina",
"Chimes",
178 "Harp",
"Splash",
"Trumpet",
"Horn",
"Snare A",
179 "Snare B",
"Choir",
"Flute",
"Oof",
"Piano"};
189 return absl::FailedPreconditionError(
"ROM is not loaded");
199 std::vector<MusicSong> custom_songs;
200 custom_songs.reserve(8);
227 for (
auto& song : custom_songs) {
228 songs_.push_back(std::move(song));
242 return absl::OkStatus();
247 return absl::FailedPreconditionError(
"No music data loaded");
251 return absl::FailedPreconditionError(
"ROM is not loaded");
256 return absl::ResourceExhaustedError(
257 "Songs do not fit in ROM banks. Reduce song size or remove songs.");
288 return absl::OkStatus();
292 if (index < 0 || index >=
static_cast<int>(
songs_.size())) {
299 if (index < 0 || index >=
static_cast<int>(
songs_.size())) {
307 if (song_id <= 0 || song_id >
static_cast<int>(
songs_.size())) {
310 return &
songs_[song_id - 1];
316 song.
bank =
static_cast<uint8_t
>(bank);
321 for (
auto& track : segment.
tracks) {
322 track.is_empty =
true;
325 song.
segments.push_back(std::move(segment));
327 songs_.push_back(std::move(song));
328 return static_cast<int>(
songs_.size()) - 1;
337 new_song.
name +=
" (Copy)";
340 songs_.push_back(std::move(new_song));
341 return static_cast<int>(
songs_.size()) - 1;
345 return index >= 0 && index < kVanillaSongCount;
349 if (index < 0 || index >=
static_cast<int>(
songs_.size())) {
350 return absl::InvalidArgumentError(
"Invalid song index");
354 return absl::InvalidArgumentError(
"Cannot delete vanilla songs");
358 return absl::OkStatus();
362 std::vector<MusicSong*> result;
363 for (
auto& song :
songs_) {
364 if (
static_cast<Bank>(song.bank) == bank) {
365 result.push_back(&song);
372 if (index < 0 || index >=
static_cast<int>(
instruments_.size())) {
379 if (index < 0 || index >=
static_cast<int>(
instruments_.size())) {
401 if (index < 0 || index >=
static_cast<int>(
samples_.size())) {
408 if (index < 0 || index >=
static_cast<int>(
samples_.size())) {
415 const std::string&
name) {
423 for (
int i = 0; i < 1000; ++i) {
424 sample.
pcm_data[i] =
static_cast<int16_t
>(32040.0 * std::sin(i * 0.1));
427 samples_.push_back(std::move(sample));
430 return static_cast<int>(
samples_.size()) - 1;
438 for (
const auto& song :
songs_) {
439 if (
static_cast<Bank>(song.bank) == bank) {
500 return ExpandedOverworldBankRom();
502 return ExpandedAuxBankRom();
511 for (
const auto& song :
songs_) {
522 for (
auto& song :
songs_) {
523 song.modified =
false;
538 const uint32_t hook_address = ExpandedMusicHookAddress();
539 if (hook_address >= rom.
size()) {
540 return absl::OkStatus();
543 auto opcode_result = rom.
ReadByte(hook_address);
544 if (!opcode_result.ok()) {
545 return absl::OkStatus();
549 return absl::OkStatus();
553 auto addr_low = rom.
ReadByte(hook_address + 1);
554 auto addr_mid = rom.
ReadByte(hook_address + 2);
555 auto addr_bank = rom.
ReadByte(hook_address + 3);
557 if (!addr_low.ok() || !addr_mid.ok() || !addr_bank.ok()) {
558 return absl::OkStatus();
562 uint32_t jsl_target =
static_cast<uint32_t
>(addr_low.value()) |
563 (
static_cast<uint32_t
>(addr_mid.value()) << 8) |
564 (
static_cast<uint32_t
>(addr_bank.value()) << 16);
568 uint8_t target_bank = (jsl_target >> 16) & 0xFF;
569 if (target_bank > 0x3F && target_bank < 0x80) {
570 return absl::OkStatus();
583 return absl::OkStatus();
587 Rom& rom, std::vector<MusicSong>* custom_songs) {
589 return absl::OkStatus();
597 if (expanded_rom_offset + 4 >= rom.
size()) {
598 return absl::OkStatus();
602 if (!header_result.ok()) {
603 return absl::OkStatus();
606 const auto& header = header_result.value();
607 uint16_t block_size =
static_cast<uint16_t
>(header[0]) |
608 (
static_cast<uint16_t
>(header[1]) << 8);
609 uint16_t aram_dest =
static_cast<uint16_t
>(header[2]) |
610 (
static_cast<uint16_t
>(header[3]) << 8);
615 return absl::OkStatus();
619 const uint8_t expanded_spc_bank = 4;
623 const int max_songs =
628 if (!pointer_result.ok()) {
630 return absl::OkStatus();
633 std::vector<uint16_t> song_addresses = std::move(pointer_result.value());
636 int expanded_index = 0;
637 for (
const uint16_t spc_address : song_addresses) {
638 if (spc_address == 0)
644 if (parsed_song.ok()) {
645 song = std::move(parsed_song.value());
649 for (
auto& track : segment.
tracks) {
650 track.is_empty =
true;
653 song.
segments.push_back(std::move(segment));
656 song.
name = absl::StrFormat(
"Expanded Song %d", ++expanded_index);
661 custom_songs->push_back(std::move(song));
663 songs_.push_back(std::move(song));
672 return absl::OkStatus();
676 if (index < 0 || index >=
static_cast<int>(
songs_.size())) {
679 const auto& song =
songs_[index];
685 std::vector<MusicSong>* custom_songs) {
687 const size_t vanilla_slots =
static_cast<size_t>(range.
Count());
695 if (!pointer_result.ok()) {
697 pointer_result.status().code(),
698 absl::StrFormat(
"Failed to read song table for bank %d: %s",
699 static_cast<int>(bank),
700 pointer_result.status().message()));
703 std::vector<uint16_t> song_addresses = std::move(pointer_result.value());
704 if (song_addresses.empty()) {
705 return absl::InvalidArgumentError(absl::StrFormat(
706 "Song table for bank %d is empty",
static_cast<int>(bank)));
709 auto make_empty_song = []() ->
MusicSong {
712 for (
auto& track : segment.
tracks) {
713 track.is_empty =
true;
714 track.events.clear();
717 song.
segments.push_back(std::move(segment));
721 auto emit_song = [&](
MusicSong&& song,
bool is_custom) {
723 if (is_custom && custom_songs) {
724 custom_songs->push_back(std::move(song));
726 songs_.push_back(std::move(song));
730 auto parse_and_emit = [&](uint16_t spc_address,
731 const std::string& display_name,
732 bool is_custom) -> absl::Status {
734 if (spc_address == 0) {
735 song = make_empty_song();
739 if (!parsed_song.ok()) {
741 parsed_song.status().code(),
743 "Failed to parse song '%s' at $%04X (SPC bank %d): %s",
744 display_name, spc_address, spc_bank,
745 parsed_song.status().message()));
747 song = std::move(parsed_song.value());
750 song.
name = display_name;
751 song.
bank =
static_cast<uint8_t
>(bank);
752 emit_song(std::move(song), is_custom);
753 return absl::OkStatus();
763 for (
size_t i = 0; i < vanilla_slots; ++i) {
764 const uint16_t spc_address =
765 (i < song_addresses.size()) ? song_addresses[i] : 0;
766 const int song_id = range.
start_id +
static_cast<int>(i);
767 const std::string display_name =
768 (song_id > 0 && song_id <= kVanillaSongCount)
770 : absl::StrFormat(
"Vanilla Song %d", song_id);
772 parse_and_emit(spc_address, display_name,
false);
779 int custom_counter = 1;
780 for (
size_t table_index = vanilla_slots; table_index < song_addresses.size();
782 const uint16_t spc_address = song_addresses[table_index];
784 if (spc_address == 0)
787 const std::string display_name =
788 absl::StrFormat(
"Custom Song %d", custom_counter++);
790 auto status = parse_and_emit(spc_address, display_name,
true);
795 const int total_songs =
static_cast<int>(song_addresses.size());
808 return absl::OkStatus();
813 if (songs_in_bank.empty()) {
814 return absl::OkStatus();
817 const uint16_t pointer_entry_count =
818 static_cast<uint16_t
>(songs_in_bank.size());
819 const uint16_t pointer_table_size =
820 static_cast<uint16_t
>((pointer_entry_count + 1) * 2);
823 uint32_t current_spc_address =
824 static_cast<uint32_t
>(bank_base) + pointer_table_size;
825 const uint32_t bank_limit =
828 std::vector<uint8_t> payload;
829 payload.resize(pointer_table_size, 0);
831 auto write_pointer_entry = [&](
size_t index, uint16_t address) {
832 payload[index * 2] = address & 0xFF;
833 payload[index * 2 + 1] =
static_cast<uint8_t
>((address >> 8) & 0xFF);
836 size_t pointer_index = 0;
838 for (
auto* song : songs_in_bank) {
840 if (!serialized_or.ok()) {
841 return serialized_or.status();
843 auto serialized = std::move(serialized_or.value());
845 const uint32_t song_size = serialized.data.size();
846 if (current_spc_address + song_size > bank_limit) {
847 return absl::ResourceExhaustedError(absl::StrFormat(
848 "Bank %d overflow (%u bytes needed, limit %u)",
849 static_cast<int>(bank), current_spc_address + song_size - bank_base,
853 const uint16_t song_base =
static_cast<uint16_t
>(current_spc_address);
856 write_pointer_entry(pointer_index++, song_base);
857 payload.insert(payload.end(), serialized.data.begin(),
858 serialized.data.end());
860 song->rom_address = song_base;
861 song->modified =
false;
862 current_spc_address += song_size;
865 write_pointer_entry(pointer_index, 0);
868 return absl::ResourceExhaustedError(absl::StrFormat(
869 "Bank %d payload size %zu exceeds limit %d",
static_cast<int>(bank),
873 const uint16_t block_size =
static_cast<uint16_t
>(payload.size());
874 std::vector<uint8_t> block_data;
875 block_data.reserve(
static_cast<size_t>(block_size) + 8);
876 block_data.push_back(block_size & 0xFF);
877 block_data.push_back(
static_cast<uint8_t
>((block_size >> 8) & 0xFF));
878 block_data.push_back(bank_base & 0xFF);
879 block_data.push_back(
static_cast<uint8_t
>((bank_base >> 8) & 0xFF));
880 block_data.insert(block_data.end(), payload.begin(), payload.end());
881 block_data.push_back(0x00);
882 block_data.push_back(0x00);
883 block_data.push_back(0x00);
884 block_data.push_back(0x00);
887 if (rom_offset + block_data.size() > rom.
size()) {
888 return absl::OutOfRangeError(absl::StrFormat(
889 "Bank %d ROM write exceeds image size (offset=%u, "
890 "size=%zu, rom_size=%zu)",
891 static_cast<int>(bank), rom_offset, block_data.size(), rom.
size()));
895 rom.
WriteVector(
static_cast<int>(rom_offset), std::move(block_data));
899 status = UpdateDynamicBankPointer(rom, bank, rom_offset);
903 return absl::OkStatus();
909 const uint32_t rom_offset =
911 if (rom_offset == 0) {
912 return absl::InvalidArgumentError(
913 "Unable to resolve instrument table address in ROM");
916 const size_t table_size = kVanillaInstrumentCount * kInstrumentEntrySize;
917 if (rom_offset + table_size > rom.
size()) {
918 return absl::OutOfRangeError(
"Instrument table exceeds ROM bounds");
923 rom.
ReadByteVector(rom_offset,
static_cast<uint32_t
>(table_size)));
926 for (
int i = 0; i < kVanillaInstrumentCount; ++i) {
927 const size_t base =
static_cast<size_t>(i) * kInstrumentEntrySize;
931 inst.
gain = bytes[base + 3];
933 (
static_cast<uint16_t
>(bytes[base + 4]) << 8) | bytes[base + 5]);
934 inst.
name = kAltTpInstrumentNames[i];
938 return absl::OkStatus();
943 return absl::UnimplementedError(
"SaveInstruments not yet implemented");
953 const uint8_t* dir_data =
957 return absl::InternalError(
"Failed to locate sample directory in ROM");
962 const int max_samples = std::min(64, dir_length / 4);
964 for (
int i = 0; i < max_samples; ++i) {
965 uint16_t start_addr = dir_data[i * 4] | (dir_data[i * 4 + 1] << 8);
966 uint16_t loop_addr = dir_data[i * 4 + 2] | (dir_data[i * 4 + 3] << 8);
969 sample.
name = absl::StrFormat(
"Sample %02X", i);
972 (loop_addr >= start_addr) ? (loop_addr - start_addr) : 0;
977 if (rom_offset == 0 || rom_offset >= rom.
size()) {
979 samples_.push_back(std::move(sample));
984 const uint8_t* rom_ptr = rom.
data() + rom_offset;
985 size_t remaining = rom.
size() - rom_offset;
987 while (remaining >= 9) {
992 if (rom_ptr[0] & 0x01) {
993 sample.
loops = (rom_ptr[0] & 0x02) != 0;
1006 samples_.push_back(std::move(sample));
1009 return absl::OkStatus();
1014 return absl::UnimplementedError(
"SaveSamples not yet implemented");
1021 for (
const auto& segment : song.
segments) {
1024 for (
const auto& track : segment.tracks) {
1025 if (track.is_empty) {
1029 size +=
static_cast<int>(track.events.size()) * 3;
1043 nlohmann::json root;
1044 nlohmann::json songs = nlohmann::json::array();
1045 for (
const auto& song :
songs_) {
1047 js[
"name"] = song.name;
1048 js[
"bank"] = song.bank;
1049 js[
"loop_point"] = song.loop_point;
1050 js[
"rom_address"] = song.rom_address;
1051 js[
"modified"] = song.modified;
1053 nlohmann::json segments = nlohmann::json::array();
1054 for (
const auto& segment : song.segments) {
1055 nlohmann::json jseg;
1056 jseg[
"rom_address"] = segment.rom_address;
1057 nlohmann::json tracks = nlohmann::json::array();
1058 for (
const auto& track : segment.tracks) {
1060 jt[
"rom_address"] = track.rom_address;
1061 jt[
"duration_ticks"] = track.duration_ticks;
1062 jt[
"is_empty"] = track.is_empty;
1063 nlohmann::json events = nlohmann::json::array();
1064 for (
const auto& evt : track.events) {
1065 nlohmann::json jevt;
1066 jevt[
"tick"] = evt.tick;
1067 jevt[
"rom_offset"] = evt.rom_offset;
1070 jevt[
"type"] =
"note";
1071 jevt[
"note"][
"pitch"] = evt.note.pitch;
1072 jevt[
"note"][
"duration"] = evt.note.duration;
1073 jevt[
"note"][
"velocity"] = evt.note.velocity;
1074 jevt[
"note"][
"has_duration_prefix"] =
1075 evt.note.has_duration_prefix;
1078 jevt[
"type"] =
"command";
1079 jevt[
"command"][
"opcode"] = evt.command.opcode;
1080 jevt[
"command"][
"params"] = evt.command.params;
1083 jevt[
"type"] =
"subroutine";
1084 jevt[
"command"][
"opcode"] = evt.command.opcode;
1085 jevt[
"command"][
"params"] = evt.command.params;
1089 jevt[
"type"] =
"end";
1092 events.push_back(std::move(jevt));
1094 jt[
"events"] = std::move(events);
1095 tracks.push_back(std::move(jt));
1097 jseg[
"tracks"] = std::move(tracks);
1098 segments.push_back(std::move(jseg));
1100 js[
"segments"] = std::move(segments);
1101 songs.push_back(std::move(js));
1104 nlohmann::json instruments = nlohmann::json::array();
1107 ji[
"name"] = inst.name;
1108 ji[
"sample_index"] = inst.sample_index;
1109 ji[
"attack"] = inst.attack;
1110 ji[
"decay"] = inst.decay;
1111 ji[
"sustain_level"] = inst.sustain_level;
1112 ji[
"sustain_rate"] = inst.sustain_rate;
1113 ji[
"gain"] = inst.gain;
1114 ji[
"pitch_mult"] = inst.pitch_mult;
1115 instruments.push_back(std::move(ji));
1118 nlohmann::json samples = nlohmann::json::array();
1119 for (
const auto& sample :
samples_) {
1120 nlohmann::json jsample;
1121 jsample[
"name"] = sample.name;
1122 jsample[
"loop_point"] = sample.loop_point;
1123 jsample[
"loops"] = sample.loops;
1124 jsample[
"pcm_data"] = sample.pcm_data;
1125 jsample[
"brr_data"] = sample.brr_data;
1126 samples.push_back(std::move(jsample));
1129 root[
"songs"] = std::move(songs);
1130 root[
"instruments"] = std::move(instruments);
1131 root[
"samples"] = std::move(samples);
1144 if (j.contains(
"songs") && j[
"songs"].is_array()) {
1145 for (
const auto& js : j[
"songs"]) {
1147 song.
name = js.value(
"name",
"");
1148 song.
bank = js.value(
"bank", 0);
1149 song.
loop_point = js.value(
"loop_point", -1);
1151 song.
modified = js.value(
"modified",
false);
1153 if (js.contains(
"segments") && js[
"segments"].is_array()) {
1154 for (
const auto& jseg : js[
"segments"]) {
1157 if (jseg.contains(
"tracks") && jseg[
"tracks"].is_array()) {
1159 for (
const auto& jt : jseg[
"tracks"]) {
1162 auto& track = seg.
tracks[track_idx++];
1163 track.rom_address = jt.value(
"rom_address", 0);
1164 track.duration_ticks = jt.value(
"duration_ticks", 0);
1165 track.is_empty = jt.value(
"is_empty",
false);
1166 if (jt.contains(
"events") && jt[
"events"].is_array()) {
1167 track.events.clear();
1168 for (
const auto& jevt : jt[
"events"]) {
1170 evt.
tick = jevt.value(
"tick", 0);
1171 evt.
rom_offset = jevt.value(
"rom_offset", 0);
1172 std::string type = jevt.value(
"type",
"end");
1173 if (type ==
"note" && jevt.contains(
"note")) {
1177 jevt[
"note"].value(
"duration", uint8_t{0});
1179 jevt[
"note"].value(
"velocity", uint8_t{0});
1181 jevt[
"note"].value(
"has_duration_prefix",
false);
1182 }
else if (type ==
"command" || type ==
"subroutine") {
1183 evt.
type = (type ==
"subroutine")
1187 jevt[
"command"].value(
"opcode", uint8_t{0});
1188 auto params = jevt[
"command"].value(
1189 "params", std::array<uint8_t, 3>{0, 0, 0});
1194 track.events.push_back(std::move(evt));
1199 song.
segments.push_back(std::move(seg));
1202 songs_.push_back(std::move(song));
1206 if (j.contains(
"instruments") && j[
"instruments"].is_array()) {
1207 for (
const auto& ji : j[
"instruments"]) {
1209 inst.
name = ji.value(
"name",
"");
1211 inst.
attack = ji.value(
"attack", 0);
1212 inst.
decay = ji.value(
"decay", 0);
1215 inst.
gain = ji.value(
"gain", 0);
1221 if (j.contains(
"samples") && j[
"samples"].is_array()) {
1222 for (
const auto& jsample : j[
"samples"]) {
1224 sample.
name = jsample.value(
"name",
"");
1225 sample.
loop_point = jsample.value(
"loop_point", 0);
1226 sample.
loops = jsample.value(
"loops",
false);
1227 sample.
pcm_data = jsample.value(
"pcm_data", std::vector<int16_t>{});
1228 sample.
brr_data = jsample.value(
"brr_data", std::vector<uint8_t>{});
1229 samples_.push_back(std::move(sample));
1238 return absl::OkStatus();
1239 }
catch (
const std::exception& e) {
1240 return absl::InvalidArgumentError(
1241 absl::StrFormat(
"Failed to parse music state: %s", e.what()));
1250 if (song_id <= 0 || song_id > kVanillaSongCount) {
1253 return kVanillaSongs[song_id].
name;
1257 if (song_id <= 0 || song_id > kVanillaSongCount) {
1260 return kVanillaSongs[song_id].
bank;
1264 if (
const auto* metadata = GetMetadataForBank(bank)) {
1265 return BankSongRange{metadata->vanilla_start_id, metadata->vanilla_end_id};
1271 if (
const auto* metadata = GetMetadataForBank(bank)) {
1272 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::StatusOr< uint8_t > ReadByte(int offset) const
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
static RomSettings & Get()
uint32_t GetAddressOr(const std::string &key, uint32_t default_value) const
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)
static void ApplyBaseAddress(SerializeResult *result, uint16_t new_base_address)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
constexpr char kExpandedMusicHook[]
constexpr char kExpandedMusicMain[]
constexpr char kExpandedMusicAux[]
constexpr BankPointerRegisters kCreditsPointerRegs
constexpr int kVanillaSongCount
uint8_t EncodeLoRomMid(uint32_t pc_offset)
uint32_t ExpandedMusicHookAddress()
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)
uint32_t ExpandedAuxBankRom()
uint32_t ExpandedOverworldBankRom()
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)