Initial amuseconv implementation with SNG extraction

This commit is contained in:
Jack Andersen 2016-06-20 20:40:13 -10:00
parent 3bc47baa1d
commit a0bb35433a
13 changed files with 1584 additions and 32 deletions

View File

@ -10,12 +10,14 @@ set(SOURCES
lib/AudioGroupPool.cpp lib/AudioGroupPool.cpp
lib/AudioGroupProject.cpp lib/AudioGroupProject.cpp
lib/AudioGroupSampleDirectory.cpp lib/AudioGroupSampleDirectory.cpp
lib/DirectoryEnumerator.cpp
lib/Emitter.cpp lib/Emitter.cpp
lib/Engine.cpp lib/Engine.cpp
lib/Envelope.cpp lib/Envelope.cpp
lib/Listener.cpp lib/Listener.cpp
lib/Sequencer.cpp lib/Sequencer.cpp
lib/SoundMacroState.cpp lib/SoundMacroState.cpp
lib/SongConverter.cpp
lib/SongState.cpp lib/SongState.cpp
lib/Voice.cpp lib/Voice.cpp
lib/VolumeLUT.cpp lib/VolumeLUT.cpp
@ -35,6 +37,7 @@ set(HEADERS
include/amuse/AudioGroupPool.hpp include/amuse/AudioGroupPool.hpp
include/amuse/AudioGroupProject.hpp include/amuse/AudioGroupProject.hpp
include/amuse/AudioGroupSampleDirectory.hpp include/amuse/AudioGroupSampleDirectory.hpp
include/amuse/DirectoryEnumerator.hpp
include/amuse/Emitter.hpp include/amuse/Emitter.hpp
include/amuse/Engine.hpp include/amuse/Engine.hpp
include/amuse/Entity.hpp include/amuse/Entity.hpp
@ -42,6 +45,7 @@ set(HEADERS
include/amuse/Listener.hpp include/amuse/Listener.hpp
include/amuse/Sequencer.hpp include/amuse/Sequencer.hpp
include/amuse/SoundMacroState.hpp include/amuse/SoundMacroState.hpp
include/amuse/SongConverter.hpp
include/amuse/SongState.hpp include/amuse/SongState.hpp
include/amuse/Voice.hpp include/amuse/Voice.hpp
include/amuse/Submix.hpp include/amuse/Submix.hpp
@ -81,7 +85,13 @@ if(TARGET boo)
# VST Target # VST Target
add_subdirectory(VST) add_subdirectory(VST)
# Multi-platform CLI tool # Multi-platform CLI tools
add_executable(amuseplay WIN32 driver/main.cpp)
# Player
add_executable(amuseplay WIN32 driver/amuseplay.cpp)
target_link_libraries(amuseplay amuse boo ${BOO_SYS_LIBS} logvisor athena-core ${ZLIB_LIBRARIES}) 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() endif()

View File

@ -385,7 +385,7 @@ void VSTEditor::addAction()
if (dotpos != std::string::npos) if (dotpos != std::string::npos)
name.assign(path.cbegin(), path.cbegin() + dotpos); name.assign(path.cbegin(), path.cbegin() + dotpos);
size_t slashpos = name.rfind(L'\\'); size_t slashpos = name.rfind(L'\\');
size_t fslashpos = name.rfind(L"/"); size_t fslashpos = name.rfind(L'/');
if (slashpos == std::string::npos) if (slashpos == std::string::npos)
slashpos = fslashpos; slashpos = fslashpos;
else if (fslashpos != std::string::npos) else if (fslashpos != std::string::npos)

192
driver/amuseconv.cpp Normal file
View File

@ -0,0 +1,192 @@
#include "amuse/amuse.hpp"
#include "athena/FileReader.hpp"
#include "athena/DNAYaml.hpp"
#include "logvisor/logvisor.hpp"
#include <stdio.h>
#include <string.h>
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<uint8_t> 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 <in-file> <out-file> [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;
}

View File

@ -11,6 +11,7 @@
#ifndef _MSC_VER #ifndef _MSC_VER
#include <strings.h> #include <strings.h>
#include <sys/stat.h>
#endif #endif
namespace amuse namespace amuse
@ -30,12 +31,18 @@ namespace amuse
# ifndef _S # ifndef _S
# define _S(val) L ## val # define _S(val) L ## val
# endif # 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 #else
using SystemString = std::string; using SystemString = std::string;
using SystemChar = char; using SystemChar = char;
# ifndef _S # ifndef _S
# define _S(val) val # define _S(val) val
# endif # 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 #endif
#if _WIN32 #if _WIN32

View File

@ -0,0 +1,74 @@
#ifndef __AMUSE_DIRECTORY_ENUMERATOR__
#define __AMUSE_DIRECTORY_ENUMERATOR__
#include "Common.hpp"
#include <vector>
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<Entry> 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<Entry>::const_iterator begin() const {return m_entries.cbegin();}
std::vector<Entry>::const_iterator end() const {return m_entries.cend();}
};
}
#endif // __AMUSE_DIRECTORY_ENUMERATOR__

View File

@ -0,0 +1,25 @@
#ifndef __AMUSE_SONGCONVERTER_HPP__
#define __AMUSE_SONGCONVERTER_HPP__
#include <vector>
#include <stdint.h>
namespace amuse
{
class SongConverter
{
public:
enum class Target
{
N64,
GCN,
PC
};
static std::vector<uint8_t> SongToMIDI(const unsigned char* data, Target& targetOut);
static std::vector<uint8_t> MIDIToSong(const unsigned char* data, Target target);
};
}
#endif // __AMUSE_SONGCONVERTER_HPP__

View File

@ -22,6 +22,7 @@ enum class SongPlayState
class SongState class SongState
{ {
friend class Voice; friend class Voice;
friend class SongConverter;
/** Song header */ /** Song header */
struct Header struct Header
@ -55,6 +56,7 @@ class SongState
}; };
const unsigned char* m_songData = nullptr; /**< Base pointer to active song */ 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 */ /** State of a single track within arrangement */
struct Track 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) */ 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); Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions);
void setRegion(Sequencer& seq, const TrackRegion* region); void setRegion(Sequencer* seq, const TrackRegion* region);
void advanceRegion(Sequencer& seq); void advanceRegion(Sequencer* seq);
bool advance(Sequencer& seq, int32_t ticks); bool advance(Sequencer& seq, int32_t ticks);
}; };
std::array<std::experimental::optional<Track>, 64> m_tracks; std::array<std::experimental::optional<Track>, 64> m_tracks;

View File

@ -16,6 +16,8 @@
#include "Listener.hpp" #include "Listener.hpp"
#include "Sequencer.hpp" #include "Sequencer.hpp"
#include "SoundMacroState.hpp" #include "SoundMacroState.hpp"
#include "SongConverter.hpp"
#include "SongState.hpp"
#include "Submix.hpp" #include "Submix.hpp"
#include "Voice.hpp" #include "Voice.hpp"

View File

@ -725,6 +725,41 @@ static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadRS1PC(F
return ret; return ret;
} }
static std::vector<std::pair<SystemString, ContainerRegistry::SongData>> LoadRS1PCSongs(FILE* fp)
{
std::vector<std::pair<SystemString, ContainerRegistry::SongData>> 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<RS1FSTEntry[]> entries(new RS1FSTEntry[elemCount]);
fread(entries.get(), fstSz, 1, fp);
for (uint32_t i=0 ; i<elemCount ; ++i)
{
RS1FSTEntry& entry = entries[i];
if (strstr(entry.name, "SNG"))
{
std::unique_ptr<uint8_t[]> 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) static bool ValidateRS1N64(FILE* fp)
{ {
size_t endPos = FileLength(fp); size_t endPos = FileLength(fp);
@ -1051,6 +1086,41 @@ static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadBFNPC(F
return ret; return ret;
} }
static std::vector<std::pair<SystemString, ContainerRegistry::SongData>> LoadBFNPCSongs(FILE* fp)
{
std::vector<std::pair<SystemString, ContainerRegistry::SongData>> 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<RS1FSTEntry[]> entries(new RS1FSTEntry[elemCount]);
fread(entries.get(), fstSz, 1, fp);
for (uint32_t i=0 ; i<elemCount ; ++i)
{
RS1FSTEntry& entry = entries[i];
if (!strncmp(entry.name, "s_", 2))
{
std::unique_ptr<uint8_t[]> 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) static bool ValidateBFNN64(FILE* fp)
{ {
size_t endPos = FileLength(fp); size_t endPos = FileLength(fp);
@ -1216,6 +1286,61 @@ static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadBFNN64(
return ret; return ret;
} }
static std::vector<std::pair<SystemString, ContainerRegistry::SongData>> LoadBFNN64Songs(FILE* fp)
{
std::vector<std::pair<SystemString, ContainerRegistry::SongData>> ret;
size_t endPos = FileLength(fp);
std::unique_ptr<uint8_t[]> 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<const uint8_t*>(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<const uint32_t*>(dataSeg));
dataSeg += 4;
size_t fstOff = SBig(*reinterpret_cast<const uint32_t*>(dataSeg));
if (endPos <= size_t(dataSeg - data.get()) + fstOff || endPos <= size_t(dataSeg - data.get()) + fstEnd)
return ret;
const RS1FSTEntry* entry = reinterpret_cast<const RS1FSTEntry*>(dataSeg + fstOff);
const RS1FSTEntry* lastEnt = reinterpret_cast<const RS1FSTEntry*>(dataSeg + fstEnd);
for (; entry != lastEnt ; ++entry)
{
RS1FSTEntry ent = *entry;
ent.swapBig();
if (!strncmp(ent.name, "s_", 2))
{
std::unique_ptr<uint8_t[]> 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 struct RS2FSTEntry
{ {
uint64_t offset; uint64_t offset;
@ -1902,6 +2027,13 @@ ContainerRegistry::LoadSongs(const SystemChar* path)
return ret; return ret;
} }
if (ValidateRS1PC(fp))
{
auto ret = LoadRS1PCSongs(fp);
fclose(fp);
return ret;
}
if (ValidateRS1N64(fp)) if (ValidateRS1N64(fp))
{ {
auto ret = LoadRS1N64Songs(fp); auto ret = LoadRS1N64Songs(fp);
@ -1909,28 +2041,19 @@ ContainerRegistry::LoadSongs(const SystemChar* path)
return ret; return ret;
} }
#if 0 if (ValidateBFNPC(fp))
if (ValidateRS1PCSongs(fp))
{
auto ret = LoadRS1PCSongs(fp);
fclose(fp);
return ret;
}
if (ValidateBFNPCSongs(fp))
{ {
auto ret = LoadBFNPCSongs(fp); auto ret = LoadBFNPCSongs(fp);
fclose(fp); fclose(fp);
return ret; return ret;
} }
if (ValidateBFNN64Songs(fp)) if (ValidateBFNN64(fp))
{ {
auto ret = LoadBFNN64Songs(fp); auto ret = LoadBFNN64Songs(fp);
fclose(fp); fclose(fp);
return ret; return ret;
} }
#endif
if (ValidateRS2(fp)) if (ValidateRS2(fp))
{ {

277
lib/DirectoryEnumerator.cpp Normal file
View File

@ -0,0 +1,277 @@
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/stat.h>
#include <dirent.h>
#endif
#include <map>
#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<SystemString, Entry, CaseInsensitiveCompare> 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<size_t, Entry> 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<SystemString, Entry, CaseInsensitiveCompare> 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<SystemString, Entry, CaseInsensitiveCompare> 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<size_t, Entry> 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<SystemString, Entry, CaseInsensitiveCompare> 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
}
}

826
lib/SongConverter.cpp Normal file
View File

@ -0,0 +1,826 @@
#include "amuse/SongConverter.hpp"
#include "amuse/SongState.hpp"
#include "amuse/Common.hpp"
#include <stdlib.h>
#include <map>
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<uint8_t>::const_iterator& it,
std::vector<uint8_t>::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<uint8_t>::const_iterator
receiveBytes(std::vector<uint8_t>::const_iterator begin,
std::vector<uint8_t>::const_iterator end)
{
std::vector<uint8_t>::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<uint8_t> 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<len ; ++i)
m_result.push_back(data[i]);
}
else
{
if (data[0] & 0x80)
m_status = data[0];
for (size_t i=0 ; i<len ; ++i)
m_result.push_back(data[i]);
}
}
void _sendContinuedValue(uint32_t val)
{
uint32_t send[3] = {};
uint32_t* ptr = nullptr;
if (val >= 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<len ; ++i)
m_result.push_back(ptr[i]);
}
public:
void noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
{
uint8_t cmd[3] = {uint8_t(int(Status::NoteOff) | (chan & 0xf)),
uint8_t(key & 0x7f), uint8_t(velocity & 0x7f)};
_sendMessage(cmd, 3);
}
void noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
{
uint8_t cmd[3] = {uint8_t(int(Status::NoteOn) | (chan & 0xf)),
uint8_t(key & 0x7f), uint8_t(velocity & 0x7f)};
_sendMessage(cmd, 3);
}
void notePressure(uint8_t chan, uint8_t key, uint8_t pressure)
{
uint8_t cmd[3] = {uint8_t(int(Status::NotePressure) | (chan & 0xf)),
uint8_t(key & 0x7f), uint8_t(pressure & 0x7f)};
_sendMessage(cmd, 3);
}
void controlChange(uint8_t chan, uint8_t control, uint8_t value)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
uint8_t(control & 0x7f), uint8_t(value & 0x7f)};
_sendMessage(cmd, 3);
}
void programChange(uint8_t chan, uint8_t program)
{
uint8_t cmd[2] = {uint8_t(int(Status::ProgramChange) | (chan & 0xf)),
uint8_t(program & 0x7f)};
_sendMessage(cmd, 2);
}
void channelPressure(uint8_t chan, uint8_t pressure)
{
uint8_t cmd[2] = {uint8_t(int(Status::ChannelPressure) | (chan & 0xf)),
uint8_t(pressure & 0x7f)};
_sendMessage(cmd, 2);
}
void pitchBend(uint8_t chan, int16_t pitch)
{
uint8_t cmd[3] = {uint8_t(int(Status::PitchBend) | (chan & 0xf)),
uint8_t((pitch % 128) & 0x7f), uint8_t((pitch / 128) & 0x7f)};
_sendMessage(cmd, 3);
}
void allSoundOff(uint8_t chan)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
120, 0};
_sendMessage(cmd, 3);
}
void resetAllControllers(uint8_t chan)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
121, 0};
_sendMessage(cmd, 3);
}
void localControl(uint8_t chan, bool on)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
122, uint8_t(on ? 127 : 0)};
_sendMessage(cmd, 3);
}
void allNotesOff(uint8_t chan)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
123, 0};
_sendMessage(cmd, 3);
}
void omniMode(uint8_t chan, bool on)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
uint8_t(on ? 125 : 124), 0};
_sendMessage(cmd, 3);
}
void polyMode(uint8_t chan, bool on)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
uint8_t(on ? 127 : 126), 0};
_sendMessage(cmd, 3);
}
void sysex(const void* data, size_t len)
{
uint8_t cmd = uint8_t(Status::SysEx);
_sendMessage(&cmd, 1);
_sendContinuedValue(len);
for (size_t i=0 ; i<len ; ++i)
m_result.push_back(reinterpret_cast<const uint8_t*>(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<uint8_t>& getResult() const {return m_result;}
std::vector<uint8_t>& 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<const uint16_t*>(data));
if (thisPart == 0xffff)
{
ret += 65535;
data += 4;
continue;
}
ret += thisPart;
data += 2;
break;
}
return ret;
}
std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, Target& targetOut)
{
std::vector<uint8_t> ret = {'M', 'T', 'h', 'd'};
uint32_t six32 = SBig(uint32_t(6));
for (int i=0 ; i<4 ; ++i)
ret.push_back(reinterpret_cast<uint8_t*>(&six32)[i]);
ret.push_back(0);
ret.push_back(1);
SongState song;
song.initialize(data);
size_t trkCount = 1;
for (std::experimental::optional<SongState::Track>& trk : song.m_tracks)
if (trk)
++trkCount;
uint16_t trkCount16 = SBig(uint16_t(trkCount));
ret.push_back(reinterpret_cast<uint8_t*>(&trkCount16)[0]);
ret.push_back(reinterpret_cast<uint8_t*>(&trkCount16)[1]);
uint16_t tickDiv16 = SBig(uint16_t(384));
ret.push_back(reinterpret_cast<uint8_t*>(&tickDiv16)[0]);
ret.push_back(reinterpret_cast<uint8_t*>(&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<uint8_t*>(&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<uint8_t*>(&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<uint8_t*>(&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<SongState::Track>& trk : song.m_tracks)
{
if (trk)
{
std::multimap<int, Event> 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<const uint16_t*>(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<const uint16_t*>(trk->m_data + 2)) :
*reinterpret_cast<const uint16_t*>(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<const uint32_t*>(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<const uint16_t*>(trk->m_data)) :
*reinterpret_cast<const uint16_t*>(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<const int32_t*>(trk->m_data)) :
*reinterpret_cast<const int32_t*>(trk->m_data));
trk->m_eventWaitCountdown += absTick - trk->m_lastN64EventTick;
trk->m_lastN64EventTick = absTick;
trk->m_data += 4;
}
}
}
/* Resolve key-off events */
std::multimap<int, Event> 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<uint8_t*>(&trkSz)[i]);
ret.insert(ret.cend(), encoder.getResult().begin(), encoder.getResult().end());
}
}
return ret;
}
std::vector<uint8_t> SongConverter::MIDIToSong(const unsigned char* data, Target target)
{
}
}

View File

@ -101,15 +101,18 @@ SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion*
m_remNoteLengths[i] = INT_MIN; 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; 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_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<const Header*>(m_data); Header header = *reinterpret_cast<const Header*>(m_data);
if (m_parent.m_bigEndian)
header.swapBig(); header.swapBig();
m_data += 12; m_data += 12;
@ -121,30 +124,36 @@ void SongState::Track::setRegion(Sequencer& seq, const TrackRegion* region)
m_eventWaitCountdown = 0; m_eventWaitCountdown = 0;
m_lastPitchTick = m_parent.m_curTick; m_lastPitchTick = m_parent.m_curTick;
m_lastPitchVal = 0; m_lastPitchVal = 0;
seq.setPitchWheel(m_midiChan, clamp(-1.f, m_lastPitchVal / 32768.f, 1.f));
m_lastModTick = m_parent.m_curTick; m_lastModTick = m_parent.m_curTick;
m_lastModVal = 0; 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) if (m_parent.m_header.m_trackIdxOff == 0x18 || m_parent.m_header.m_trackIdxOff == 0x58)
m_eventWaitCountdown = int32_t(DecodeTimeRLE(m_data)); m_eventWaitCountdown = int32_t(DecodeTimeRLE(m_data));
else else
{ {
int32_t absTick = SBig(*reinterpret_cast<const int32_t*>(m_data)); int32_t absTick = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data)) :
*reinterpret_cast<const int32_t*>(m_data));
m_eventWaitCountdown = absTick; m_eventWaitCountdown = absTick;
m_lastN64EventTick = absTick; m_lastN64EventTick = absTick;
m_data += 4; m_data += 4;
} }
} }
void SongState::Track::advanceRegion(Sequencer& seq) void SongState::Track::advanceRegion(Sequencer* seq)
{ {
setRegion(seq, m_nextRegion); setRegion(seq, m_nextRegion);
} }
void SongState::initialize(const unsigned char* ptr) void SongState::initialize(const unsigned char* ptr)
{ {
m_bigEndian = ptr[0] == 0;
m_songData = ptr; m_songData = ptr;
m_header = *reinterpret_cast<const Header*>(ptr); m_header = *reinterpret_cast<const Header*>(ptr);
if (m_bigEndian)
m_header.swapBig(); m_header.swapBig();
const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_trackIdxOff); const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_trackIdxOff);
m_regionIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_regionIdxOff); m_regionIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_regionIdxOff);
@ -155,7 +164,7 @@ void SongState::initialize(const unsigned char* ptr)
{ {
if (trackIdx[i]) if (trackIdx[i])
{ {
const TrackRegion* region = reinterpret_cast<const TrackRegion*>(ptr + SBig(trackIdx[i])); const TrackRegion* region = reinterpret_cast<const TrackRegion*>(ptr + (m_bigEndian ? SBig(trackIdx[i]) : trackIdx[i]));
m_tracks[i].emplace(*this, chanMap[i], region); m_tracks[i].emplace(*this, chanMap[i], region);
} }
else else
@ -180,9 +189,10 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
/* Advance region if needed */ /* Advance region if needed */
while (m_nextRegion->indexValid()) 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) if (endTick > nextRegTick)
advanceRegion(seq); advanceRegion(&seq);
else else
break; break;
} }
@ -305,7 +315,8 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
/* Note */ /* Note */
uint8_t note = m_data[0] & 0x7f; uint8_t note = m_data[0] & 0x7f;
uint8_t vel = m_data[1] & 0x7f; uint8_t vel = m_data[1] & 0x7f;
uint16_t length = SBig(*reinterpret_cast<const uint16_t*>(m_data + 2)); uint16_t length = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(m_data + 2)) :
*reinterpret_cast<const uint16_t*>(m_data + 2));
seq.keyOn(m_midiChan, note, vel); seq.keyOn(m_midiChan, note, vel);
m_remNoteLengths[note] = length; m_remNoteLengths[note] = length;
m_data += 4; m_data += 4;
@ -349,7 +360,8 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
if ((m_data[2] & 0x80) != 0x80) if ((m_data[2] & 0x80) != 0x80)
{ {
/* Note */ /* Note */
uint16_t length = SBig(*reinterpret_cast<const uint16_t*>(m_data)); uint16_t length = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(m_data)) :
*reinterpret_cast<const uint16_t*>(m_data));
uint8_t note = m_data[2] & 0x7f; uint8_t note = m_data[2] & 0x7f;
uint8_t vel = m_data[3] & 0x7f; uint8_t vel = m_data[3] & 0x7f;
seq.keyOn(m_midiChan, note, vel); seq.keyOn(m_midiChan, note, vel);
@ -359,7 +371,8 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
} }
/* Set next delta-time */ /* Set next delta-time */
int32_t absTick = SBig(*reinterpret_cast<const int32_t*>(m_data)); int32_t absTick = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data)) :
*reinterpret_cast<const int32_t*>(m_data));
m_eventWaitCountdown += absTick - m_lastN64EventTick; m_eventWaitCountdown += absTick - m_lastN64EventTick;
m_lastN64EventTick = absTick; m_lastN64EventTick = absTick;
m_data += 4; m_data += 4;
@ -391,6 +404,7 @@ bool SongState::advance(Sequencer& seq, double dt)
if (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff) if (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff)
{ {
TempoChange change = *m_tempoPtr; TempoChange change = *m_tempoPtr;
if (m_bigEndian)
change.swapBig(); change.swapBig();
if (m_curTick + remTicks > change.m_tick) if (m_curTick + remTicks > change.m_tick)