mirror of https://github.com/AxioDL/amuse.git
Initial amuseconv implementation with SNG extraction
This commit is contained in:
parent
3bc47baa1d
commit
a0bb35433a
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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__
|
|
@ -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__
|
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue