diff --git a/CMakeLists.txt b/CMakeLists.txt index 6014de7..9d0e53f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,12 +10,14 @@ set(SOURCES lib/AudioGroupPool.cpp lib/AudioGroupProject.cpp lib/AudioGroupSampleDirectory.cpp + lib/DirectoryEnumerator.cpp lib/Emitter.cpp lib/Engine.cpp lib/Envelope.cpp lib/Listener.cpp lib/Sequencer.cpp lib/SoundMacroState.cpp + lib/SongConverter.cpp lib/SongState.cpp lib/Voice.cpp lib/VolumeLUT.cpp @@ -35,6 +37,7 @@ set(HEADERS include/amuse/AudioGroupPool.hpp include/amuse/AudioGroupProject.hpp include/amuse/AudioGroupSampleDirectory.hpp + include/amuse/DirectoryEnumerator.hpp include/amuse/Emitter.hpp include/amuse/Engine.hpp include/amuse/Entity.hpp @@ -42,6 +45,7 @@ set(HEADERS include/amuse/Listener.hpp include/amuse/Sequencer.hpp include/amuse/SoundMacroState.hpp + include/amuse/SongConverter.hpp include/amuse/SongState.hpp include/amuse/Voice.hpp include/amuse/Submix.hpp @@ -81,7 +85,13 @@ if(TARGET boo) # VST Target add_subdirectory(VST) - # Multi-platform CLI tool - add_executable(amuseplay WIN32 driver/main.cpp) + # Multi-platform CLI tools + + # Player + add_executable(amuseplay WIN32 driver/amuseplay.cpp) target_link_libraries(amuseplay amuse boo ${BOO_SYS_LIBS} logvisor athena-core ${ZLIB_LIBRARIES}) + + # Converter + add_executable(amuseconv driver/amuseconv.cpp) + target_link_libraries(amuseconv amuse ${BOO_SYS_LIBS} logvisor athena-core ${ZLIB_LIBRARIES}) endif() diff --git a/VST/VSTEditor.cpp b/VST/VSTEditor.cpp index 5933045..b0852c9 100644 --- a/VST/VSTEditor.cpp +++ b/VST/VSTEditor.cpp @@ -385,7 +385,7 @@ void VSTEditor::addAction() if (dotpos != std::string::npos) name.assign(path.cbegin(), path.cbegin() + dotpos); size_t slashpos = name.rfind(L'\\'); - size_t fslashpos = name.rfind(L"/"); + size_t fslashpos = name.rfind(L'/'); if (slashpos == std::string::npos) slashpos = fslashpos; else if (fslashpos != std::string::npos) diff --git a/driver/amuseconv.cpp b/driver/amuseconv.cpp new file mode 100644 index 0000000..7c056fb --- /dev/null +++ b/driver/amuseconv.cpp @@ -0,0 +1,192 @@ +#include "amuse/amuse.hpp" +#include "athena/FileReader.hpp" +#include "athena/DNAYaml.hpp" +#include "logvisor/logvisor.hpp" +#include +#include + +static logvisor::Module Log(_S("amuseconv")); + +static bool BuildAudioGroup(const amuse::SystemString& groupBase, const amuse::SystemString& targetPath) +{ + return true; +} + +static bool ExtractAudioGroup(const amuse::SystemString& inPath, const amuse::SystemString& targetPath) +{ + amuse::ContainerRegistry::Type type; + auto groups = amuse::ContainerRegistry::LoadContainer(inPath.c_str(), type); + + if (groups.size()) + { + Log.report(logvisor::Info, _S("Found '%s'"), amuse::ContainerRegistry::TypeToName(type)); + + amuse::Mkdir(targetPath.c_str(), 0755); + Log.report(logvisor::Info, _S("Established directory at %s"), targetPath.c_str()); + + for (auto& group : groups) + { + Log.report(logvisor::Info, _S("Extracting %s"), group.first.c_str()); + } + + } + + auto songs = amuse::ContainerRegistry::LoadSongs(inPath.c_str()); + amuse::SystemString songsDir = targetPath + _S("/midifiles"); + bool madeDir = false; + for (auto& pair : songs) + { + if (!madeDir) + { + amuse::Mkdir(songsDir.c_str(), 0755); + madeDir = true; + } + + amuse::SystemString songPath = songsDir + _S('/') + pair.first + _S(".mid"); + FILE* fp = amuse::FOpen(songPath.c_str(), _S("wb")); + if (fp) + { + Log.report(logvisor::Info, _S("Extracting %s"), pair.first.c_str()); + amuse::SongConverter::Target extractedTarget; + std::vector mid = amuse::SongConverter::SongToMIDI(pair.second.m_data.get(), extractedTarget); + fwrite(mid.data(), 1, mid.size(), fp); + fclose(fp); + } + } + + return true; +} + +static bool BuildN64SNG(const amuse::SystemString& inPath, const amuse::SystemString& targetPath, bool bigEndian) +{ + return true; +} + +static bool BuildGCNSNG(const amuse::SystemString& inPath, const amuse::SystemString& targetPath) +{ + return true; +} + +static bool ExtractSNG(const amuse::SystemString& inPath, const amuse::SystemString& targetPath) +{ + return true; +} + +enum ConvType +{ + ConvN64, + ConvGCN, + ConvPC +}; + +static void ReportConvType(ConvType tp) +{ + switch (tp) + { + case ConvN64: + Log.report(logvisor::Info, _S("using N64 format")); + break; + case ConvPC: + Log.report(logvisor::Info, _S("using PC format")); + break; + case ConvGCN: + default: + Log.report(logvisor::Info, _S("using GameCube format")); + break; + } +} + +#if _WIN32 +int wmain(int argc, const amuse::SystemChar** argv) +#else +int main(int argc, const amuse::SystemChar** argv) +#endif +{ + logvisor::RegisterConsoleLogger(); + + if (argc < 3) + { + printf("Usage: amuseconv [n64|pc|gcn]\n"); + return 0; + } + + ConvType type = ConvGCN; + if (argc >= 4) + { + if (!amuse::CompareCaseInsensitive(argv[3], _S("n64"))) + type = ConvN64; + else if (!amuse::CompareCaseInsensitive(argv[3], _S("gcn"))) + type = ConvGCN; + else if (!amuse::CompareCaseInsensitive(argv[3], _S("pc"))) + type = ConvPC; + else + { + Log.report(logvisor::Error, _S("unrecognized format: %s"), argv[3]); + return 1; + } + } + + bool good = false; + FILE* fin = amuse::FOpen(argv[1], _S("rb")); + if (fin) + { + fclose(fin); + amuse::SystemString barePath(argv[1]); + size_t dotPos = barePath.rfind(_S('.')); + const amuse::SystemChar* dot = barePath.c_str() + dotPos; + if (dotPos != amuse::SystemString::npos) + { + if (!amuse::CompareCaseInsensitive(dot, _S(".mid")) || + !amuse::CompareCaseInsensitive(dot, _S(".midi"))) + { + ReportConvType(type); + switch (type) + { + case ConvN64: + good = BuildN64SNG(amuse::SystemString(barePath.begin(), barePath.begin() + dotPos), argv[2], true); + break; + case ConvPC: + good = BuildN64SNG(amuse::SystemString(barePath.begin(), barePath.begin() + dotPos), argv[2], false); + break; + case ConvGCN: + default: + good = BuildGCNSNG(amuse::SystemString(barePath.begin(), barePath.begin() + dotPos), argv[2]); + break; + } + } + else if (!amuse::CompareCaseInsensitive(dot, _S(".son")) || + !amuse::CompareCaseInsensitive(dot, _S(".sng"))) + { + good = ExtractSNG(argv[1], argv[2]); + } + else + { + good = ExtractAudioGroup(argv[1], argv[2]); + } + } + } + else + { + amuse::Sstat theStat; + if (!amuse::Stat(argv[1], &theStat) && S_ISDIR(theStat.st_mode)) + { + amuse::SystemString projectPath(argv[1]); + projectPath += _S("/project.yaml"); + fin = amuse::FOpen(projectPath.c_str(), _S("rb")); + if (fin) + { + fclose(fin); + ReportConvType(type); + good = BuildAudioGroup(argv[1], argv[2]); + } + } + } + + if (!good) + { + Log.report(logvisor::Error, _S("unable to convert %s to %s"), argv[1], argv[2]); + return 1; + } + + return 0; +} diff --git a/driver/main.cpp b/driver/amuseplay.cpp similarity index 100% rename from driver/main.cpp rename to driver/amuseplay.cpp diff --git a/include/amuse/Common.hpp b/include/amuse/Common.hpp index 64be161..081e5ba 100644 --- a/include/amuse/Common.hpp +++ b/include/amuse/Common.hpp @@ -11,6 +11,7 @@ #ifndef _MSC_VER #include +#include #endif namespace amuse @@ -30,12 +31,18 @@ namespace amuse # ifndef _S # define _S(val) L ## val # endif + typedef struct _stat Sstat; + static inline int Mkdir(const wchar_t* path, int) {return _wmkdir(path);} + static inline int Stat(const wchar_t* path, Sstat* statout) {return _wstat(path, statout);} #else using SystemString = std::string; using SystemChar = char; # ifndef _S # define _S(val) val # endif + typedef struct stat Sstat; + static inline int Mkdir(const char* path, mode_t mode) {return mkdir(path, mode);} + static inline int Stat(const char* path, Sstat* statout) {return stat(path, statout);} #endif #if _WIN32 diff --git a/include/amuse/DirectoryEnumerator.hpp b/include/amuse/DirectoryEnumerator.hpp new file mode 100644 index 0000000..c8df838 --- /dev/null +++ b/include/amuse/DirectoryEnumerator.hpp @@ -0,0 +1,74 @@ +#ifndef __AMUSE_DIRECTORY_ENUMERATOR__ +#define __AMUSE_DIRECTORY_ENUMERATOR__ + +#include "Common.hpp" +#include + +namespace amuse +{ + +struct CaseInsensitiveCompare +{ + bool operator()(const std::string& lhs, const std::string& rhs) const + { +#if _WIN32 + if (_stricmp(lhs.c_str(), rhs.c_str()) < 0) +#else + if (strcasecmp(lhs.c_str(), rhs.c_str()) < 0) +#endif + return true; + return false; + } + +#if _WIN32 + bool operator()(const std::wstring& lhs, const std::wstring& rhs) const + { + if (_wcsicmp(lhs.c_str(), rhs.c_str()) < 0) + return true; + return false; + } +#endif +}; + +class DirectoryEnumerator +{ +public: + enum class Mode + { + Native, + DirsSorted, + FilesSorted, + DirsThenFilesSorted + }; + struct Entry + { + SystemString m_path; + SystemString m_name; + size_t m_fileSz; + bool m_isDir; + + private: + friend class DirectoryEnumerator; + Entry(SystemString&& path, const SystemChar* name, size_t sz, bool isDir) + : m_path(std::move(path)), m_name(name), m_fileSz(sz), m_isDir(isDir) {} + }; + +private: + std::vector m_entries; + +public: + DirectoryEnumerator(const SystemString& path, Mode mode=Mode::DirsThenFilesSorted, + bool sizeSort=false, bool reverse=false, bool noHidden=false) + : DirectoryEnumerator(path.c_str(), mode, sizeSort, reverse, noHidden) {} + DirectoryEnumerator(const SystemChar* path, Mode mode=Mode::DirsThenFilesSorted, + bool sizeSort=false, bool reverse=false, bool noHidden=false); + + operator bool() const {return m_entries.size() != 0;} + size_t size() const {return m_entries.size();} + std::vector::const_iterator begin() const {return m_entries.cbegin();} + std::vector::const_iterator end() const {return m_entries.cend();} +}; + +} + +#endif // __AMUSE_DIRECTORY_ENUMERATOR__ diff --git a/include/amuse/SongConverter.hpp b/include/amuse/SongConverter.hpp new file mode 100644 index 0000000..d921f07 --- /dev/null +++ b/include/amuse/SongConverter.hpp @@ -0,0 +1,25 @@ +#ifndef __AMUSE_SONGCONVERTER_HPP__ +#define __AMUSE_SONGCONVERTER_HPP__ + +#include +#include + +namespace amuse +{ + +class SongConverter +{ +public: + enum class Target + { + N64, + GCN, + PC + }; + static std::vector SongToMIDI(const unsigned char* data, Target& targetOut); + static std::vector MIDIToSong(const unsigned char* data, Target target); +}; + +} + +#endif // __AMUSE_SONGCONVERTER_HPP__ diff --git a/include/amuse/SongState.hpp b/include/amuse/SongState.hpp index 10c263f..6ba0e02 100644 --- a/include/amuse/SongState.hpp +++ b/include/amuse/SongState.hpp @@ -22,6 +22,7 @@ enum class SongPlayState class SongState { friend class Voice; + friend class SongConverter; /** Song header */ struct Header @@ -55,6 +56,7 @@ class SongState }; const unsigned char* m_songData = nullptr; /**< Base pointer to active song */ + bool m_bigEndian; /**< True if loaded song is big-endian data */ /** State of a single track within arrangement */ struct Track @@ -85,8 +87,8 @@ class SongState int32_t m_lastN64EventTick = 0; /**< Last command time on this channel (for computing delta times from absolute times in N64 songs) */ Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions); - void setRegion(Sequencer& seq, const TrackRegion* region); - void advanceRegion(Sequencer& seq); + void setRegion(Sequencer* seq, const TrackRegion* region); + void advanceRegion(Sequencer* seq); bool advance(Sequencer& seq, int32_t ticks); }; std::array, 64> m_tracks; diff --git a/include/amuse/amuse.hpp b/include/amuse/amuse.hpp index ca4a950..8b37f28 100644 --- a/include/amuse/amuse.hpp +++ b/include/amuse/amuse.hpp @@ -16,6 +16,8 @@ #include "Listener.hpp" #include "Sequencer.hpp" #include "SoundMacroState.hpp" +#include "SongConverter.hpp" +#include "SongState.hpp" #include "Submix.hpp" #include "Voice.hpp" diff --git a/lib/ContainerRegistry.cpp b/lib/ContainerRegistry.cpp index 754e31a..81a7537 100644 --- a/lib/ContainerRegistry.cpp +++ b/lib/ContainerRegistry.cpp @@ -725,6 +725,41 @@ static std::vector> LoadRS1PC(F return ret; } +static std::vector> LoadRS1PCSongs(FILE* fp) +{ + std::vector> ret; + size_t endPos = FileLength(fp); + + uint32_t fstOff; + uint32_t fstSz; + if (fread(&fstOff, 1, 4, fp) == 4 && fread(&fstSz, 1, 4, fp) == 4) + { + if (fstOff + fstSz <= endPos) + { + FSeek(fp, fstOff, SEEK_SET); + uint32_t elemCount = fstSz / 32; + std::unique_ptr entries(new RS1FSTEntry[elemCount]); + fread(entries.get(), fstSz, 1, fp); + + for (uint32_t i=0 ; i song(new uint8_t[entry.decompSz]); + FSeek(fp, entry.offset, SEEK_SET); + fread(song.get(), 1, entry.decompSz, fp); + + SystemString name = StrToSys(entry.name); + ret.emplace_back(name, ContainerRegistry::SongData(std::move(song), entry.decompSz, -1, -1)); + } + } + } + } + + return ret; +} + static bool ValidateRS1N64(FILE* fp) { size_t endPos = FileLength(fp); @@ -1051,6 +1086,41 @@ static std::vector> LoadBFNPC(F return ret; } +static std::vector> LoadBFNPCSongs(FILE* fp) +{ + std::vector> ret; + size_t endPos = FileLength(fp); + + uint32_t fstOff; + uint32_t fstSz; + if (fread(&fstOff, 1, 4, fp) == 4 && fread(&fstSz, 1, 4, fp) == 4) + { + if (fstOff + fstSz <= endPos) + { + FSeek(fp, fstOff, SEEK_SET); + uint32_t elemCount = fstSz / 32; + std::unique_ptr entries(new RS1FSTEntry[elemCount]); + fread(entries.get(), fstSz, 1, fp); + + for (uint32_t i=0 ; i song(new uint8_t[entry.decompSz]); + FSeek(fp, entry.offset, SEEK_SET); + fread(song.get(), 1, entry.decompSz, fp); + + SystemString name = StrToSys(entry.name); + ret.emplace_back(name, ContainerRegistry::SongData(std::move(song), entry.decompSz, -1, -1)); + } + } + } + } + + return ret; +} + static bool ValidateBFNN64(FILE* fp) { size_t endPos = FileLength(fp); @@ -1216,6 +1286,61 @@ static std::vector> LoadBFNN64( return ret; } +static std::vector> LoadBFNN64Songs(FILE* fp) +{ + std::vector> ret; + size_t endPos = FileLength(fp); + + std::unique_ptr data(new uint8_t[endPos]); + fread(data.get(), 1, endPos, fp); + + if ((data[0] & 0x80) != 0x80 && (data[3] & 0x80) == 0x80) + SwapN64Rom32(data.get(), endPos); + else if ((data[0] & 0x80) != 0x80 && (data[1] & 0x80) == 0x80) + SwapN64Rom16(data.get(), endPos); + + const uint8_t* dataSeg = reinterpret_cast(memmem(data.get(), endPos, + "dbg_data\0\0\0\0\0\0\0\0", 16)); + if (dataSeg) + { + dataSeg += 28; + size_t fstEnd = SBig(*reinterpret_cast(dataSeg)); + dataSeg += 4; + size_t fstOff = SBig(*reinterpret_cast(dataSeg)); + if (endPos <= size_t(dataSeg - data.get()) + fstOff || endPos <= size_t(dataSeg - data.get()) + fstEnd) + return ret; + + const RS1FSTEntry* entry = reinterpret_cast(dataSeg + fstOff); + const RS1FSTEntry* lastEnt = reinterpret_cast(dataSeg + fstEnd); + + for (; entry != lastEnt ; ++entry) + { + RS1FSTEntry ent = *entry; + ent.swapBig(); + + if (!strncmp(ent.name, "s_", 2)) + { + std::unique_ptr song(new uint8_t[ent.decompSz]); + + if (ent.compSz == 0xffffffff) + { + memmove(song.get(), dataSeg + ent.offset, ent.decompSz); + } + else + { + uLongf outSz = ent.decompSz; + uncompress(song.get(), &outSz, dataSeg + ent.offset, ent.compSz); + } + + SystemString name = StrToSys(ent.name); + ret.emplace_back(name, ContainerRegistry::SongData(std::move(song), ent.decompSz, -1, -1)); + } + } + } + + return ret; +} + struct RS2FSTEntry { uint64_t offset; @@ -1902,6 +2027,13 @@ ContainerRegistry::LoadSongs(const SystemChar* path) return ret; } + if (ValidateRS1PC(fp)) + { + auto ret = LoadRS1PCSongs(fp); + fclose(fp); + return ret; + } + if (ValidateRS1N64(fp)) { auto ret = LoadRS1N64Songs(fp); @@ -1909,28 +2041,19 @@ ContainerRegistry::LoadSongs(const SystemChar* path) return ret; } -#if 0 - if (ValidateRS1PCSongs(fp)) - { - auto ret = LoadRS1PCSongs(fp); - fclose(fp); - return ret; - } - - if (ValidateBFNPCSongs(fp)) + if (ValidateBFNPC(fp)) { auto ret = LoadBFNPCSongs(fp); fclose(fp); return ret; } - if (ValidateBFNN64Songs(fp)) + if (ValidateBFNN64(fp)) { auto ret = LoadBFNN64Songs(fp); fclose(fp); return ret; } -#endif if (ValidateRS2(fp)) { diff --git a/lib/DirectoryEnumerator.cpp b/lib/DirectoryEnumerator.cpp new file mode 100644 index 0000000..7ab607d --- /dev/null +++ b/lib/DirectoryEnumerator.cpp @@ -0,0 +1,277 @@ +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +#include + +#include "amuse/DirectoryEnumerator.hpp" + +namespace amuse +{ + +DirectoryEnumerator::DirectoryEnumerator(const SystemChar* path, Mode mode, + bool sizeSort, bool reverse, bool noHidden) +{ + Sstat theStat; + if (Stat(path, &theStat) || !S_ISDIR(theStat.st_mode)) + return; + +#if _WIN32 + SystemString wc(path); + wc += _S("/*"); + WIN32_FIND_DATAW d; + HANDLE dir = FindFirstFileW(wc.c_str(), &d); + if (dir == INVALID_HANDLE_VALUE) + return; + switch (mode) + { + case Mode::Native: + do + { + if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S(".."))) + continue; + if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0)) + continue; + SystemString fp(path); + fp += _S('/'); + fp += d.cFileName; + Sstat st; + if (Stat(fp.c_str(), &st)) + continue; + + size_t sz = 0; + bool isDir = false; + if (S_ISDIR(st.st_mode)) + isDir = true; + else if (S_ISREG(st.st_mode)) + sz = st.st_size; + else + continue; + + m_entries.push_back(std::move(Entry(std::move(fp), d.cFileName, sz, isDir))); + } while (FindNextFileW(dir, &d)); + break; + case Mode::DirsThenFilesSorted: + case Mode::DirsSorted: + { + std::map sort; + do + { + if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S(".."))) + continue; + if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0)) + continue; + SystemString fp(path); + fp +=_S('/'); + fp += d.cFileName; + Sstat st; + if (Stat(fp.c_str(), &st) || !S_ISDIR(st.st_mode)) + continue; + sort.emplace(std::make_pair(d.cFileName, Entry(std::move(fp), d.cFileName, 0, true))); + } while (FindNextFileW(dir, &d)); + + if (reverse) + for (auto it=sort.crbegin() ; it != sort.crend() ; ++it) + m_entries.push_back(std::move(it->second)); + else + for (auto& e : sort) + m_entries.push_back(std::move(e.second)); + + if (mode == Mode::DirsSorted) + break; + FindClose(dir); + dir = FindFirstFileW(wc.c_str(), &d); + } + case Mode::FilesSorted: + { + if (mode == Mode::FilesSorted) + m_entries.clear(); + + if (sizeSort) + { + std::multimap sort; + do + { + if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S(".."))) + continue; + if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0)) + continue; + SystemString fp(path); + fp += _S('/'); + fp += d.cFileName; + Sstat st; + if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode)) + continue; + sort.emplace(std::make_pair(st.st_size, Entry(std::move(fp), d.cFileName, st.st_size, false))); + } while (FindNextFileW(dir, &d)); + + if (reverse) + for (auto it=sort.crbegin() ; it != sort.crend() ; ++it) + m_entries.push_back(std::move(it->second)); + else + for (auto& e : sort) + m_entries.push_back(std::move(e.second)); + } + else + { + std::map sort; + do + { + if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S(".."))) + continue; + if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0)) + continue; + SystemString fp(path); + fp += _S('/'); + fp += d.cFileName; + Sstat st; + if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode)) + continue; + sort.emplace(std::make_pair(d.cFileName, Entry(std::move(fp), d.cFileName, st.st_size, false))); + } while (FindNextFileW(dir, &d)); + + if (reverse) + for (auto it=sort.crbegin() ; it != sort.crend() ; ++it) + m_entries.push_back(std::move(it->second)); + else + for (auto& e : sort) + m_entries.push_back(std::move(e.second)); + } + + break; + } + } + FindClose(dir); + +#else + + DIR* dir = opendir(path); + if (!dir) + return; + const dirent* d; + switch (mode) + { + case Mode::Native: + while ((d = readdir(dir))) + { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + if (noHidden && d->d_name[0] == '.') + continue; + SystemString fp(path); + fp += '/'; + fp += d->d_name; + Sstat st; + if (Stat(fp.c_str(), &st)) + continue; + + size_t sz = 0; + bool isDir = false; + if (S_ISDIR(st.st_mode)) + isDir = true; + else if (S_ISREG(st.st_mode)) + sz = st.st_size; + else + continue; + + m_entries.push_back(std::move(Entry(std::move(fp), d->d_name, sz, isDir))); + } + break; + case Mode::DirsThenFilesSorted: + case Mode::DirsSorted: + { + std::map sort; + while ((d = readdir(dir))) + { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + if (noHidden && d->d_name[0] == '.') + continue; + SystemString fp(path); + fp += '/'; + fp += d->d_name; + Sstat st; + if (Stat(fp.c_str(), &st) || !S_ISDIR(st.st_mode)) + continue; + sort.emplace(std::make_pair(d->d_name, Entry(std::move(fp), d->d_name, 0, true))); + } + + if (reverse) + for (auto it=sort.crbegin() ; it != sort.crend() ; ++it) + m_entries.push_back(std::move(it->second)); + else + for (auto& e : sort) + m_entries.push_back(std::move(e.second)); + + if (mode == Mode::DirsSorted) + break; + rewinddir(dir); + } + case Mode::FilesSorted: + { + if (mode == Mode::FilesSorted) + m_entries.clear(); + + if (sizeSort) + { + std::multimap sort; + while ((d = readdir(dir))) + { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + if (noHidden && d->d_name[0] == '.') + continue; + SystemString fp(path); + fp += '/'; + fp += d->d_name; + Sstat st; + if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode)) + continue; + sort.emplace(std::make_pair(st.st_size, Entry(std::move(fp), d->d_name, st.st_size, false))); + } + + if (reverse) + for (auto it=sort.crbegin() ; it != sort.crend() ; ++it) + m_entries.push_back(std::move(it->second)); + else + for (auto& e : sort) + m_entries.push_back(std::move(e.second)); + } + else + { + std::map sort; + while ((d = readdir(dir))) + { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + if (noHidden && d->d_name[0] == '.') + continue; + SystemString fp(path); + fp += '/'; + fp += d->d_name; + Sstat st; + if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode)) + continue; + sort.emplace(std::make_pair(d->d_name, Entry(std::move(fp), d->d_name, st.st_size, false))); + } + + if (reverse) + for (auto it=sort.crbegin() ; it != sort.crend() ; ++it) + m_entries.push_back(std::move(it->second)); + else + for (auto& e : sort) + m_entries.push_back(std::move(e.second)); + } + + break; + } + } + closedir(dir); + +#endif +} + +} diff --git a/lib/SongConverter.cpp b/lib/SongConverter.cpp new file mode 100644 index 0000000..a798605 --- /dev/null +++ b/lib/SongConverter.cpp @@ -0,0 +1,826 @@ +#include "amuse/SongConverter.hpp" +#include "amuse/SongState.hpp" +#include "amuse/Common.hpp" +#include +#include + +namespace amuse +{ + +static inline uint8_t clamp7(uint8_t val) {return std::max(0, std::min(127, int(val)));} + +enum class Status +{ + NoteOff = 0x80, + NoteOn = 0x90, + NotePressure = 0xA0, + ControlChange = 0xB0, + ProgramChange = 0xC0, + ChannelPressure = 0xD0, + PitchBend = 0xE0, + SysEx = 0xF0, + TimecodeQuarterFrame = 0xF1, + SongPositionPointer = 0xF2, + SongSelect = 0xF3, + TuneRequest = 0xF6, + SysExTerm = 0xF7, + TimingClock = 0xF8, + Start = 0xFA, + Continue = 0xFB, + Stop = 0xFC, + ActiveSensing = 0xFE, + Reset = 0xFF, +}; + +class IMIDIReader +{ +public: + virtual void noteOff(uint8_t chan, uint8_t key, uint8_t velocity)=0; + virtual void noteOn(uint8_t chan, uint8_t key, uint8_t velocity)=0; + virtual void notePressure(uint8_t chan, uint8_t key, uint8_t pressure)=0; + virtual void controlChange(uint8_t chan, uint8_t control, uint8_t value)=0; + virtual void programChange(uint8_t chan, uint8_t program)=0; + virtual void channelPressure(uint8_t chan, uint8_t pressure)=0; + virtual void pitchBend(uint8_t chan, int16_t pitch)=0; + + virtual void allSoundOff(uint8_t chan)=0; + virtual void resetAllControllers(uint8_t chan)=0; + virtual void localControl(uint8_t chan, bool on)=0; + virtual void allNotesOff(uint8_t chan)=0; + virtual void omniMode(uint8_t chan, bool on)=0; + virtual void polyMode(uint8_t chan, bool on)=0; + + virtual void sysex(const void* data, size_t len)=0; + virtual void timeCodeQuarterFrame(uint8_t message, uint8_t value)=0; + virtual void songPositionPointer(uint16_t pointer)=0; + virtual void songSelect(uint8_t song)=0; + virtual void tuneRequest()=0; + + virtual void startSeq()=0; + virtual void continueSeq()=0; + virtual void stopSeq()=0; + + virtual void reset()=0; +}; + +class MIDIDecoder +{ + IMIDIReader& m_out; + uint8_t m_status = 0; + bool _readContinuedValue(std::vector::const_iterator& it, + std::vector::const_iterator end, + uint32_t& valOut) + { + uint8_t a = *it++; + valOut = a & 0x7f; + + if (a & 0x80) + { + if (it == end) + return false; + valOut <<= 7; + a = *it++; + valOut |= a & 0x7f; + + if (a & 0x80) + { + if (it == end) + return false; + valOut <<= 7; + a = *it++; + valOut |= a & 0x7f; + } + } + + return true; + } +public: + MIDIDecoder(IMIDIReader& out) : m_out(out) {} + std::vector::const_iterator + receiveBytes(std::vector::const_iterator begin, + std::vector::const_iterator end) + { + std::vector::const_iterator it = begin; + if (it == end) + return begin; + + uint8_t a = *it++; + uint8_t b; + if (a & 0x80) + m_status = a; + else + it--; + + uint8_t chan = m_status & 0xf; + switch (Status(m_status & 0xf0)) + { + case Status::NoteOff: + { + if (it == end) + return begin; + a = *it++; + if (it == end) + return begin; + b = *it++; + m_out.noteOff(chan, clamp7(a), clamp7(b)); + break; + } + case Status::NoteOn: + { + if (it == end) + return begin; + a = *it++; + if (it == end) + return begin; + b = *it++; + m_out.noteOn(chan, clamp7(a), clamp7(b)); + break; + } + case Status::NotePressure: + { + if (it == end) + return begin; + a = *it++; + if (it == end) + return begin; + b = *it++; + m_out.notePressure(chan, clamp7(a), clamp7(b)); + break; + } + case Status::ControlChange: + { + if (it == end) + return begin; + a = *it++; + if (it == end) + return begin; + b = *it++; + m_out.controlChange(chan, clamp7(a), clamp7(b)); + break; + } + case Status::ProgramChange: + { + if (it == end) + return begin; + a = *it++; + m_out.programChange(chan, clamp7(a)); + break; + } + case Status::ChannelPressure: + { + if (it == end) + return begin; + a = *it++; + m_out.channelPressure(chan, clamp7(a)); + break; + } + case Status::PitchBend: + { + if (it == end) + return begin; + a = *it++; + if (it == end) + return begin; + b = *it++; + m_out.pitchBend(chan, clamp7(b) * 128 + clamp7(a)); + break; + } + case Status::SysEx: + { + switch (Status(m_status & 0xff)) + { + case Status::SysEx: + { + uint32_t len; + if (!_readContinuedValue(it, end, len) || end - it < len) + return begin; + m_out.sysex(&*it, len); + break; + } + case Status::TimecodeQuarterFrame: + { + if (it == end) + return begin; + a = *it++; + m_out.timeCodeQuarterFrame(a >> 4 & 0x7, a & 0xf); + break; + } + case Status::SongPositionPointer: + { + if (it == end) + return begin; + a = *it++; + if (it == end) + return begin; + b = *it++; + m_out.songPositionPointer(clamp7(b) * 128 + clamp7(a)); + break; + } + case Status::SongSelect: + { + if (it == end) + return begin; + a = *it++; + m_out.songSelect(clamp7(a)); + break; + } + case Status::TuneRequest: + m_out.tuneRequest(); + break; + case Status::Start: + m_out.startSeq(); + break; + case Status::Continue: + m_out.continueSeq(); + break; + case Status::Stop: + m_out.stopSeq(); + break; + case Status::Reset: + m_out.reset(); + break; + case Status::SysExTerm: + case Status::TimingClock: + case Status::ActiveSensing: + default: break; + } + break; + } + default: break; + } + + return it; + } +}; + +class MIDIEncoder : public IMIDIReader +{ + friend class SongConverter; + std::vector m_result; + uint8_t m_status = 0; + + void _sendMessage(const uint8_t* data, size_t len) + { + if (data[0] == m_status) + { + for (size_t i=1 ; i= 0x4000) + { + ptr = &send[0]; + send[0] = 0x80 | ((val / 0x4000) & 0x7f); + send[1] = 0x80; + val &= 0x3fff; + } + + if (val >= 0x80) + { + if (!ptr) + ptr = &send[1]; + send[1] = 0x80 | ((val / 0x80) & 0x7f); + } + + if (!ptr) + ptr = &send[2]; + send[2] = val & 0x7f; + + size_t len = 3 - (ptr - send); + for (size_t i=0 ; i(data)[i]); + cmd = uint8_t(Status::SysExTerm); + _sendMessage(&cmd, 1); + } + + void timeCodeQuarterFrame(uint8_t message, uint8_t value) + { + uint8_t cmd[2] = {uint8_t(int(Status::TimecodeQuarterFrame)), + uint8_t((message & 0x7 << 4) | (value & 0xf))}; + _sendMessage(cmd, 2); + } + + void songPositionPointer(uint16_t pointer) + { + uint8_t cmd[3] = {uint8_t(int(Status::SongPositionPointer)), + uint8_t((pointer % 128) & 0x7f), uint8_t((pointer / 128) & 0x7f)}; + _sendMessage(cmd, 3); + } + + void songSelect(uint8_t song) + { + uint8_t cmd[2] = {uint8_t(int(Status::TimecodeQuarterFrame)), + uint8_t(song & 0x7f)}; + _sendMessage(cmd, 2); + } + + void tuneRequest() + { + uint8_t cmd = uint8_t(Status::TuneRequest); + _sendMessage(&cmd, 1); + } + + + void startSeq() + { + uint8_t cmd = uint8_t(Status::Start); + _sendMessage(&cmd, 1); + } + + void continueSeq() + { + uint8_t cmd = uint8_t(Status::Continue); + _sendMessage(&cmd, 1); + } + + void stopSeq() + { + uint8_t cmd = uint8_t(Status::Stop); + _sendMessage(&cmd, 1); + } + + + void reset() + { + uint8_t cmd = uint8_t(Status::Reset); + _sendMessage(&cmd, 1); + } + + const std::vector& getResult() const {return m_result;} + std::vector& getResult() {return m_result;} +}; + +static uint32_t DecodeRLE(const unsigned char*& data) +{ + uint32_t ret = 0; + + while (true) + { + uint32_t thisPart = *data & 0x7f; + if (*data & 0x80) + { + ++data; + thisPart = thisPart * 256 + *data; + if (thisPart == 0) + return -1; + } + + if (thisPart == 32767) + { + ret += 32767; + data += 2; + continue; + } + + ret += thisPart; + data += 1; + break; + } + + return ret; +} + +static int32_t DecodeContinuousRLE(const unsigned char*& data) +{ + int32_t ret = int32_t(DecodeRLE(data)); + if (ret >= 16384) + return ret - 32767; + return ret; +} + +static uint32_t DecodeTimeRLE(const unsigned char*& data) +{ + uint32_t ret = 0; + + while (true) + { + uint16_t thisPart = SBig(*reinterpret_cast(data)); + if (thisPart == 0xffff) + { + ret += 65535; + data += 4; + continue; + } + + ret += thisPart; + data += 2; + break; + } + + return ret; +} + +std::vector SongConverter::SongToMIDI(const unsigned char* data, Target& targetOut) +{ + std::vector ret = {'M', 'T', 'h', 'd'}; + uint32_t six32 = SBig(uint32_t(6)); + for (int i=0 ; i<4 ; ++i) + ret.push_back(reinterpret_cast(&six32)[i]); + + ret.push_back(0); + ret.push_back(1); + + SongState song; + song.initialize(data); + + size_t trkCount = 1; + for (std::experimental::optional& trk : song.m_tracks) + if (trk) + ++trkCount; + + uint16_t trkCount16 = SBig(uint16_t(trkCount)); + ret.push_back(reinterpret_cast(&trkCount16)[0]); + ret.push_back(reinterpret_cast(&trkCount16)[1]); + + uint16_t tickDiv16 = SBig(uint16_t(384)); + ret.push_back(reinterpret_cast(&tickDiv16)[0]); + ret.push_back(reinterpret_cast(&tickDiv16)[1]); + + /* Intermediate event */ + struct Event + { + bool endEvent = false; + bool controlChange = false; + uint8_t channel; + uint8_t noteOrCtrl; + uint8_t velOrVal; + uint16_t length; + + bool isPitchBend = false; + int16_t pitchBend; + + Event(int16_t pBend) : isPitchBend(true), pitchBend(pBend) {} + + Event(bool ctrlCh, uint8_t chan, uint8_t note, uint8_t vel, uint16_t len) + : controlChange(ctrlCh), channel(chan), noteOrCtrl(note), velOrVal(vel), length(len) {} + }; + + /* Write tempo track */ + { + MIDIEncoder encoder; + + /* Initial tempo */ + encoder._sendContinuedValue(0); + encoder.getResult().push_back(0xff); + encoder.getResult().push_back(0x51); + encoder.getResult().push_back(3); + + uint32_t tempo24 = SBig(60000000 / song.m_tempo); + for (int i=1 ; i<4 ; ++i) + encoder.getResult().push_back(reinterpret_cast(&tempo24)[i]); + + /* Write out tempo changes */ + int lastTick = 0; + while (song.m_tempoPtr && song.m_tempoPtr->m_tick != 0xffffffff) + { + SongState::TempoChange change = *song.m_tempoPtr; + if (song.m_bigEndian) + change.swapBig(); + + encoder._sendContinuedValue(change.m_tick - lastTick); + lastTick = change.m_tick; + encoder.getResult().push_back(0xff); + encoder.getResult().push_back(0x51); + encoder.getResult().push_back(3); + + uint32_t tempo24 = SBig(60000000 / change.m_tempo); + for (int i=1 ; i<4 ; ++i) + encoder.getResult().push_back(reinterpret_cast(&tempo24)[i]); + + ++song.m_tempoPtr; + } + + encoder.getResult().push_back(0); + encoder.getResult().push_back(0xff); + encoder.getResult().push_back(0x2f); + encoder.getResult().push_back(0); + + ret.push_back('M'); + ret.push_back('T'); + ret.push_back('r'); + ret.push_back('k'); + uint32_t trkSz = SBig(uint32_t(encoder.getResult().size())); + for (int i=0 ; i<4 ; ++i) + ret.push_back(reinterpret_cast(&trkSz)[i]); + ret.insert(ret.cend(), encoder.getResult().begin(), encoder.getResult().end()); + } + + /* Iterate each SNG track into type-1 MIDI track */ + for (std::experimental::optional& trk : song.m_tracks) + { + if (trk) + { + std::multimap events; + + /* Iterate all regions */ + while (trk->m_nextRegion->indexValid()) + { + trk->advanceRegion(nullptr); + uint32_t regStart = song.m_bigEndian ? SBig(trk->m_curRegion->m_startTick) : trk->m_curRegion->m_startTick; + + /* Update continuous pitch data */ + if (trk->m_pitchWheelData) + { + while (true) + { + /* See if there's an upcoming pitch change in this interval */ + const unsigned char* ptr = trk->m_pitchWheelData; + uint32_t deltaTicks = DecodeRLE(ptr); + if (deltaTicks != 0xffffffff) + { + int32_t nextTick = trk->m_lastPitchTick + deltaTicks; + int32_t pitchDelta = DecodeContinuousRLE(ptr); + trk->m_lastPitchVal += pitchDelta; + trk->m_pitchWheelData = ptr; + trk->m_lastPitchTick = nextTick; + events.emplace(regStart + nextTick, Event{clamp(0, trk->m_lastPitchVal / 2 + 0x2000, 0x4000)}); + } + else + break; + } + } + + /* Update continuous modulation data */ + if (trk->m_modWheelData) + { + while (true) + { + /* See if there's an upcoming modulation change in this interval */ + const unsigned char* ptr = trk->m_modWheelData; + uint32_t deltaTicks = DecodeRLE(ptr); + if (deltaTicks != 0xffffffff) + { + int32_t nextTick = trk->m_lastModTick + deltaTicks; + int32_t modDelta = DecodeContinuousRLE(ptr); + trk->m_lastModVal += modDelta; + trk->m_modWheelData = ptr; + trk->m_lastModTick = nextTick; + events.emplace(regStart + nextTick, Event{true, trk->m_midiChan, 1, clamp(0, trk->m_lastModVal * 128 / 16384, 127), 0}); + } + else + break; + } + } + + /* Loop through as many commands as we can for this time period */ + if (song.m_header.m_trackIdxOff == 0x18 || song.m_header.m_trackIdxOff == 0x58) + { + /* GameCube */ + while (true) + { + /* Load next command */ + if (*reinterpret_cast(trk->m_data) == 0xffff) + { + /* End of channel */ + trk->m_data = nullptr; + break; + } + else if (trk->m_data[0] & 0x80) + { + /* Control change */ + uint8_t val = trk->m_data[0] & 0x7f; + uint8_t ctrl = trk->m_data[1] & 0x7f; + events.emplace(regStart + trk->m_eventWaitCountdown, Event{true, trk->m_midiChan, ctrl, val, 0}); + trk->m_data += 2; + } + else + { + /* Note */ + uint8_t note = trk->m_data[0] & 0x7f; + uint8_t vel = trk->m_data[1] & 0x7f; + uint16_t length = (song.m_bigEndian ? SBig(*reinterpret_cast(trk->m_data + 2)) : + *reinterpret_cast(trk->m_data + 2)); + if (length) + events.emplace(regStart + trk->m_eventWaitCountdown, Event{false, trk->m_midiChan, note, vel, length}); + trk->m_data += 4; + } + + /* Set next delta-time */ + trk->m_eventWaitCountdown += int32_t(DecodeTimeRLE(trk->m_data)); + } + } + else + { + /* N64 */ + while (true) + { + /* Load next command */ + if (*reinterpret_cast(trk->m_data) == 0xffff0000) + { + /* End of channel */ + trk->m_data = nullptr; + break; + } + else if (trk->m_data[0] & 0x80) + { + /* Control change */ + uint8_t val = trk->m_data[0] & 0x7f; + uint8_t ctrl = trk->m_data[1] & 0x7f; + events.emplace(regStart + trk->m_eventWaitCountdown, Event{true, trk->m_midiChan, ctrl, val, 0}); + trk->m_data += 2; + } + else + { + if ((trk->m_data[2] & 0x80) != 0x80) + { + /* Note */ + uint16_t length = (song.m_bigEndian ? SBig(*reinterpret_cast(trk->m_data)) : + *reinterpret_cast(trk->m_data)); + uint8_t note = trk->m_data[2] & 0x7f; + uint8_t vel = trk->m_data[3] & 0x7f; + if (length) + events.emplace(regStart + trk->m_eventWaitCountdown, Event{false, trk->m_midiChan, note, vel, length}); + } + trk->m_data += 4; + } + + /* Set next delta-time */ + int32_t absTick = (song.m_bigEndian ? SBig(*reinterpret_cast(trk->m_data)) : + *reinterpret_cast(trk->m_data)); + trk->m_eventWaitCountdown += absTick - trk->m_lastN64EventTick; + trk->m_lastN64EventTick = absTick; + trk->m_data += 4; + } + } + } + + /* Resolve key-off events */ + std::multimap offEvents; + for (auto& pair : events) + { + if (!pair.second.controlChange) + { + auto it = offEvents.emplace(pair.first + pair.second.length, pair.second); + it->second.endEvent = true; + } + } + + /* Merge key-off events */ + events.insert(offEvents.begin(), offEvents.end()); + + /* Emit MIDI events */ + MIDIEncoder encoder; + int lastTime = 0; + for (auto& pair : events) + { + encoder._sendContinuedValue(pair.first - lastTime); + lastTime = pair.first; + + if (pair.second.controlChange) + { + encoder.controlChange(pair.second.channel, pair.second.noteOrCtrl, pair.second.velOrVal); + } + else if (pair.second.isPitchBend) + { + encoder.pitchBend(trk->m_midiChan, pair.second.pitchBend); + } + else + { + if (pair.second.endEvent) + encoder.noteOff(pair.second.channel, pair.second.noteOrCtrl, pair.second.velOrVal); + else + encoder.noteOn(pair.second.channel, pair.second.noteOrCtrl, pair.second.velOrVal); + } + } + + encoder.getResult().push_back(0); + encoder.getResult().push_back(0xff); + encoder.getResult().push_back(0x2f); + encoder.getResult().push_back(0); + + /* Write out */ + ret.push_back('M'); + ret.push_back('T'); + ret.push_back('r'); + ret.push_back('k'); + uint32_t trkSz = SBig(uint32_t(encoder.getResult().size())); + for (int i=0 ; i<4 ; ++i) + ret.push_back(reinterpret_cast(&trkSz)[i]); + ret.insert(ret.cend(), encoder.getResult().begin(), encoder.getResult().end()); + } + } + + return ret; +} + +std::vector SongConverter::MIDIToSong(const unsigned char* data, Target target) +{ + +} + +} diff --git a/lib/SongState.cpp b/lib/SongState.cpp index b9f5284..8e720fb 100644 --- a/lib/SongState.cpp +++ b/lib/SongState.cpp @@ -101,16 +101,19 @@ SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion* m_remNoteLengths[i] = INT_MIN; } -void SongState::Track::setRegion(Sequencer& seq, const TrackRegion* region) +void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region) { m_curRegion = region; - uint32_t regionIdx = SBig(m_curRegion->m_regionIndex); + uint32_t regionIdx = (m_parent.m_bigEndian ? SBig(m_curRegion->m_regionIndex) : + m_curRegion->m_regionIndex); m_nextRegion = &m_curRegion[1]; - m_data = m_parent.m_songData + SBig(m_parent.m_regionIdx[regionIdx]); + m_data = m_parent.m_songData + (m_parent.m_bigEndian ? SBig(m_parent.m_regionIdx[regionIdx]) : + m_parent.m_regionIdx[regionIdx]); Header header = *reinterpret_cast(m_data); - header.swapBig(); + if (m_parent.m_bigEndian) + header.swapBig(); m_data += 12; if (header.m_pitchOff) @@ -121,31 +124,37 @@ void SongState::Track::setRegion(Sequencer& seq, const TrackRegion* region) m_eventWaitCountdown = 0; m_lastPitchTick = m_parent.m_curTick; m_lastPitchVal = 0; - seq.setPitchWheel(m_midiChan, clamp(-1.f, m_lastPitchVal / 32768.f, 1.f)); m_lastModTick = m_parent.m_curTick; m_lastModVal = 0; - seq.setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127)); + if (seq) + { + seq->setPitchWheel(m_midiChan, clamp(-1.f, m_lastPitchVal / 32768.f, 1.f)); + seq->setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127)); + } if (m_parent.m_header.m_trackIdxOff == 0x18 || m_parent.m_header.m_trackIdxOff == 0x58) m_eventWaitCountdown = int32_t(DecodeTimeRLE(m_data)); else { - int32_t absTick = SBig(*reinterpret_cast(m_data)); + int32_t absTick = (m_parent.m_bigEndian ? SBig(*reinterpret_cast(m_data)) : + *reinterpret_cast(m_data)); m_eventWaitCountdown = absTick; m_lastN64EventTick = absTick; m_data += 4; } } -void SongState::Track::advanceRegion(Sequencer& seq) +void SongState::Track::advanceRegion(Sequencer* seq) { setRegion(seq, m_nextRegion); } void SongState::initialize(const unsigned char* ptr) { + m_bigEndian = ptr[0] == 0; m_songData = ptr; m_header = *reinterpret_cast(ptr); - m_header.swapBig(); + if (m_bigEndian) + m_header.swapBig(); const uint32_t* trackIdx = reinterpret_cast(ptr + m_header.m_trackIdxOff); m_regionIdx = reinterpret_cast(ptr + m_header.m_regionIdxOff); const uint8_t* chanMap = reinterpret_cast(ptr + m_header.m_chanMapOff); @@ -155,7 +164,7 @@ void SongState::initialize(const unsigned char* ptr) { if (trackIdx[i]) { - const TrackRegion* region = reinterpret_cast(ptr + SBig(trackIdx[i])); + const TrackRegion* region = reinterpret_cast(ptr + (m_bigEndian ? SBig(trackIdx[i]) : trackIdx[i])); m_tracks[i].emplace(*this, chanMap[i], region); } else @@ -180,9 +189,10 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks) /* Advance region if needed */ while (m_nextRegion->indexValid()) { - uint32_t nextRegTick = SBig(m_nextRegion->m_startTick); + uint32_t nextRegTick = (m_parent.m_bigEndian ? SBig(m_nextRegion->m_startTick) : + m_nextRegion->m_startTick); if (endTick > nextRegTick) - advanceRegion(seq); + advanceRegion(&seq); else break; } @@ -305,7 +315,8 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks) /* Note */ uint8_t note = m_data[0] & 0x7f; uint8_t vel = m_data[1] & 0x7f; - uint16_t length = SBig(*reinterpret_cast(m_data + 2)); + uint16_t length = (m_parent.m_bigEndian ? SBig(*reinterpret_cast(m_data + 2)) : + *reinterpret_cast(m_data + 2)); seq.keyOn(m_midiChan, note, vel); m_remNoteLengths[note] = length; m_data += 4; @@ -349,7 +360,8 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks) if ((m_data[2] & 0x80) != 0x80) { /* Note */ - uint16_t length = SBig(*reinterpret_cast(m_data)); + uint16_t length = (m_parent.m_bigEndian ? SBig(*reinterpret_cast(m_data)) : + *reinterpret_cast(m_data)); uint8_t note = m_data[2] & 0x7f; uint8_t vel = m_data[3] & 0x7f; seq.keyOn(m_midiChan, note, vel); @@ -359,7 +371,8 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks) } /* Set next delta-time */ - int32_t absTick = SBig(*reinterpret_cast(m_data)); + int32_t absTick = (m_parent.m_bigEndian ? SBig(*reinterpret_cast(m_data)) : + *reinterpret_cast(m_data)); m_eventWaitCountdown += absTick - m_lastN64EventTick; m_lastN64EventTick = absTick; m_data += 4; @@ -391,7 +404,8 @@ bool SongState::advance(Sequencer& seq, double dt) if (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff) { TempoChange change = *m_tempoPtr; - change.swapBig(); + if (m_bigEndian) + change.swapBig(); if (m_curTick + remTicks > change.m_tick) remTicks = change.m_tick - m_curTick;