#include "2s2h/resource/importer/AudioSequenceFactory.h" #include "2s2h/resource/type/AudioSequence.h" #include "2s2h/resource/importer/AudioSoundFontFactory.h" #include #include #include #include #include #include namespace SOH { std::shared_ptr ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr file, std::shared_ptr initData) { if (!FileHasValidFormatAndReader(file, initData)) { return nullptr; } auto audioSequence = std::make_shared(initData); auto reader = std::get>(file->Reader); audioSequence->sequence.seqDataSize = reader->ReadUInt32(); audioSequence->sequence.seqData = new char[audioSequence->sequence.seqDataSize]; for (uint32_t i = 0; i < audioSequence->sequence.seqDataSize; i++) { audioSequence->sequence.seqData[i] = reader->ReadChar(); } audioSequence->sequence.seqNumber = reader->ReadUByte(); audioSequence->sequence.medium = reader->ReadUByte(); audioSequence->sequence.cachePolicy = reader->ReadUByte(); audioSequence->sequence.numFonts = reader->ReadUInt32(); for (uint32_t i = 0; i < 16; i++) { audioSequence->sequence.fonts[i] = 0; } for (uint32_t i = 0; i < audioSequence->sequence.numFonts; i++) { audioSequence->sequence.fonts[i] = reader->ReadUByte(); } return audioSequence; } template static void WriteInsnOneArg(Ship::BinaryWriter* writer, uint8_t opcode, T arg) { static_assert(std::is_fundamental::value); writer->Write(opcode); writer->Write(arg); } template static void WriteInsnTwoArg(Ship::BinaryWriter* writer, uint8_t opcode, T1 arg1, T2 arg2) { static_assert(std::is_fundamental::value && std::is_fundamental::value); writer->Write(opcode); writer->Write(arg1); writer->Write(arg2); } template static void WriteInsnThreeArg(Ship::BinaryWriter* writer, uint8_t opcode, T1 arg1, T2 arg2, T3 arg3) { static_assert(std::is_fundamental::value && std::is_fundamental::value); writer->Write(opcode); writer->Write(arg1); writer->Write(arg2); writer->Write(arg3); } static void WriteInsnNoArg(Ship::BinaryWriter* writer, uint8_t opcode) { writer->Write(opcode); } static void WriteLegato(Ship::BinaryWriter* writer) { WriteInsnNoArg(writer, 0xC4); } static void WriteNoLegato(Ship::BinaryWriter* writer) { WriteInsnNoArg(writer, 0xC5); } static void WriteMuteBhv(Ship::BinaryWriter* writer, uint8_t arg) { WriteInsnOneArg(writer, 0xD3, arg); } static void WriteMuteScale(Ship::BinaryWriter* writer, uint8_t arg) { WriteInsnOneArg(writer, 0xD5, arg); } static void WriteInitchan(Ship::BinaryWriter* writer, uint16_t channels) { WriteInsnOneArg(writer, 0xD7, channels); } static void WriteLdchan(Ship::BinaryWriter* writer, uint8_t channel, uint16_t offset) { WriteInsnOneArg(writer, 0x90 | channel, offset); } static void WriteVolSHeader(Ship::BinaryWriter* writer, uint8_t vol) { WriteInsnOneArg(writer, 0xDB, vol); } static void WriteVolCHeader(Ship::BinaryWriter* writer, uint8_t vol) { WriteInsnOneArg(writer, 0xDF, vol); } static void WriteTempo(Ship::BinaryWriter* writer, uint8_t tempo) { WriteInsnOneArg(writer, 0xDD, tempo); } static void WriteJump(Ship::BinaryWriter* writer, uint16_t offset) { WriteInsnOneArg(writer, 0xFB, offset); } static void WriteDisablecan(Ship::BinaryWriter* writer, uint16_t channels) { WriteInsnOneArg(writer, 0xD6, channels); } static void WriteNoshort(Ship::BinaryWriter* writer) { WriteInsnNoArg(writer, 0xC4); } static void WriteLdlayer(Ship::BinaryWriter* writer, uint8_t layer, uint16_t offset) { WriteInsnOneArg(writer, 0x88 | layer, offset); } static void WritePan(Ship::BinaryWriter* writer, uint8_t pan) { WriteInsnOneArg(writer, 0xDD, pan); } static void WriteBend(Ship::BinaryWriter* writer, uint8_t bend) { WriteInsnOneArg(writer, 0xD3, bend); } static void WriteInstrument(Ship::BinaryWriter* writer, uint8_t instrument) { WriteInsnOneArg(writer, 0xC1, instrument); } static void WriteTranspose(Ship::BinaryWriter* writer, int8_t transpose) { WriteInsnOneArg(writer, 0xC2, transpose); } static void WriteDelay(Ship::BinaryWriter* writer, uint16_t delay) { if (delay > 0x7F) { WriteInsnOneArg(writer, 0xFD, static_cast(delay | 0x8000)); } else { WriteInsnOneArg(writer, 0xFD, static_cast(delay)); } } template static void WriteLDelay(Ship::BinaryWriter* writer, T delay) { WriteInsnOneArg(writer, 0xC0, delay); } template static void WriteNotedv(Ship::BinaryWriter* writer, uint8_t note, T delay, uint8_t velocity) { WriteInsnTwoArg(writer, note, delay, velocity); } static void WriteNotedvg(Ship::BinaryWriter* writer, uint8_t note, uint16_t delay, uint8_t velocity, uint8_t gateTime) { if (delay > 0x7F) { WriteInsnThreeArg(writer, note, static_cast(delay | 0x8000), velocity, gateTime); } else { WriteInsnThreeArg(writer, note, static_cast(delay), velocity, gateTime); } } static void WriteMonoSingleSeq(Ship::BinaryWriter* writer, uint16_t delay, uint8_t tempo, bool looped) { uint16_t channelStart; uint16_t channelPlaceholderOff; uint16_t loopPoint; uint16_t layerPlaceholderOff; uint16_t layerStart; if (looped) { delay = 0x7FFF; } // Write seq header // These two values are always the same in OOT and MM WriteMuteBhv(writer, 0x20); WriteMuteScale(writer, 0x32); // We only have one channel WriteInitchan(writer, 0b11); // Store the current position so we can write the address of the channel when we are ready. channelPlaceholderOff = writer->GetBaseAddress(); // Store the current position so we can loop here after the song ends. loopPoint = writer->GetBaseAddress(); WriteLdchan(writer, 0, 0); // Fill in the actual address later WriteVolSHeader(writer, 127); // Max volume WriteTempo(writer, tempo); WriteDelay(writer, delay); if (looped) { WriteJump(writer, loopPoint); } WriteDisablecan(writer, 0b11); writer->Write(static_cast(0xFF)); // Fill in the ldchan from before channelStart = writer->GetBaseAddress(); writer->Seek(channelPlaceholderOff, Ship::SeekOffsetType::Start); WriteLdchan(writer, 0, channelStart); writer->Seek(channelStart, Ship::SeekOffsetType::Start); // Channel header layerPlaceholderOff = writer->GetBaseAddress(); WriteNoshort(writer); WriteLdlayer(writer, 0, 0); WritePan(writer, 64); WriteVolCHeader(writer, 127); // Max volume WriteBend(writer, 0); WriteInstrument(writer, 0); WriteDelay(writer, delay); writer->Write(static_cast(0xFF)); layerStart = writer->GetBaseAddress(); writer->Seek(layerPlaceholderOff, Ship::SeekOffsetType::Start); WriteLdlayer(writer, 0, layerStart); writer->Seek(layerStart, Ship::SeekOffsetType::Start); // Note layer WriteLegato(writer); WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast(0x7F), static_cast(1)); writer->Write(static_cast(0xFF)); } static void WriteStereoSingleSeq(Ship::BinaryWriter* writer, uint16_t delay, uint8_t tempo, bool looped) { uint16_t lChannelStart; uint16_t rChannelStart; uint16_t channelPlaceholderOff; uint16_t loopPoint; uint16_t lLayerPlaceholderOff; uint16_t rLayerPlaceholderOff; uint16_t lLayerOffset; uint16_t rLayerOffset; uint16_t layerStart; // Write seq header if (looped) { delay = 0x7FFF; } // These two values are always the same in OOT and MM WriteMuteBhv(writer, 0x20); WriteMuteScale(writer, 0x32); // We only have one channel WriteInitchan(writer, 0b11); // Store the current position so we can write the address of the channel when we are ready. channelPlaceholderOff = writer->GetBaseAddress(); // Store the current position so we can loop here after the song ends. loopPoint = writer->GetBaseAddress(); // Left note channel WriteLdchan(writer, 0, 0); // Fill in the actual address later // Right note channel WriteLdchan(writer, 1, 0); // Fill in the actual address later WriteVolSHeader(writer, 127); // Max volume WriteTempo(writer, tempo); WriteDelay(writer, delay); if (looped) { WriteJump(writer, loopPoint); } WriteDisablecan(writer, 0b11); writer->Write(static_cast(0xFF)); lChannelStart = writer->GetBaseAddress(); // Left Channel header WriteNoshort(writer); lLayerPlaceholderOff = writer->GetBaseAddress(); WriteLdlayer(writer, 0, 0); WritePan(writer, 0); WriteVolCHeader(writer, 127); // Max volume WriteBend(writer, 0); WriteInstrument(writer, 0); WriteDelay(writer, delay); writer->Write(static_cast(0xFF)); rChannelStart = writer->GetBaseAddress(); // Right Channel header WriteNoshort(writer); rLayerPlaceholderOff = writer->GetBaseAddress(); WriteLdlayer(writer, 1, 0); WritePan(writer, 127); WriteVolCHeader(writer, 127); // Max volume WriteBend(writer, 0); WriteInstrument(writer, 1); WriteDelay(writer, delay); writer->Write(static_cast(0xFF)); uint16_t placeHolder = writer->GetBaseAddress(); writer->Seek(channelPlaceholderOff, Ship::SeekOffsetType::Start); WriteLdchan(writer, 0, lChannelStart); WriteLdchan(writer, 1, rChannelStart); writer->Seek(placeHolder, Ship::SeekOffsetType::Start); // Left Note layer lLayerOffset = writer->GetBaseAddress(); WriteLegato(writer); WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast(0x7F), static_cast(1)); writer->Write(static_cast(0xFF)); // Right Note layer rLayerOffset = writer->GetBaseAddress(); WriteLegato(writer); WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast(0x7F), static_cast(1)); writer->Write(static_cast(0xFF)); writer->Seek(lLayerPlaceholderOff, Ship::SeekOffsetType::Start); WriteLdlayer(writer, 0, lLayerOffset); writer->Seek(rLayerPlaceholderOff, Ship::SeekOffsetType::Start); WriteLdlayer(writer, 1, rLayerOffset); } std::shared_ptr ResourceFactoryXMLAudioSequenceV0::ReadResource(std::shared_ptr file, std::shared_ptr initData) { if (!FileHasValidFormatAndReader(file, initData)) { return nullptr; } auto sequence = std::make_shared(initData); auto child = std::get>(file->Reader)->FirstChildElement(); unsigned int i = 0; sequence->sequence.medium = ResourceFactoryXMLSoundFontV0::MediumStrToInt(child->Attribute("Medium"), initData->Path.c_str()); sequence->sequence.cachePolicy = ResourceFactoryXMLSoundFontV0::CachePolicyToInt(child->Attribute("CachePolicy"), initData->Path.c_str()); sequence->sequence.seqDataSize = child->IntAttribute("Size"); sequence->sequence.seqNumber = child->IntAttribute("Index"); bool streamed = child->BoolAttribute("Streamed"); memset(sequence->sequence.fonts, 0, sizeof(sequence->sequence.fonts)); tinyxml2::XMLElement* fontsElement = child->FirstChildElement(); tinyxml2::XMLElement* fontElement = fontsElement->FirstChildElement(); while (fontElement != nullptr) { sequence->sequence.fonts[i] = fontElement->IntAttribute("FontIdx"); fontElement = fontElement->NextSiblingElement(); i++; } sequence->sequence.numFonts = i; const char* path = child->Attribute("Path"); std::shared_ptr seqFile; if (path != nullptr) { seqFile = Ship::Context::GetRawInstance()->GetResourceManager()->GetArchiveManager()->LoadFile(path); } if (!streamed) { sequence->sequence.seqDataSize = seqFile->Buffer.get()->size(); sequence->sequence.seqData = new char[seqFile->Buffer.get()->size()]; memcpy(sequence->sequence.seqData, seqFile->Buffer.get()->data(), seqFile->Buffer.get()->size()); } else { // setting numFonts to -1 tells the game's audio engine the sound font to used is CRC64 encoded in the font // indicies. sequence->sequence.numFonts = -1; if (path != nullptr) { sequence->sequence.seqDataSize = seqFile->Buffer.get()->size(); sequence->sequence.seqData = new char[seqFile->Buffer.get()->size()]; memcpy(sequence->sequence.seqData, seqFile->Buffer.get()->data(), seqFile->Buffer.get()->size()); } else { unsigned int length = child->UnsignedAttribute("Length"); bool looped = child->BoolAttribute("Looped", true); bool stereo = child->BoolAttribute("Stereo", false); Ship::BinaryWriter writer = Ship::BinaryWriter(); writer.SetEndianness(Ship::Endianness::Big); // 1 second worth of ticks can be found by using `ticks = 60 / (bpm * 48)` // Get the number of ticks per second and then divide the length by this number to get the number of ticks // for the song. constexpr uint8_t TEMPO = 1; constexpr float TEMPO_F = TEMPO; // Use floats for this first calculation so we can round up float delayF = length / (60.0f / (TEMPO_F * 48.0f)); // Convert to u16. This way this value is encoded changes depending on the value. // It can be at most 0xFFFF so store it in a u16 for now. uint16_t delay; if (delayF >= 65535.0f) { delay = 0x7FFF; } else { delay = delayF; } if (stereo) { WriteStereoSingleSeq(&writer, delay, TEMPO, looped); } else { WriteMonoSingleSeq(&writer, delay, TEMPO, looped); } sequence->sequence.seqDataSize = writer.ToVector().size(); sequence->sequence.seqData = new char[sequence->sequence.seqDataSize]; memcpy(sequence->sequence.seqData, writer.ToVector().data(), sequence->sequence.seqDataSize); } } return sequence; } } // namespace SOH