mirror of
https://github.com/AxioDL/amuse.git
synced 2025-12-08 21:17:49 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
596bc66ce6 | ||
|
|
3a7b43a63a | ||
|
|
83a2bf0b4e | ||
|
|
695fc10b8f | ||
| feea7c2ecc | |||
|
|
1be5d6e821 | ||
|
|
2d31313594 | ||
|
|
52cba61f76 | ||
|
|
fe78a675d7 | ||
|
|
3427515960 | ||
|
|
5ad8c06b99 |
@@ -94,4 +94,8 @@ if(TARGET boo)
|
||||
# Converter
|
||||
add_executable(amuseconv driver/amuseconv.cpp)
|
||||
target_link_libraries(amuseconv amuse ${BOO_SYS_LIBS} logvisor athena-core ${ZLIB_LIBRARIES})
|
||||
|
||||
# Renderer
|
||||
add_executable(amuserender driver/amuserender.cpp)
|
||||
target_link_libraries(amuserender amuse boo ${BOO_SYS_LIBS} logvisor athena-core ${ZLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
@@ -72,8 +72,9 @@ static bool ExtractAudioGroup(const amuse::SystemString& inPath, const amuse::Sy
|
||||
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);
|
||||
int extractedVersion;
|
||||
bool isBig;
|
||||
std::vector<uint8_t> mid = amuse::SongConverter::SongToMIDI(pair.second.m_data.get(), extractedVersion, isBig);
|
||||
fwrite(mid.data(), 1, mid.size(), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
@@ -82,7 +83,7 @@ static bool ExtractAudioGroup(const amuse::SystemString& inPath, const amuse::Sy
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool BuildSNG(const amuse::SystemString& inPath, const amuse::SystemString& targetPath, amuse::SongConverter::Target target)
|
||||
static bool BuildSNG(const amuse::SystemString& inPath, const amuse::SystemString& targetPath, int version, bool big)
|
||||
{
|
||||
FILE* fp = amuse::FOpen(inPath.c_str(), _S("rb"));
|
||||
if (!fp)
|
||||
@@ -95,7 +96,7 @@ static bool BuildSNG(const amuse::SystemString& inPath, const amuse::SystemStrin
|
||||
fread(&data[0], 1, sz, fp);
|
||||
fclose(fp);
|
||||
|
||||
std::vector<uint8_t> out = amuse::SongConverter::MIDIToSong(data, target);
|
||||
std::vector<uint8_t> out = amuse::SongConverter::MIDIToSong(data, version, big);
|
||||
if (out.empty())
|
||||
return false;
|
||||
|
||||
@@ -119,13 +120,12 @@ static bool ExtractSNG(const amuse::SystemString& inPath, const amuse::SystemStr
|
||||
fread(&data[0], 1, sz, fp);
|
||||
fclose(fp);
|
||||
|
||||
amuse::SongConverter::Target target;
|
||||
std::vector<uint8_t> out = amuse::SongConverter::SongToMIDI(data.data(), target);
|
||||
int extractedVersion;
|
||||
bool isBig;
|
||||
std::vector<uint8_t> out = amuse::SongConverter::SongToMIDI(data.data(), extractedVersion, isBig);
|
||||
if (out.empty())
|
||||
return false;
|
||||
|
||||
ReportConvType(ConvType(target));
|
||||
|
||||
fp = amuse::FOpen(targetPath.c_str(), _S("wb"));
|
||||
fwrite(out.data(), 1, out.size(), fp);
|
||||
fclose(fp);
|
||||
@@ -177,7 +177,7 @@ int main(int argc, const amuse::SystemChar** argv)
|
||||
!amuse::CompareCaseInsensitive(dot, _S(".midi")))
|
||||
{
|
||||
ReportConvType(type);
|
||||
good = BuildSNG(barePath, argv[2], amuse::SongConverter::Target(type));
|
||||
good = BuildSNG(barePath, argv[2], 1, true);
|
||||
}
|
||||
else if (!amuse::CompareCaseInsensitive(dot, _S(".son")) ||
|
||||
!amuse::CompareCaseInsensitive(dot, _S(".sng")))
|
||||
|
||||
@@ -132,7 +132,7 @@ struct AppCallback : boo::IApplicationCallback
|
||||
int8_t m_lastChanProg = -1;
|
||||
|
||||
/* Control state */
|
||||
float m_volume = 0.5f;
|
||||
float m_volume = 0.8f;
|
||||
float m_modulation = 0.f;
|
||||
float m_pitchBend = 0.f;
|
||||
bool m_updateDisp = false;
|
||||
@@ -724,8 +724,27 @@ struct AppCallback : boo::IApplicationCallback
|
||||
int idx = 0;
|
||||
for (const auto& pair : songs)
|
||||
{
|
||||
const amuse::ContainerRegistry::SongData& sngData = pair.second;
|
||||
int16_t grpId = sngData.m_groupId;
|
||||
int16_t setupId = sngData.m_setupId;
|
||||
if (sngData.m_groupId == -1 && sngData.m_setupId != -1)
|
||||
{
|
||||
for (const auto& pair : allSongGroups)
|
||||
{
|
||||
for (const auto& setup : pair.second.second->m_midiSetups)
|
||||
{
|
||||
if (setup.first == sngData.m_setupId)
|
||||
{
|
||||
grpId = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (grpId != -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
amuse::Printf(_S(" %d %s (Group %d, Setup %d)\n"), idx++,
|
||||
pair.first.c_str(), pair.second.m_groupId, pair.second.m_setupId);
|
||||
pair.first.c_str(), grpId, setupId);
|
||||
}
|
||||
|
||||
int userSel = 0;
|
||||
@@ -757,7 +776,25 @@ struct AppCallback : boo::IApplicationCallback
|
||||
}
|
||||
}
|
||||
|
||||
/* Get group selection from user */
|
||||
/* Get group selection via setup search */
|
||||
if (m_groupId == -1 && m_setupId != -1)
|
||||
{
|
||||
for (const auto& pair : allSongGroups)
|
||||
{
|
||||
for (const auto& setup : pair.second.second->m_midiSetups)
|
||||
{
|
||||
if (setup.first == m_setupId)
|
||||
{
|
||||
m_groupId = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_groupId != -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get group selection via user */
|
||||
if (m_groupId != -1)
|
||||
{
|
||||
if (allSongGroups.find(m_groupId) != allSongGroups.end())
|
||||
|
||||
497
driver/amuserender.cpp
Normal file
497
driver/amuserender.cpp
Normal file
@@ -0,0 +1,497 @@
|
||||
#include "amuse/amuse.hpp"
|
||||
#include "amuse/BooBackend.hpp"
|
||||
#include "athena/FileReader.hpp"
|
||||
#include "boo/boo.hpp"
|
||||
#include "boo/audiodev/IAudioVoiceEngine.hpp"
|
||||
#include "logvisor/logvisor.hpp"
|
||||
#include "optional.hpp"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <thread>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <stdarg.h>
|
||||
|
||||
static logvisor::Module Log("amuserender");
|
||||
|
||||
#if __GNUC__
|
||||
__attribute__((__format__ (__printf__, 3, 4)))
|
||||
#endif
|
||||
static inline void SNPrintf(boo::SystemChar* str, size_t maxlen, const boo::SystemChar* format, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
#if _WIN32
|
||||
_vsnwprintf(str, maxlen, format, va);
|
||||
#else
|
||||
vsnprintf(str, maxlen, format, va);
|
||||
#endif
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
#include <DbgHelp.h>
|
||||
#pragma comment(lib, "Dbghelp.lib")
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
static void abortHandler( int signum )
|
||||
{
|
||||
unsigned int i;
|
||||
void * stack[ 100 ];
|
||||
unsigned short frames;
|
||||
SYMBOL_INFO * symbol;
|
||||
HANDLE process;
|
||||
|
||||
process = GetCurrentProcess();
|
||||
SymInitialize( process, NULL, TRUE );
|
||||
frames = CaptureStackBackTrace( 0, 100, stack, NULL );
|
||||
symbol = ( SYMBOL_INFO * )calloc( sizeof( SYMBOL_INFO ) + 256 * sizeof( char ), 1 );
|
||||
symbol->MaxNameLen = 255;
|
||||
symbol->SizeOfStruct = sizeof( SYMBOL_INFO );
|
||||
|
||||
for( i = 0; i < frames; i++ )
|
||||
{
|
||||
SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, symbol );
|
||||
|
||||
printf( "%i: %s - 0x%0llX", frames - i - 1, symbol->Name, symbol->Address );
|
||||
|
||||
DWORD dwDisplacement;
|
||||
IMAGEHLP_LINE64 line;
|
||||
SymSetOptions(SYMOPT_LOAD_LINES);
|
||||
|
||||
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
if (SymGetLineFromAddr64(process, ( DWORD64 )( stack[ i ] ), &dwDisplacement, &line))
|
||||
{
|
||||
// SymGetLineFromAddr64 returned success
|
||||
printf(" LINE %d\n", line.LineNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
free( symbol );
|
||||
|
||||
// If you caught one of the above signals, it is likely you just
|
||||
// want to quit your program right now.
|
||||
system("PAUSE");
|
||||
exit( signum );
|
||||
}
|
||||
#endif
|
||||
|
||||
/* SIGINT will gracefully break write loop */
|
||||
static bool g_BreakLoop = false;
|
||||
static void SIGINTHandler(int sig)
|
||||
{
|
||||
g_BreakLoop = true;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
int wmain(int argc, const boo::SystemChar** argv)
|
||||
#else
|
||||
int main(int argc, const boo::SystemChar** argv)
|
||||
#endif
|
||||
{
|
||||
logvisor::RegisterConsoleLogger();
|
||||
|
||||
std::vector<boo::SystemString> m_args;
|
||||
m_args.reserve(argc);
|
||||
double rate = 32000.0;
|
||||
for (int i=1 ; i<argc ; ++i)
|
||||
{
|
||||
#if _WIN32
|
||||
if (!wcsncmp(argv[i], L"-r", 2))
|
||||
{
|
||||
if (argv[i][2])
|
||||
rate = wcstod(&argv[i][2], nullptr);
|
||||
else if (argc > (i+1))
|
||||
{
|
||||
rate = wcstod(argv[i+1], nullptr);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_args.push_back(argv[i]);
|
||||
#else
|
||||
if (!strncmp(argv[i], "-r", 2))
|
||||
{
|
||||
if (argv[i][2])
|
||||
rate = strtod(&argv[i][2], nullptr);
|
||||
else if (argc > (i+1))
|
||||
{
|
||||
rate = strtod(argv[i+1], nullptr);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_args.push_back(argv[i]);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Load data */
|
||||
if (m_args.size() < 1)
|
||||
{
|
||||
Log.report(logvisor::Error, "Usage: amuserender <group-file> [<songs-file>] [-r <sample-rate>]");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
amuse::ContainerRegistry::Type cType = amuse::ContainerRegistry::DetectContainerType(m_args[0].c_str());
|
||||
if (cType == amuse::ContainerRegistry::Type::Invalid)
|
||||
{
|
||||
Log.report(logvisor::Error, "invalid/no data at path argument");
|
||||
exit(1);
|
||||
}
|
||||
Log.report(logvisor::Info, _S("Found '%s' Audio Group data"), amuse::ContainerRegistry::TypeToName(cType));
|
||||
|
||||
std::vector<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>> data =
|
||||
amuse::ContainerRegistry::LoadContainer(m_args[0].c_str());
|
||||
if (data.empty())
|
||||
{
|
||||
Log.report(logvisor::Error, "invalid/no data at path argument");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int m_groupId = -1;
|
||||
int m_setupId = -1;
|
||||
const amuse::SystemString* m_groupName = nullptr;
|
||||
const amuse::SystemString* m_songName = nullptr;
|
||||
amuse::ContainerRegistry::SongData* m_arrData = nullptr;
|
||||
bool m_sfxGroup = false;
|
||||
|
||||
std::list<amuse::AudioGroupProject> m_projs;
|
||||
std::map<int, std::pair<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>*, const amuse::SongGroupIndex*>> allSongGroups;
|
||||
std::map<int, std::pair<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>*, const amuse::SFXGroupIndex*>> allSFXGroups;
|
||||
size_t totalGroups = 0;
|
||||
|
||||
for (auto& grp : data)
|
||||
{
|
||||
/* Load project to assemble group list */
|
||||
m_projs.push_back(amuse::AudioGroupProject::CreateAudioGroupProject(grp.second));
|
||||
amuse::AudioGroupProject& proj = m_projs.back();
|
||||
totalGroups += proj.sfxGroups().size() + proj.songGroups().size();
|
||||
|
||||
for (auto it = proj.songGroups().begin() ; it != proj.songGroups().end() ; ++it)
|
||||
allSongGroups[it->first] = std::make_pair(&grp, &it->second);
|
||||
|
||||
for (auto it = proj.sfxGroups().begin() ; it != proj.sfxGroups().end() ; ++it)
|
||||
allSFXGroups[it->first] = std::make_pair(&grp, &it->second);
|
||||
}
|
||||
|
||||
/* Attempt loading song */
|
||||
std::vector<std::pair<amuse::SystemString, amuse::ContainerRegistry::SongData>> songs;
|
||||
if (m_args.size() > 1)
|
||||
songs = amuse::ContainerRegistry::LoadSongs(m_args[1].c_str());
|
||||
else
|
||||
songs = amuse::ContainerRegistry::LoadSongs(m_args[0].c_str());
|
||||
|
||||
if (songs.size())
|
||||
{
|
||||
bool play = true;
|
||||
if (m_args.size() <= 1)
|
||||
{
|
||||
bool prompt = true;
|
||||
while (true)
|
||||
{
|
||||
if (prompt)
|
||||
{
|
||||
printf("Render Song? (Y/N): ");
|
||||
prompt = false;
|
||||
}
|
||||
char userSel;
|
||||
if (scanf("%c", &userSel) <= 0 || userSel == '\n')
|
||||
continue;
|
||||
userSel = tolower(userSel);
|
||||
if (userSel == 'n')
|
||||
play = false;
|
||||
else if (userSel != 'y')
|
||||
{
|
||||
prompt = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (play)
|
||||
{
|
||||
/* Get song selection from user */
|
||||
if (songs.size() > 1)
|
||||
{
|
||||
/* Ask user to specify which song */
|
||||
printf("Multiple Songs discovered:\n");
|
||||
int idx = 0;
|
||||
for (const auto& pair : songs)
|
||||
{
|
||||
const amuse::ContainerRegistry::SongData& sngData = pair.second;
|
||||
int16_t grpId = sngData.m_groupId;
|
||||
int16_t setupId = sngData.m_setupId;
|
||||
if (sngData.m_groupId == -1 && sngData.m_setupId != -1)
|
||||
{
|
||||
for (const auto& pair : allSongGroups)
|
||||
{
|
||||
for (const auto& setup : pair.second.second->m_midiSetups)
|
||||
{
|
||||
if (setup.first == sngData.m_setupId)
|
||||
{
|
||||
grpId = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (grpId != -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
amuse::Printf(_S(" %d %s (Group %d, Setup %d)\n"), idx++,
|
||||
pair.first.c_str(), grpId, setupId);
|
||||
}
|
||||
|
||||
int userSel = 0;
|
||||
printf("Enter Song Number: ");
|
||||
if (scanf("%d", &userSel) <= 0)
|
||||
{
|
||||
Log.report(logvisor::Error, "unable to parse prompt");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (userSel < songs.size())
|
||||
{
|
||||
m_arrData = &songs[userSel].second;
|
||||
m_groupId = m_arrData->m_groupId;
|
||||
m_setupId = m_arrData->m_setupId;
|
||||
m_songName = &songs[userSel].first;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.report(logvisor::Error, "unable to find Song %d", userSel);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else if (songs.size() == 1)
|
||||
{
|
||||
m_arrData = &songs[0].second;
|
||||
m_groupId = m_arrData->m_groupId;
|
||||
m_setupId = m_arrData->m_setupId;
|
||||
m_songName = &songs[0].first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Get group selection via setup search */
|
||||
if (m_groupId == -1 && m_setupId != -1)
|
||||
{
|
||||
for (const auto& pair : allSongGroups)
|
||||
{
|
||||
for (const auto& setup : pair.second.second->m_midiSetups)
|
||||
{
|
||||
if (setup.first == m_setupId)
|
||||
{
|
||||
m_groupId = pair.first;
|
||||
m_groupName = &pair.second.first->first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_groupId != -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get group selection via user */
|
||||
if (m_groupId != -1)
|
||||
{
|
||||
if (allSongGroups.find(m_groupId) != allSongGroups.end())
|
||||
m_sfxGroup = false;
|
||||
else if (allSFXGroups.find(m_groupId) != allSFXGroups.end())
|
||||
m_sfxGroup = true;
|
||||
else
|
||||
{
|
||||
Log.report(logvisor::Error, "unable to find Group %d", m_groupId);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else if (totalGroups > 1)
|
||||
{
|
||||
/* Ask user to specify which group in project */
|
||||
printf("Multiple Audio Groups discovered:\n");
|
||||
for (const auto& pair : allSFXGroups)
|
||||
{
|
||||
amuse::Printf(_S(" %d %s (SFXGroup) %" PRISize " sfx-entries\n"),
|
||||
pair.first, pair.second.first->first.c_str(),
|
||||
pair.second.second->m_sfxEntries.size());
|
||||
}
|
||||
for (const auto& pair : allSongGroups)
|
||||
{
|
||||
amuse::Printf(_S(" %d %s (SongGroup) %" PRISize " normal-pages, %" PRISize " drum-pages, %" PRISize " MIDI-setups\n"),
|
||||
pair.first, pair.second.first->first.c_str(),
|
||||
pair.second.second->m_normPages.size(),
|
||||
pair.second.second->m_drumPages.size(),
|
||||
pair.second.second->m_midiSetups.size());
|
||||
}
|
||||
|
||||
int userSel = 0;
|
||||
printf("Enter Group Number: ");
|
||||
if (scanf("%d", &userSel) <= 0)
|
||||
{
|
||||
Log.report(logvisor::Error, "unable to parse prompt");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
auto songSearch = allSongGroups.find(userSel);
|
||||
auto sfxSearch = allSFXGroups.find(userSel);
|
||||
if (songSearch != allSongGroups.end())
|
||||
{
|
||||
m_groupId = userSel;
|
||||
m_groupName = &songSearch->second.first->first;
|
||||
m_sfxGroup = false;
|
||||
}
|
||||
else if (sfxSearch != allSFXGroups.end())
|
||||
{
|
||||
m_groupId = userSel;
|
||||
m_groupName = &sfxSearch->second.first->first;
|
||||
m_sfxGroup = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.report(logvisor::Error, "unable to find Group %d", userSel);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else if (totalGroups == 1)
|
||||
{
|
||||
/* Load one and only group */
|
||||
if (allSongGroups.size())
|
||||
{
|
||||
const auto& pair = *allSongGroups.cbegin();
|
||||
m_groupId = pair.first;
|
||||
m_groupName = &pair.second.first->first;
|
||||
m_sfxGroup = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& pair = *allSFXGroups.cbegin();
|
||||
m_groupId = pair.first;
|
||||
m_groupName = &pair.second.first->first;
|
||||
m_sfxGroup = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.report(logvisor::Error, "empty project");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Make final group selection */
|
||||
amuse::IntrusiveAudioGroupData* selData = nullptr;
|
||||
const amuse::SongGroupIndex* songIndex = nullptr;
|
||||
const amuse::SFXGroupIndex* sfxIndex = nullptr;
|
||||
auto songSearch = allSongGroups.find(m_groupId);
|
||||
if (songSearch != allSongGroups.end())
|
||||
{
|
||||
selData = &songSearch->second.first->second;
|
||||
songIndex = songSearch->second.second;
|
||||
std::set<int> sortSetups;
|
||||
for (auto& pair : songIndex->m_midiSetups)
|
||||
sortSetups.insert(pair.first);
|
||||
if (m_setupId == -1)
|
||||
{
|
||||
/* Ask user to specify which group in project */
|
||||
printf("Multiple MIDI Setups:\n");
|
||||
for (int setup : sortSetups)
|
||||
printf(" %d\n", setup);
|
||||
int userSel = 0;
|
||||
printf("Enter Setup Number: ");
|
||||
if (scanf("%d", &userSel) <= 0)
|
||||
{
|
||||
Log.report(logvisor::Error, "unable to parse prompt");
|
||||
exit(1);
|
||||
}
|
||||
m_setupId = userSel;
|
||||
}
|
||||
if (sortSetups.find(m_setupId) == sortSetups.cend())
|
||||
{
|
||||
Log.report(logvisor::Error, "unable to find setup %d", m_setupId);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto sfxSearch = allSFXGroups.find(m_groupId);
|
||||
if (sfxSearch != allSFXGroups.end())
|
||||
{
|
||||
selData = &sfxSearch->second.first->second;
|
||||
sfxIndex = sfxSearch->second.second;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selData)
|
||||
{
|
||||
Log.report(logvisor::Error, "unable to select audio group data");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (m_sfxGroup)
|
||||
{
|
||||
Log.report(logvisor::Error, "amuserender is currently only able to render SongGroups");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* WAV out path */
|
||||
amuse::SystemChar pathOut[1024];
|
||||
SNPrintf(pathOut, 1024, _S("%s-%s.wav"), m_groupName->c_str(), m_songName->c_str());
|
||||
Log.report(logvisor::Info, _S("Writing to %s"), pathOut);
|
||||
|
||||
/* Build voice engine */
|
||||
std::unique_ptr<boo::IAudioVoiceEngine> voxEngine = boo::NewWAVAudioVoiceEngine(pathOut, rate);
|
||||
amuse::BooBackendVoiceAllocator booBackend(*voxEngine);
|
||||
amuse::Engine engine(booBackend, amuse::AmplitudeMode::PerSample);
|
||||
|
||||
/* Load group into engine */
|
||||
const amuse::AudioGroup* group = engine.addAudioGroup(*selData);
|
||||
if (!group)
|
||||
{
|
||||
Log.report(logvisor::Error, "unable to add audio group");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Enter playback loop */
|
||||
std::shared_ptr<amuse::Sequencer> seq = engine.seqPlay(m_groupId, m_setupId, m_arrData->m_data.get());
|
||||
size_t wroteFrames = 0;
|
||||
signal(SIGINT, SIGINTHandler);
|
||||
do
|
||||
{
|
||||
engine.pumpEngine();
|
||||
wroteFrames += voxEngine->get5MsFrames();
|
||||
printf("\rFrame %" PRISize, wroteFrames);
|
||||
fflush(stdout);
|
||||
} while (!g_BreakLoop && (seq->state() == amuse::SequencerState::Playing || seq->getVoiceCount() != 0));
|
||||
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
|
||||
{
|
||||
signal( SIGABRT, abortHandler );
|
||||
signal( SIGSEGV, abortHandler );
|
||||
signal( SIGILL, abortHandler );
|
||||
signal( SIGFPE, abortHandler );
|
||||
|
||||
int argc = 0;
|
||||
const boo::SystemChar** argv = (const wchar_t**)(CommandLineToArgvW(lpCmdLine, &argc));
|
||||
static boo::SystemChar selfPath[1024];
|
||||
GetModuleFileNameW(nullptr, selfPath, 1024);
|
||||
static const boo::SystemChar* booArgv[32] = {};
|
||||
booArgv[0] = selfPath;
|
||||
for (int i=0 ; i<argc ; ++i)
|
||||
booArgv[i+1] = argv[i];
|
||||
|
||||
logvisor::CreateWin32Console();
|
||||
SetConsoleOutputCP(65001);
|
||||
return wmain(argc+1, booArgv);
|
||||
}
|
||||
#endif
|
||||
@@ -22,6 +22,7 @@ class BooBackendVoice : public IBackendVoice
|
||||
struct VoiceCallback : boo::IAudioVoiceCallback
|
||||
{
|
||||
BooBackendVoice& m_parent;
|
||||
void preSupplyAudio(boo::IAudioVoice& voice, double dt);
|
||||
size_t supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t* data);
|
||||
VoiceCallback(BooBackendVoice& parent) : m_parent(parent) {}
|
||||
} m_cb;
|
||||
|
||||
@@ -21,10 +21,10 @@ public:
|
||||
};
|
||||
private:
|
||||
State m_phase = State::Attack; /**< Current envelope state */
|
||||
double m_attackTime = 0.02; /**< Time of attack in seconds */
|
||||
double m_attackTime = 0.0; /**< Time of attack in seconds */
|
||||
double m_decayTime = 0.0; /**< Time of decay in seconds */
|
||||
double m_sustainFactor = 1.0; /**< Evaluated sustain percentage */
|
||||
double m_releaseTime = 0.02; /**< Time of release in seconds */
|
||||
double m_releaseTime = 0.0; /**< Time of release in seconds */
|
||||
double m_releaseStartFactor = 0.0; /**< Level at whenever release event occurs */
|
||||
double m_curTime = 0.0; /**< Current time of envelope stage in seconds */
|
||||
bool m_adsrSet = false;
|
||||
|
||||
@@ -10,14 +10,8 @@ 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 std::vector<uint8_t>& data, Target target);
|
||||
static std::vector<uint8_t> SongToMIDI(const unsigned char* data, int& versionOut, bool& isBig);
|
||||
static std::vector<uint8_t> MIDIToSong(const std::vector<uint8_t>& data, int version, bool big);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ class SongState
|
||||
};
|
||||
|
||||
const unsigned char* m_songData = nullptr; /**< Base pointer to active song */
|
||||
int m_sngVersion; /**< Detected song revision, 1 has RLE-compressed delta-times */
|
||||
bool m_bigEndian; /**< True if loaded song is big-endian data */
|
||||
|
||||
/** State of a single track within arrangement */
|
||||
@@ -104,8 +105,13 @@ class SongState
|
||||
double m_curDt = 0.f; /**< Cumulative dt value for time-remainder tracking */
|
||||
|
||||
public:
|
||||
/** Determine SNG version
|
||||
* @param isBig returns true if big-endian SNG
|
||||
* @return 0 for initial version, 1 for delta-time revision, -1 for non-SNG */
|
||||
static int DetectVersion(const unsigned char* ptr, bool& isBig);
|
||||
|
||||
/** initialize state for Song data at `ptr` */
|
||||
void initialize(const unsigned char* ptr);
|
||||
bool initialize(const unsigned char* ptr);
|
||||
|
||||
/** advances `dt` seconds worth of commands in the Song
|
||||
* @return `true` if END reached
|
||||
|
||||
@@ -86,6 +86,7 @@ class Voice : public Entity
|
||||
int32_t m_pitchWheelVal = 0; /**< Current resolved pitchwheel delta for control */
|
||||
int32_t m_curPitch; /**< Current base pitch in cents */
|
||||
bool m_pitchDirty = true; /**< m_curPitch has been updated and needs sending to voice */
|
||||
bool m_needsSlew = false; /**< next _setTotalPitch will be slewed */
|
||||
|
||||
Envelope m_volAdsr; /**< Volume envelope */
|
||||
double m_envelopeTime = -1.f; /**< time since last ENVELOPE command, -1 for no active volume-sweep */
|
||||
@@ -137,7 +138,7 @@ class Voice : public Entity
|
||||
void _doKeyOff();
|
||||
void _macroKeyOff();
|
||||
void _macroSampleEnd();
|
||||
bool _advanceSample(int16_t& samp, int32_t& curPitch);
|
||||
void _advanceSample(int16_t& samp);
|
||||
void _setTotalPitch(int32_t cents, bool slew);
|
||||
bool _isRecursivelyDead();
|
||||
void _bringOutYourDead();
|
||||
@@ -166,6 +167,10 @@ public:
|
||||
Voice(Engine& engine, const AudioGroup& group, int groupId, int vid, bool emitter, Submix* smx);
|
||||
Voice(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid, int vid, bool emitter, Submix* smx);
|
||||
|
||||
/** Called before each supplyAudio invocation to prepare voice
|
||||
* backend for possible parameter updates */
|
||||
void preSupplyAudio(double dt);
|
||||
|
||||
/** Request specified count of audio frames (samples) from voice,
|
||||
* internally advancing the voice stream */
|
||||
size_t supplyAudio(size_t frames, int16_t* data);
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
namespace amuse
|
||||
{
|
||||
|
||||
void BooBackendVoice::VoiceCallback::preSupplyAudio(boo::IAudioVoice&,
|
||||
double dt)
|
||||
{
|
||||
m_parent.m_clientVox.preSupplyAudio(dt);
|
||||
}
|
||||
|
||||
size_t BooBackendVoice::VoiceCallback::supplyAudio(boo::IAudioVoice&,
|
||||
size_t frames, int16_t* data)
|
||||
{
|
||||
|
||||
@@ -1679,6 +1679,139 @@ static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadRS3(FIL
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool ValidateStarFoxAdvSongs(FILE* fp)
|
||||
{
|
||||
size_t endPos = FileLength(fp);
|
||||
if (endPos > 2 * 1024 * 1024)
|
||||
return false;
|
||||
|
||||
std::unique_ptr<uint8_t[]> data(new uint8_t[endPos]);
|
||||
fread(data.get(), 1, endPos, fp);
|
||||
|
||||
const uint32_t* lengths = reinterpret_cast<const uint32_t*>(data.get());
|
||||
size_t totalLen = 0;
|
||||
int i=0;
|
||||
for (; i<128 ; ++i)
|
||||
{
|
||||
uint32_t len = SBig(lengths[i]);
|
||||
if (len == 0)
|
||||
break;
|
||||
totalLen += len;
|
||||
totalLen = ((totalLen + 31) & ~31);
|
||||
}
|
||||
totalLen += (((i*4) + 31) & ~31);
|
||||
|
||||
return totalLen == endPos;
|
||||
}
|
||||
|
||||
static std::vector<std::pair<SystemString, ContainerRegistry::SongData>> LoadStarFoxAdvSongs(FILE* midifp)
|
||||
{
|
||||
std::vector<std::pair<SystemString, ContainerRegistry::SongData>> ret;
|
||||
|
||||
size_t endPos = FileLength(midifp);
|
||||
if (endPos > 2 * 1024 * 1024)
|
||||
return {};
|
||||
|
||||
std::unique_ptr<uint8_t[]> data(new uint8_t[endPos]);
|
||||
fread(data.get(), 1, endPos, midifp);
|
||||
|
||||
const uint32_t* lengths = reinterpret_cast<const uint32_t*>(data.get());
|
||||
int i=0;
|
||||
for (; i<128 ; ++i)
|
||||
{
|
||||
uint32_t len = SBig(lengths[i]);
|
||||
if (len == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
size_t sngCount = i;
|
||||
size_t cur = (((sngCount*4) + 31) & ~31);
|
||||
for (i=0; i<sngCount ; ++i)
|
||||
{
|
||||
uint32_t len = SBig(lengths[i]);
|
||||
if (len == 0)
|
||||
break;
|
||||
|
||||
SystemChar name[128];
|
||||
SNPrintf(name, 128, _S("Song%u"), i);
|
||||
std::unique_ptr<uint8_t[]> song(new uint8_t[len]);
|
||||
memmove(song.get(), data.get() + cur, len);
|
||||
ret.emplace_back(name, ContainerRegistry::SongData(std::move(song), len, -1, i));
|
||||
|
||||
cur += len;
|
||||
cur = ((cur + 31) & ~31);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool ValidatePaperMarioTTYDSongs(FILE* fp)
|
||||
{
|
||||
size_t endPos = FileLength(fp);
|
||||
std::unique_ptr<uint8_t[]> data(new uint8_t[endPos]);
|
||||
fread(data.get(), 1, endPos, fp);
|
||||
uint32_t off = 0;
|
||||
while (off < endPos)
|
||||
{
|
||||
int32_t len = SBig(*(reinterpret_cast<int32_t*>(data.get() + off)));
|
||||
if (len == -1)
|
||||
break;
|
||||
off += len;
|
||||
}
|
||||
return (off + 4) == endPos;
|
||||
}
|
||||
|
||||
struct TTYDSongDesc
|
||||
{
|
||||
char name[30];
|
||||
uint8_t group;
|
||||
uint8_t setup;
|
||||
};
|
||||
|
||||
static std::vector<std::pair<SystemString, ContainerRegistry::SongData>> LoadPaperMarioTTYDSongs(FILE* midifp, FILE* descFp)
|
||||
{
|
||||
if (!descFp)
|
||||
return {};
|
||||
|
||||
std::vector<TTYDSongDesc> songDescs;
|
||||
/* We need at least 143 for the default song table */
|
||||
songDescs.reserve(143);
|
||||
|
||||
while (true)
|
||||
{
|
||||
TTYDSongDesc songDesc;
|
||||
fread(&songDesc, sizeof(TTYDSongDesc), 1, descFp);
|
||||
if (songDesc.name[0] == 0)
|
||||
break;
|
||||
|
||||
uint32_t i = 0;
|
||||
songDescs.push_back(songDesc);
|
||||
}
|
||||
size_t endPos = FileLength(midifp);
|
||||
std::unique_ptr<uint8_t[]> data(new uint8_t[endPos]);
|
||||
fread(data.get(), 1, endPos, midifp);
|
||||
uint32_t off = 0;
|
||||
uint32_t song = 0;
|
||||
std::vector<std::pair<SystemString, ContainerRegistry::SongData>> ret;
|
||||
|
||||
while (off < endPos)
|
||||
{
|
||||
int32_t len = SBig(*(reinterpret_cast<int32_t*>(data.get() + off)));
|
||||
if (len == -1)
|
||||
break;
|
||||
|
||||
std::unique_ptr<uint8_t[]> songData(new uint8_t[len - 32]);
|
||||
memcpy(songData.get(), (data.get() + off + 32), len - 32);
|
||||
ret.emplace_back(StrToSys(std::string(songDescs[song].name, 30)),
|
||||
ContainerRegistry::SongData(std::move(songData), len - 32,
|
||||
songDescs[song].group, songDescs[song].setup));
|
||||
off += len;
|
||||
song++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ContainerRegistry::Type ContainerRegistry::DetectContainerType(const SystemChar* path)
|
||||
{
|
||||
FILE* fp;
|
||||
@@ -2062,6 +2195,29 @@ ContainerRegistry::LoadSongs(const SystemChar* path)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ValidateStarFoxAdvSongs(fp))
|
||||
{
|
||||
auto ret = LoadStarFoxAdvSongs(fp);
|
||||
fclose(fp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ValidatePaperMarioTTYDSongs(fp))
|
||||
{
|
||||
/* Song Description */
|
||||
SystemChar newpath[1024];
|
||||
dot = StrRChr(path, _S('.'));
|
||||
SNPrintf(newpath, 1024, _S("%.*s.stbl"), int(dot - path), path);
|
||||
FILE* descFp = FOpen(newpath, _S("rb"));
|
||||
if (descFp)
|
||||
{
|
||||
auto ret = LoadPaperMarioTTYDSongs(fp, descFp);
|
||||
fclose(fp);
|
||||
fclose(descFp);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
|
||||
@@ -356,6 +356,15 @@ void Sequencer::setCtrlValue(uint8_t chan, uint8_t ctrl, int8_t val)
|
||||
if (chan > 15)
|
||||
return;
|
||||
|
||||
if (ctrl == 0x66)
|
||||
{
|
||||
printf("Loop Start\n");
|
||||
}
|
||||
else if (ctrl == 0x67)
|
||||
{
|
||||
printf("Loop End\n");
|
||||
}
|
||||
|
||||
if (!m_chanStates[chan])
|
||||
m_chanStates[chan].emplace(*this, chan);
|
||||
|
||||
|
||||
@@ -618,7 +618,7 @@ static void EncodeTimeRLE(std::vector<uint8_t>& vecOut, uint32_t val)
|
||||
vecOut.push_back(reinterpret_cast<const uint8_t*>(&lastPart)[1]);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, Target& targetOut)
|
||||
std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& versionOut, bool& isBig)
|
||||
{
|
||||
std::vector<uint8_t> ret = {'M', 'T', 'h', 'd'};
|
||||
uint32_t six32 = SBig(uint32_t(6));
|
||||
@@ -629,17 +629,10 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, Target
|
||||
ret.push_back(1);
|
||||
|
||||
SongState song;
|
||||
song.initialize(data);
|
||||
|
||||
if (song.m_bigEndian)
|
||||
{
|
||||
if (song.m_header.m_trackIdxOff == 0x18 || song.m_header.m_trackIdxOff == 0x58)
|
||||
targetOut = Target::GCN;
|
||||
else
|
||||
targetOut = Target::N64;
|
||||
}
|
||||
else
|
||||
targetOut = Target::PC;
|
||||
if (!song.initialize(data))
|
||||
return {};
|
||||
versionOut = song.m_sngVersion;
|
||||
isBig = song.m_bigEndian;
|
||||
|
||||
size_t trkCount = 1;
|
||||
for (std::experimental::optional<SongState::Track>& trk : song.m_tracks)
|
||||
@@ -682,7 +675,7 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, Target
|
||||
encoder.getResult().push_back(0x51);
|
||||
encoder.getResult().push_back(3);
|
||||
|
||||
uint32_t tempo24 = SBig(60000000 / change.m_tempo);
|
||||
uint32_t tempo24 = SBig(60000000 / (change.m_tempo & 0x7fffffff));
|
||||
for (int i=1 ; i<4 ; ++i)
|
||||
encoder.getResult().push_back(reinterpret_cast<uint8_t*>(&tempo24)[i]);
|
||||
|
||||
@@ -768,9 +761,9 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, Target
|
||||
}
|
||||
|
||||
/* 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)
|
||||
if (song.m_sngVersion == 1)
|
||||
{
|
||||
/* GameCube */
|
||||
/* Revision */
|
||||
while (true)
|
||||
{
|
||||
/* Load next command */
|
||||
@@ -812,7 +805,7 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, Target
|
||||
}
|
||||
else
|
||||
{
|
||||
/* N64 */
|
||||
/* Legacy */
|
||||
while (true)
|
||||
{
|
||||
/* Load next command */
|
||||
@@ -920,11 +913,10 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, Target
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data, Target target)
|
||||
std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data, int version, bool big)
|
||||
{
|
||||
std::vector<uint8_t> ret;
|
||||
std::vector<uint8_t>::const_iterator it = data.cbegin();
|
||||
bool bigEndian = (target == Target::GCN || target == Target::N64);
|
||||
|
||||
struct MIDIHeader
|
||||
{
|
||||
@@ -971,6 +963,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
std::vector<uint8_t> eventBuf;
|
||||
std::vector<uint8_t> pitchBuf;
|
||||
std::vector<uint8_t> modBuf;
|
||||
int padding = 0;
|
||||
|
||||
bool operator==(const Region& other) const
|
||||
{
|
||||
@@ -1022,7 +1015,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
++it;
|
||||
for (auto& pair : tempos)
|
||||
{
|
||||
if (bigEndian)
|
||||
if (big)
|
||||
tempoBuf.emplace_back(SBig(uint32_t(pair.first * 384 / header.div)), SBig(uint32_t(pair.second)));
|
||||
else
|
||||
tempoBuf.emplace_back(pair.first * 384 / header.div, pair.second);
|
||||
@@ -1091,14 +1084,16 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
}
|
||||
else
|
||||
{
|
||||
if (target == Target::GCN)
|
||||
if (version == 1)
|
||||
{
|
||||
EncodeTimeRLE(region.eventBuf, uint32_t(eventTick - lastEventTick));
|
||||
lastEventTick = eventTick;
|
||||
region.eventBuf.push_back(0x80 | event.second.velOrVal);
|
||||
region.eventBuf.push_back(0x80 | event.second.noteOrCtrl);
|
||||
}
|
||||
else if (target == Target::N64)
|
||||
else
|
||||
{
|
||||
if (big)
|
||||
{
|
||||
uint32_t tickBig = SBig(uint32_t(eventTick - startTick));
|
||||
for (int i=0 ; i<4 ; ++i)
|
||||
@@ -1106,7 +1101,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
region.eventBuf.push_back(0x80 | event.second.velOrVal);
|
||||
region.eventBuf.push_back(0x80 | event.second.noteOrCtrl);
|
||||
}
|
||||
else if (target == Target::PC)
|
||||
else
|
||||
{
|
||||
uint32_t tick = uint32_t(eventTick - startTick);
|
||||
for (int i=0 ; i<4 ; ++i)
|
||||
@@ -1116,16 +1111,19 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (event.second.isProgChange)
|
||||
{
|
||||
if (target == Target::GCN)
|
||||
if (version == 1)
|
||||
{
|
||||
EncodeTimeRLE(region.eventBuf, uint32_t(eventTick - lastEventTick));
|
||||
lastEventTick = eventTick;
|
||||
region.eventBuf.push_back(0x80 | event.second.program);
|
||||
region.eventBuf.push_back(0);
|
||||
}
|
||||
else if (target == Target::N64)
|
||||
else
|
||||
{
|
||||
if (big)
|
||||
{
|
||||
uint32_t tickBig = SBig(uint32_t(eventTick - startTick));
|
||||
for (int i=0 ; i<4 ; ++i)
|
||||
@@ -1133,7 +1131,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
region.eventBuf.push_back(0x80 | event.second.program);
|
||||
region.eventBuf.push_back(0);
|
||||
}
|
||||
else if (target == Target::PC)
|
||||
else
|
||||
{
|
||||
uint32_t tick = uint32_t(eventTick - startTick);
|
||||
for (int i=0 ; i<4 ; ++i)
|
||||
@@ -1142,6 +1140,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
region.eventBuf.push_back(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (event.second.isPitchBend)
|
||||
{
|
||||
EncodeRLE(region.pitchBuf, uint32_t(eventTick - lastPitchTick));
|
||||
@@ -1152,7 +1151,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
}
|
||||
else if (event.second.isNote)
|
||||
{
|
||||
if (target == Target::GCN)
|
||||
if (version == 1)
|
||||
{
|
||||
EncodeTimeRLE(region.eventBuf, uint32_t(eventTick - lastEventTick));
|
||||
lastEventTick = eventTick;
|
||||
@@ -1162,7 +1161,9 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[0]);
|
||||
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[1]);
|
||||
}
|
||||
else if (target == Target::N64)
|
||||
else
|
||||
{
|
||||
if (big)
|
||||
{
|
||||
uint32_t tickBig = SBig(uint32_t(eventTick - startTick));
|
||||
for (int i=0 ; i<4 ; ++i)
|
||||
@@ -1173,7 +1174,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
region.eventBuf.push_back(event.second.noteOrCtrl);
|
||||
region.eventBuf.push_back(event.second.velOrVal);
|
||||
}
|
||||
else if (target == Target::PC)
|
||||
else
|
||||
{
|
||||
uint32_t tick = uint32_t(eventTick - startTick);
|
||||
for (int i=0 ; i<4 ; ++i)
|
||||
@@ -1187,6 +1188,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (didInit)
|
||||
{
|
||||
@@ -1200,7 +1202,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
}
|
||||
|
||||
/* Terminate region */
|
||||
if (target == Target::GCN)
|
||||
if (version == 1)
|
||||
{
|
||||
size_t pitchDelta = 0;
|
||||
size_t modDelta = 0;
|
||||
@@ -1213,7 +1215,9 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
region.eventBuf.push_back(0xff);
|
||||
region.eventBuf.push_back(0xff);
|
||||
}
|
||||
else if (target == Target::N64)
|
||||
else
|
||||
{
|
||||
if (big)
|
||||
{
|
||||
uint32_t selTick = std::max(std::max(lastEventTick - startTick,
|
||||
lastPitchTick - startTick),
|
||||
@@ -1226,7 +1230,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
region.eventBuf.push_back(0xff);
|
||||
region.eventBuf.push_back(0xff);
|
||||
}
|
||||
else if (target == Target::PC)
|
||||
else
|
||||
{
|
||||
uint32_t selTick = std::max(std::max(lastEventTick - startTick,
|
||||
lastPitchTick - startTick),
|
||||
@@ -1239,6 +1243,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
region.eventBuf.push_back(0xff);
|
||||
region.eventBuf.push_back(0xff);
|
||||
}
|
||||
}
|
||||
|
||||
if (region.pitchBuf.size())
|
||||
{
|
||||
@@ -1264,13 +1269,15 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
{
|
||||
regionDataIdxArr.push_back(curRegionOff);
|
||||
curRegionOff += 12 + region.eventBuf.size() + region.pitchBuf.size() + region.modBuf.size();
|
||||
int paddedRegOff = ((curRegionOff + 3) & ~3);
|
||||
region.padding = paddedRegOff - curRegionOff;
|
||||
regions.push_back(std::move(region));
|
||||
}
|
||||
|
||||
/* Region header */
|
||||
regionBuf.emplace_back();
|
||||
SongState::TrackRegion& reg = regionBuf.back();
|
||||
if (bigEndian)
|
||||
if (big)
|
||||
{
|
||||
reg.m_startTick = SBig(uint32_t(startTick));
|
||||
reg.m_progNum = 0xff;
|
||||
@@ -1296,7 +1303,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
/* Terminating region header */
|
||||
regionBuf.emplace_back();
|
||||
SongState::TrackRegion& reg = regionBuf.back();
|
||||
if (bigEndian)
|
||||
if (big)
|
||||
{
|
||||
reg.m_startTick = SBig(uint32_t(lastTrackStartTick));
|
||||
reg.m_progNum = 0xff;
|
||||
@@ -1318,7 +1325,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
}
|
||||
}
|
||||
|
||||
if (target == Target::GCN)
|
||||
if (version == 1)
|
||||
{
|
||||
SongState::Header head;
|
||||
head.m_trackIdxOff = 0x18;
|
||||
@@ -1329,6 +1336,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
head.m_unkOff = 0;
|
||||
|
||||
uint32_t regIdxOff = head.m_regionIdxOff;
|
||||
if (big)
|
||||
head.swapBig();
|
||||
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), 0x18, 0)) = head;
|
||||
|
||||
@@ -1341,7 +1349,8 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
}
|
||||
|
||||
uint32_t idx = trackRegionIdxArr[i];
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = SBig(uint32_t(0x18 + 4 * 64 + idx * 12));
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = big ? SBig(uint32_t(0x18 + 4 * 64 + idx * 12)) :
|
||||
uint32_t(0x18 + 4 * 64 + idx * 12);
|
||||
}
|
||||
|
||||
for (SongState::TrackRegion& reg : regionBuf)
|
||||
@@ -1349,156 +1358,24 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
|
||||
uint32_t regBase = regIdxOff + 4 * regionDataIdxArr.size();
|
||||
for (uint32_t regOff : regionDataIdxArr)
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = SBig(uint32_t(regBase + regOff));
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = big ? SBig(uint32_t(regBase + regOff)) :
|
||||
uint32_t(regBase + regOff);
|
||||
|
||||
uint32_t curOffset = regBase;
|
||||
for (Region& reg : regions)
|
||||
{
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = SBig(uint32_t(8));
|
||||
|
||||
if (reg.pitchBuf.size())
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
|
||||
SBig(uint32_t(curOffset + 12 + reg.eventBuf.size()));
|
||||
else
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
|
||||
if (reg.modBuf.size())
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
|
||||
SBig(uint32_t(curOffset + 12 + reg.eventBuf.size() + reg.pitchBuf.size()));
|
||||
else
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
|
||||
if (reg.eventBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), reg.eventBuf.size(), 0), reg.eventBuf.data(), reg.eventBuf.size());
|
||||
|
||||
if (reg.pitchBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), reg.pitchBuf.size(), 0), reg.pitchBuf.data(), reg.pitchBuf.size());
|
||||
|
||||
if (reg.modBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), reg.modBuf.size(), 0), reg.modBuf.data(), reg.modBuf.size());
|
||||
|
||||
curOffset += 12 + reg.eventBuf.size() + reg.pitchBuf.size() + reg.modBuf.size();
|
||||
}
|
||||
|
||||
memmove(&*ret.insert(ret.cend(), 64, 0), chanMap.data(), 64);
|
||||
|
||||
if (tempoBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), tempoBuf.size() * 8, 0), tempoBuf.data(), tempoBuf.size() * 8);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = uint32_t(0xffffffff);
|
||||
}
|
||||
else if (target == Target::N64)
|
||||
{
|
||||
SongState::Header head;
|
||||
head.m_trackIdxOff = 0x18 + regionBuf.size() * 12;
|
||||
head.m_regionIdxOff = head.m_trackIdxOff + 4 * 64 + 64 + curRegionOff;
|
||||
head.m_chanMapOff = head.m_trackIdxOff + 4 * 64;
|
||||
head.m_tempoTableOff = tempoBuf.size() ? head.m_regionIdxOff + 4 * regionDataIdxArr.size() : 0;
|
||||
head.m_initialTempo = initTempo;
|
||||
head.m_unkOff = 0;
|
||||
|
||||
uint32_t chanMapOff = head.m_chanMapOff;
|
||||
head.swapBig();
|
||||
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), 0x18, 0)) = head;
|
||||
|
||||
for (SongState::TrackRegion& reg : regionBuf)
|
||||
*reinterpret_cast<SongState::TrackRegion*>(&*ret.insert(ret.cend(), 12, 0)) = reg;
|
||||
|
||||
for (int i=0 ; i<64 ; ++i)
|
||||
{
|
||||
if (i >= trackRegionIdxArr.size())
|
||||
{
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t idx = trackRegionIdxArr[i];
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = SBig(uint32_t(0x18 + 4 * 64 + idx * 12));
|
||||
}
|
||||
|
||||
memmove(&*ret.insert(ret.cend(), 64, 0), chanMap.data(), 64);
|
||||
|
||||
uint32_t regBase = chanMapOff + 64;
|
||||
uint32_t curOffset = regBase;
|
||||
for (Region& reg : regions)
|
||||
{
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = SBig(uint32_t(8));
|
||||
|
||||
if (reg.pitchBuf.size())
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
|
||||
SBig(uint32_t(curOffset + 12 + reg.eventBuf.size()));
|
||||
else
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
|
||||
if (reg.modBuf.size())
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
|
||||
SBig(uint32_t(curOffset + 12 + reg.eventBuf.size() + reg.pitchBuf.size()));
|
||||
else
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
|
||||
if (reg.eventBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), reg.eventBuf.size(), 0), reg.eventBuf.data(), reg.eventBuf.size());
|
||||
|
||||
if (reg.pitchBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), reg.pitchBuf.size(), 0), reg.pitchBuf.data(), reg.pitchBuf.size());
|
||||
|
||||
if (reg.modBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), reg.modBuf.size(), 0), reg.modBuf.data(), reg.modBuf.size());
|
||||
|
||||
curOffset += 12 + reg.eventBuf.size() + reg.pitchBuf.size() + reg.modBuf.size();
|
||||
}
|
||||
|
||||
for (uint32_t regOff : regionDataIdxArr)
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = SBig(uint32_t(regBase + regOff));
|
||||
|
||||
if (tempoBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), tempoBuf.size() * 8, 0), tempoBuf.data(), tempoBuf.size() * 8);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = uint32_t(0xffffffff);
|
||||
}
|
||||
else if (target == Target::PC)
|
||||
{
|
||||
SongState::Header head;
|
||||
head.m_trackIdxOff = 0x18 + regionBuf.size() * 12;
|
||||
head.m_regionIdxOff = head.m_trackIdxOff + 4 * 64 + 64 + curRegionOff;
|
||||
head.m_chanMapOff = head.m_trackIdxOff + 4 * 64;
|
||||
head.m_tempoTableOff = tempoBuf.size() ? head.m_regionIdxOff + 4 * regionDataIdxArr.size() : 0;
|
||||
head.m_initialTempo = initTempo;
|
||||
head.m_unkOff = 0;
|
||||
|
||||
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), 0x18, 0)) = head;
|
||||
|
||||
for (SongState::TrackRegion& reg : regionBuf)
|
||||
*reinterpret_cast<SongState::TrackRegion*>(&*ret.insert(ret.cend(), 12, 0)) = reg;
|
||||
|
||||
for (int i=0 ; i<64 ; ++i)
|
||||
{
|
||||
if (i >= trackRegionIdxArr.size())
|
||||
{
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t idx = trackRegionIdxArr[i];
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = uint32_t(0x18 + 4 * 64 + idx * 12);
|
||||
}
|
||||
|
||||
memmove(&*ret.insert(ret.cend(), 64, 0), chanMap.data(), 64);
|
||||
|
||||
uint32_t regBase = head.m_chanMapOff + 64;
|
||||
uint32_t curOffset = regBase;
|
||||
for (Region& reg : regions)
|
||||
{
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = uint32_t(8);
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = big ? SBig(uint32_t(8)) : 8;
|
||||
|
||||
if (reg.pitchBuf.size())
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
|
||||
big ? SBig(uint32_t(curOffset + 12 + reg.eventBuf.size())) :
|
||||
uint32_t(curOffset + 12 + reg.eventBuf.size());
|
||||
else
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
|
||||
if (reg.modBuf.size())
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
|
||||
big ? SBig(uint32_t(curOffset + 12 + reg.eventBuf.size() + reg.pitchBuf.size())) :
|
||||
uint32_t(curOffset + 12 + reg.eventBuf.size() + reg.pitchBuf.size());
|
||||
else
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
@@ -1512,11 +1389,88 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
if (reg.modBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), reg.modBuf.size(), 0), reg.modBuf.data(), reg.modBuf.size());
|
||||
|
||||
ret.insert(ret.cend(), reg.padding, 0);
|
||||
|
||||
curOffset += 12 + reg.eventBuf.size() + reg.pitchBuf.size() + reg.modBuf.size() + reg.padding;
|
||||
}
|
||||
|
||||
memmove(&*ret.insert(ret.cend(), 64, 0), chanMap.data(), 64);
|
||||
|
||||
if (tempoBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), tempoBuf.size() * 8, 0), tempoBuf.data(), tempoBuf.size() * 8);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = uint32_t(0xffffffff);
|
||||
}
|
||||
else
|
||||
{
|
||||
SongState::Header head;
|
||||
head.m_trackIdxOff = 0x18 + regionBuf.size() * 12;
|
||||
head.m_regionIdxOff = head.m_trackIdxOff + 4 * 64 + 64 + curRegionOff;
|
||||
head.m_chanMapOff = head.m_trackIdxOff + 4 * 64;
|
||||
head.m_tempoTableOff = tempoBuf.size() ? head.m_regionIdxOff + 4 * regionDataIdxArr.size() : 0;
|
||||
head.m_initialTempo = initTempo;
|
||||
head.m_unkOff = 0;
|
||||
|
||||
uint32_t chanMapOff = head.m_chanMapOff;
|
||||
if (big)
|
||||
head.swapBig();
|
||||
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), 0x18, 0)) = head;
|
||||
|
||||
for (SongState::TrackRegion& reg : regionBuf)
|
||||
*reinterpret_cast<SongState::TrackRegion*>(&*ret.insert(ret.cend(), 12, 0)) = reg;
|
||||
|
||||
for (int i=0 ; i<64 ; ++i)
|
||||
{
|
||||
if (i >= trackRegionIdxArr.size())
|
||||
{
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t idx = trackRegionIdxArr[i];
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = big ? SBig(uint32_t(0x18 + 4 * 64 + idx * 12)) :
|
||||
uint32_t(0x18 + 4 * 64 + idx * 12);
|
||||
}
|
||||
|
||||
memmove(&*ret.insert(ret.cend(), 64, 0), chanMap.data(), 64);
|
||||
|
||||
uint32_t regBase = chanMapOff + 64;
|
||||
uint32_t curOffset = regBase;
|
||||
for (Region& reg : regions)
|
||||
{
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = big ? SBig(uint32_t(8)) : 8;
|
||||
|
||||
if (reg.pitchBuf.size())
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
|
||||
big ? SBig(uint32_t(curOffset + 12 + reg.eventBuf.size())) :
|
||||
uint32_t(curOffset + 12 + reg.eventBuf.size());
|
||||
else
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
|
||||
if (reg.modBuf.size())
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
|
||||
big ? SBig(uint32_t(curOffset + 12 + reg.eventBuf.size() + reg.pitchBuf.size())) :
|
||||
uint32_t(curOffset + 12 + reg.eventBuf.size() + reg.pitchBuf.size());
|
||||
else
|
||||
ret.insert(ret.cend(), 4, 0);
|
||||
|
||||
if (reg.eventBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), reg.eventBuf.size(), 0), reg.eventBuf.data(), reg.eventBuf.size());
|
||||
|
||||
if (reg.pitchBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), reg.pitchBuf.size(), 0), reg.pitchBuf.data(), reg.pitchBuf.size());
|
||||
|
||||
if (reg.modBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), reg.modBuf.size(), 0), reg.modBuf.data(), reg.modBuf.size());
|
||||
|
||||
ret.insert(ret.cend(), reg.padding, 0);
|
||||
|
||||
curOffset += 12 + reg.eventBuf.size() + reg.pitchBuf.size() + reg.modBuf.size();
|
||||
}
|
||||
|
||||
for (uint32_t regOff : regionDataIdxArr)
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = uint32_t(regBase + regOff);
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = big ? SBig(uint32_t(regBase + regOff)) :
|
||||
uint32_t(regBase + regOff);
|
||||
|
||||
if (tempoBuf.size())
|
||||
memmove(&*ret.insert(ret.cend(), tempoBuf.size() * 8, 0), tempoBuf.data(), tempoBuf.size() * 8);
|
||||
|
||||
@@ -18,8 +18,11 @@ static uint32_t DecodeRLE(const unsigned char*& data)
|
||||
++data;
|
||||
thisPart = thisPart * 256 + *data;
|
||||
if (thisPart == 0)
|
||||
{
|
||||
++data;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (thisPart == 32767)
|
||||
{
|
||||
@@ -131,7 +134,7 @@ void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
|
||||
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_sngVersion == 1)
|
||||
m_eventWaitCountdown = int32_t(DecodeTimeRLE(m_data));
|
||||
else
|
||||
{
|
||||
@@ -148,9 +151,183 @@ void SongState::Track::advanceRegion(Sequencer* seq)
|
||||
setRegion(seq, m_nextRegion);
|
||||
}
|
||||
|
||||
void SongState::initialize(const unsigned char* ptr)
|
||||
int SongState::DetectVersion(const unsigned char* ptr, bool& isBig)
|
||||
{
|
||||
m_bigEndian = ptr[0] == 0;
|
||||
isBig = ptr[0] == 0;
|
||||
Header header = *reinterpret_cast<const Header*>(ptr);
|
||||
if (isBig)
|
||||
header.swapBig();
|
||||
const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + header.m_trackIdxOff);
|
||||
const uint32_t* regionIdxTable = reinterpret_cast<const uint32_t*>(ptr + header.m_regionIdxOff);
|
||||
|
||||
/* First determine maximum index of MIDI regions across all tracks */
|
||||
uint32_t maxRegionIdx = 0;
|
||||
for (int i=0 ; i<64 ; ++i)
|
||||
{
|
||||
if (trackIdx[i])
|
||||
{
|
||||
const TrackRegion* region = nullptr;
|
||||
const TrackRegion* nextRegion = reinterpret_cast<const TrackRegion*>(ptr + (isBig ? SBig(trackIdx[i]) : trackIdx[i]));
|
||||
|
||||
/* Iterate all regions */
|
||||
while (nextRegion->indexValid(isBig))
|
||||
{
|
||||
region = nextRegion;
|
||||
uint32_t regionIdx = (isBig ? SBig(region->m_regionIndex) :
|
||||
region->m_regionIndex);
|
||||
maxRegionIdx = std::max(maxRegionIdx, regionIdx);
|
||||
nextRegion = ®ion[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform 2 trials, first assuming revised format (more likely) */
|
||||
int v=1;
|
||||
for (; v>=0 ; --v)
|
||||
{
|
||||
bool bad = false;
|
||||
|
||||
/* Validate all tracks */
|
||||
for (int i=0 ; i<64 ; ++i)
|
||||
{
|
||||
if (trackIdx[i])
|
||||
{
|
||||
const TrackRegion* region = nullptr;
|
||||
const TrackRegion* nextRegion = reinterpret_cast<const TrackRegion*>(ptr + (isBig ? SBig(trackIdx[i]) : trackIdx[i]));
|
||||
|
||||
/* Iterate all regions */
|
||||
while (nextRegion->indexValid(isBig))
|
||||
{
|
||||
region = nextRegion;
|
||||
uint32_t regionIdx = (isBig ? SBig(region->m_regionIndex) :
|
||||
region->m_regionIndex);
|
||||
nextRegion = ®ion[1];
|
||||
|
||||
const unsigned char* data = ptr + (isBig ? SBig(regionIdxTable[regionIdx]) :
|
||||
regionIdxTable[regionIdx]);
|
||||
|
||||
/* Can't reliably validate final region */
|
||||
if (regionIdx == maxRegionIdx)
|
||||
continue;
|
||||
|
||||
/* Expected end pointer (next region) */
|
||||
const unsigned char* expectedEnd = ptr + (isBig ? SBig(regionIdxTable[regionIdx+1]) :
|
||||
regionIdxTable[regionIdx+1]);
|
||||
|
||||
Track::Header header = *reinterpret_cast<const Track::Header*>(data);
|
||||
if (isBig)
|
||||
header.swapBig();
|
||||
data += 12;
|
||||
|
||||
/* continuous pitch data */
|
||||
if (header.m_pitchOff)
|
||||
{
|
||||
const unsigned char* dptr = ptr + header.m_pitchOff;
|
||||
while (DecodeRLE(dptr) != 0xffffffff) {DecodeContinuousRLE(dptr);}
|
||||
if (dptr >= (expectedEnd - 4) && (dptr <= expectedEnd))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* continuous modulation data */
|
||||
if (header.m_modOff)
|
||||
{
|
||||
const unsigned char* dptr = ptr + header.m_modOff;
|
||||
while (DecodeRLE(dptr) != 0xffffffff) {DecodeContinuousRLE(dptr);}
|
||||
if (dptr >= (expectedEnd - 4) && (dptr <= expectedEnd))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Loop through as many commands as we can for this time period */
|
||||
if (v == 1)
|
||||
{
|
||||
/* Revised */
|
||||
while (true)
|
||||
{
|
||||
/* Delta time */
|
||||
DecodeTimeRLE(data);
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(data) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
data += 2;
|
||||
break;
|
||||
}
|
||||
else if (data[0] & 0x80 && data[1] & 0x80)
|
||||
{
|
||||
/* Control change */
|
||||
data += 2;
|
||||
}
|
||||
else if (data[0] & 0x80)
|
||||
{
|
||||
/* Program change */
|
||||
data += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Note */
|
||||
data += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Legacy */
|
||||
while (true)
|
||||
{
|
||||
/* Delta-time */
|
||||
data += 4;
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(&data[2]) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
data += 4;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((data[2] & 0x80) != 0x80)
|
||||
{
|
||||
/* Note */
|
||||
}
|
||||
else if (data[2] & 0x80 && data[3] & 0x80)
|
||||
{
|
||||
/* Control change */
|
||||
}
|
||||
else if (data[2] & 0x80)
|
||||
{
|
||||
/* Program change */
|
||||
}
|
||||
data += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data < (expectedEnd - 4) || (data > expectedEnd))
|
||||
{
|
||||
bad = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bad)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bad)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
bool SongState::initialize(const unsigned char* ptr)
|
||||
{
|
||||
m_sngVersion = DetectVersion(ptr, m_bigEndian);
|
||||
if (m_sngVersion < 0)
|
||||
return false;
|
||||
|
||||
m_songData = ptr;
|
||||
m_header = *reinterpret_cast<const Header*>(ptr);
|
||||
if (m_bigEndian)
|
||||
@@ -177,9 +354,11 @@ void SongState::initialize(const unsigned char* ptr)
|
||||
else
|
||||
m_tempoPtr = nullptr;
|
||||
|
||||
m_tempo = m_header.m_initialTempo;
|
||||
m_tempo = m_header.m_initialTempo & 0x7fffffff;
|
||||
m_curTick = 0;
|
||||
m_songState = SongPlayState::Playing;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
|
||||
@@ -281,9 +460,9 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
|
||||
}
|
||||
|
||||
/* Loop through as many commands as we can for this time period */
|
||||
if (m_parent.m_header.m_trackIdxOff == 0x18 || m_parent.m_header.m_trackIdxOff == 0x58)
|
||||
if (m_parent.m_sngVersion == 1)
|
||||
{
|
||||
/* GameCube */
|
||||
/* Revision */
|
||||
while (true)
|
||||
{
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
@@ -335,7 +514,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
|
||||
}
|
||||
else
|
||||
{
|
||||
/* N64 */
|
||||
/* Legacy */
|
||||
while (true)
|
||||
{
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
@@ -425,7 +604,7 @@ bool SongState::advance(Sequencer& seq, double dt)
|
||||
if (remTicks <= 0)
|
||||
{
|
||||
/* Turn over tempo */
|
||||
m_tempo = change.m_tempo;
|
||||
m_tempo = change.m_tempo & 0x7fffffff;
|
||||
seq.setTempo(m_tempo * 384 / 60);
|
||||
++m_tempoPtr;
|
||||
continue;
|
||||
|
||||
160
lib/Voice.cpp
160
lib/Voice.cpp
@@ -115,7 +115,7 @@ void Voice::_doKeyOff()
|
||||
|
||||
void Voice::_setTotalPitch(int32_t cents, bool slew)
|
||||
{
|
||||
//fprintf(stderr, "PITCH %d\n", cents);
|
||||
//fprintf(stderr, "PITCH %d %d \n", cents, slew);
|
||||
int32_t interval = cents - m_curSample->first.m_pitch * 100;
|
||||
double ratio = std::exp2(interval / 1200.0);
|
||||
m_sampleRate = m_curSample->first.m_sampleRate * ratio;
|
||||
@@ -199,7 +199,7 @@ static void ApplyVolume(float vol, int16_t& samp)
|
||||
samp *= VolumeLUT[int(vol * 65536)];
|
||||
}
|
||||
|
||||
bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
void Voice::_advanceSample(int16_t& samp)
|
||||
{
|
||||
double dt;
|
||||
|
||||
@@ -223,7 +223,7 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
|
||||
/* Apply total volume to sample using decibel scale */
|
||||
ApplyVolume(l, samp);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
dt = 160.0 / m_sampleRate;
|
||||
@@ -232,7 +232,6 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
}
|
||||
|
||||
m_voiceTime += dt;
|
||||
bool refresh = false;
|
||||
|
||||
/* Process active envelope */
|
||||
if (m_envelopeTime >= 0.0)
|
||||
@@ -255,40 +254,6 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
/* Dynamically evaluate per-sample SoundMacro parameters */
|
||||
float evalVol = m_state.m_volumeSel ? (m_state.m_volumeSel.evaluate(*this, m_state) / 2.f * m_curVol) : m_curVol;
|
||||
|
||||
bool panDirty = false;
|
||||
if (m_state.m_panSel)
|
||||
{
|
||||
float evalPan = m_state.m_panSel.evaluate(*this, m_state);
|
||||
if (evalPan != m_curPan)
|
||||
{
|
||||
m_curPan = evalPan;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (m_state.m_spanSel)
|
||||
{
|
||||
float evalSpan = m_state.m_spanSel.evaluate(*this, m_state);
|
||||
if (evalSpan != m_curSpan)
|
||||
{
|
||||
m_curSpan = evalSpan;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (m_state.m_reverbSel)
|
||||
{
|
||||
float evalRev = m_state.m_reverbSel.evaluate(*this, m_state) / 2.f;
|
||||
if (evalRev != m_curReverbVol)
|
||||
{
|
||||
m_curReverbVol = evalRev;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (panDirty)
|
||||
_setPan(m_curPan);
|
||||
|
||||
if (m_state.m_pitchWheelSel)
|
||||
_setPitchWheel(m_state.m_pitchWheelSel.evaluate(*this, m_state));
|
||||
|
||||
/* Process user volume slew */
|
||||
if (m_engine.m_ampMode == AmplitudeMode::PerSample)
|
||||
{
|
||||
@@ -351,8 +316,70 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
|
||||
/* Apply total volume to sample using decibel scale */
|
||||
ApplyVolume(m_nextLevel, samp);
|
||||
}
|
||||
|
||||
uint32_t Voice::_GetBlockSampleCount(SampleFormat fmt)
|
||||
{
|
||||
switch (fmt)
|
||||
{
|
||||
default:
|
||||
return 1;
|
||||
case Voice::SampleFormat::DSP:
|
||||
return 14;
|
||||
case Voice::SampleFormat::N64:
|
||||
return 64;
|
||||
}
|
||||
}
|
||||
|
||||
void Voice::preSupplyAudio(double dt)
|
||||
{
|
||||
/* Process SoundMacro; bootstrapping sample if needed */
|
||||
bool dead = m_state.advance(*this, dt);
|
||||
|
||||
/* Process per-block evaluators here */
|
||||
if (m_state.m_pedalSel)
|
||||
{
|
||||
bool pedal = m_state.m_pedalSel.evaluate(*this, m_state) >= 1.f;
|
||||
if (pedal != m_sustained)
|
||||
setPedal(pedal);
|
||||
}
|
||||
|
||||
bool panDirty = false;
|
||||
if (m_state.m_panSel)
|
||||
{
|
||||
float evalPan = m_state.m_panSel.evaluate(*this, m_state);
|
||||
if (evalPan != m_curPan)
|
||||
{
|
||||
m_curPan = evalPan;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (m_state.m_spanSel)
|
||||
{
|
||||
float evalSpan = m_state.m_spanSel.evaluate(*this, m_state);
|
||||
if (evalSpan != m_curSpan)
|
||||
{
|
||||
m_curSpan = evalSpan;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (m_state.m_reverbSel)
|
||||
{
|
||||
float evalRev = m_state.m_reverbSel.evaluate(*this, m_state) / 2.f;
|
||||
if (evalRev != m_curReverbVol)
|
||||
{
|
||||
m_curReverbVol = evalRev;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (panDirty)
|
||||
_setPan(m_curPan);
|
||||
|
||||
if (m_state.m_pitchWheelSel)
|
||||
_setPitchWheel(m_state.m_pitchWheelSel.evaluate(*this, m_state));
|
||||
|
||||
/* Process active pan-sweep */
|
||||
bool refresh = false;
|
||||
if (m_panningTime >= 0.f)
|
||||
{
|
||||
m_panningTime += dt;
|
||||
@@ -383,7 +410,7 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
}
|
||||
|
||||
/* Calculate total pitch */
|
||||
newPitch = m_curPitch;
|
||||
int32_t newPitch = m_curPitch;
|
||||
refresh |= m_pitchDirty;
|
||||
m_pitchDirty = false;
|
||||
if (m_portamentoTime >= 0.f)
|
||||
@@ -423,20 +450,19 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
refresh = true;
|
||||
}
|
||||
|
||||
/* True if backend voice needs reconfiguration before next sample */
|
||||
return refresh;
|
||||
if (m_curSample && refresh)
|
||||
{
|
||||
_setTotalPitch(newPitch + m_pitchSweep1 + m_pitchSweep2 + m_pitchWheelVal, m_needsSlew);
|
||||
m_needsSlew = true;
|
||||
}
|
||||
|
||||
uint32_t Voice::_GetBlockSampleCount(SampleFormat fmt)
|
||||
if (dead && (!m_curSample || m_voxState == VoiceState::KeyOff) &&
|
||||
m_sampleEndTrap.macroId == 0xffff &&
|
||||
m_messageTrap.macroId == 0xffff &&
|
||||
(!m_curSample || (m_curSample && m_volAdsr.isComplete())))
|
||||
{
|
||||
switch (fmt)
|
||||
{
|
||||
default:
|
||||
return 1;
|
||||
case Voice::SampleFormat::DSP:
|
||||
return 14;
|
||||
case Voice::SampleFormat::N64:
|
||||
return 64;
|
||||
m_voxState = VoiceState::Dead;
|
||||
m_backendVoice->stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,23 +471,10 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
|
||||
uint32_t samplesRem = samples;
|
||||
size_t samplesProc = 0;
|
||||
|
||||
/* Process SoundMacro; bootstrapping sample if needed */
|
||||
bool dead = m_state.advance(*this, samples / m_sampleRate);
|
||||
|
||||
/* Process per-block evaluators here */
|
||||
if (m_state.m_pedalSel)
|
||||
{
|
||||
bool pedal = m_state.m_pedalSel.evaluate(*this, m_state) >= 1.f;
|
||||
if (pedal != m_sustained)
|
||||
setPedal(pedal);
|
||||
}
|
||||
|
||||
if (m_curSample)
|
||||
{
|
||||
uint32_t blockSampleCount = _GetBlockSampleCount(m_curFormat);
|
||||
uint32_t block;
|
||||
int32_t curPitch = m_curPitch;
|
||||
bool refresh = false;
|
||||
|
||||
bool looped = true;
|
||||
while (looped && samplesRem)
|
||||
@@ -517,7 +530,7 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
|
||||
{
|
||||
++samplesProc;
|
||||
++m_curSamplePos;
|
||||
refresh |= _advanceSample(data[i], curPitch);
|
||||
_advanceSample(data[i]);
|
||||
}
|
||||
|
||||
samplesRem -= decSamples;
|
||||
@@ -582,7 +595,7 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
|
||||
{
|
||||
++samplesProc;
|
||||
++m_curSamplePos;
|
||||
refresh |= _advanceSample(data[i], curPitch);
|
||||
_advanceSample(data[i]);
|
||||
}
|
||||
|
||||
samplesRem -= decSamples;
|
||||
@@ -598,22 +611,12 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh)
|
||||
_setTotalPitch(curPitch + m_pitchSweep1 + m_pitchSweep2 + m_pitchWheelVal, true);
|
||||
}
|
||||
else
|
||||
memset(data, 0, sizeof(int16_t) * samples);
|
||||
|
||||
if (dead && (!m_curSample || m_voxState == VoiceState::KeyOff) &&
|
||||
m_sampleEndTrap.macroId == 0xffff &&
|
||||
m_messageTrap.macroId == 0xffff &&
|
||||
(!m_curSample || (m_curSample && m_volAdsr.isComplete())))
|
||||
{
|
||||
if (m_voxState == VoiceState::Dead)
|
||||
m_curSample = nullptr;
|
||||
m_voxState = VoiceState::Dead;
|
||||
m_backendVoice->stop();
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
@@ -795,6 +798,7 @@ void Voice::startSample(int16_t sampId, int32_t offset)
|
||||
m_pitchDirty = true;
|
||||
_setPitchWheel(m_curPitchWheel);
|
||||
m_backendVoice->resetSampleRate(m_curSample->first.m_sampleRate);
|
||||
m_needsSlew = false;
|
||||
|
||||
int32_t numSamples = m_curSample->first.m_numSamples & 0xffffff;
|
||||
if (offset)
|
||||
@@ -1001,7 +1005,7 @@ void Voice::setPitchSweep1(uint8_t times, int16_t add)
|
||||
{
|
||||
m_pitchSweep1 = 0;
|
||||
m_pitchSweep1It = 0;
|
||||
m_pitchSweep1Times = times * 160;
|
||||
m_pitchSweep1Times = times;
|
||||
m_pitchSweep1Add = add;
|
||||
}
|
||||
|
||||
@@ -1009,7 +1013,7 @@ void Voice::setPitchSweep2(uint8_t times, int16_t add)
|
||||
{
|
||||
m_pitchSweep2 = 0;
|
||||
m_pitchSweep2It = 0;
|
||||
m_pitchSweep2Times = times * 160;
|
||||
m_pitchSweep2Times = times;
|
||||
m_pitchSweep2Add = add;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user