15 Commits
v1.2 ... v1.7

Author SHA1 Message Date
Jack Andersen
fe78a675d7 Change default volume to 80% to fill newfound headroom 2016-07-03 17:35:37 -10:00
Jack Andersen
3427515960 Add Starfox Adventures midi.wad support 2016-07-03 12:41:31 -10:00
Jack Andersen
5ad8c06b99 add SongState::DetectVersion for much less hacky version-detection 2016-07-02 11:50:38 -10:00
Jack Andersen
e99dbc7e0a Initialization fix in SongConverter as indicated by valgrind 2016-06-30 09:30:13 -10:00
Jack Andersen
d6b9d4fca1 PC SNG decoder fixes 2016-06-27 12:43:04 -10:00
Jack Andersen
c7f093c5ee Minor adjustments to test PC SNG data extract 2016-06-27 11:02:10 -10:00
Jack Andersen
22a8534887 Support for N64 SNG Control and Program Change encoding 2016-06-22 14:28:02 -10:00
Jack Andersen
0c606fa9b7 Windows Fixes 2016-06-22 12:15:53 -10:00
Jack Andersen
a0241574ba Plenty of SongConverter bug fixes 2016-06-22 11:43:45 -10:00
Jack Andersen
bd10015024 Work on MIDI-to-SNG conversion 2016-06-21 18:18:28 -10:00
Jack Andersen
a0bb35433a Initial amuseconv implementation with SNG extraction 2016-06-20 20:40:13 -10:00
Jack Andersen
3bc47baa1d N64 SNG fixes 2016-06-19 22:08:32 -10:00
Jack Andersen
7666b51c50 Merge branch 'master' of https://github.com/AxioDL/amuse 2016-06-19 17:36:32 -10:00
Jack Andersen
ee29fb4b1e Initial N64 SNG support; pitch-wheel fix 2016-06-19 17:35:57 -10:00
Jack Andersen
ad23f9d0c4 Symbol pollution is bad for your health 2016-06-19 09:19:16 -10:00
19 changed files with 2872 additions and 183 deletions

View File

@@ -49,7 +49,7 @@ void RegisterAudioUnit();
- (nullable id)initWithComponentDescription:(AudioComponentDescription)componentDescription
error:(NSError * __nullable * __nonnull)outError
viewController:(AudioUnitViewController* __nonnull)vc;
- (void)requestAudioGroup:(AudioGroupToken*)group;
- (void)requestAudioGroup:(AudioGroupToken* _Nonnull)group;
@end
#endif

View File

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

View File

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

217
driver/amuseconv.cpp Normal file
View File

@@ -0,0 +1,217 @@
#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("amuseconv");
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;
}
}
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(targetPath.c_str(), 0755);
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());
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);
}
}
return true;
}
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)
return false;
fseek(fp, 0, SEEK_END);
long sz = ftell(fp);
fseek(fp, 0, SEEK_SET);
std::vector<uint8_t> data(sz, 0);
fread(&data[0], 1, sz, fp);
fclose(fp);
std::vector<uint8_t> out = amuse::SongConverter::MIDIToSong(data, version, big);
if (out.empty())
return false;
fp = amuse::FOpen(targetPath.c_str(), _S("wb"));
fwrite(out.data(), 1, out.size(), fp);
fclose(fp);
return true;
}
static bool ExtractSNG(const amuse::SystemString& inPath, const amuse::SystemString& targetPath)
{
FILE* fp = amuse::FOpen(inPath.c_str(), _S("rb"));
if (!fp)
return false;
fseek(fp, 0, SEEK_END);
long sz = ftell(fp);
fseek(fp, 0, SEEK_SET);
std::vector<uint8_t> data(sz, 0);
fread(&data[0], 1, sz, fp);
fclose(fp);
int extractedVersion;
bool isBig;
std::vector<uint8_t> out = amuse::SongConverter::SongToMIDI(data.data(), extractedVersion, isBig);
if (out.empty())
return false;
fp = amuse::FOpen(targetPath.c_str(), _S("wb"));
fwrite(out.data(), 1, out.size(), fp);
fclose(fp);
return true;
}
#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);
good = BuildSNG(barePath, argv[2], 1, true);
}
else if (!amuse::CompareCaseInsensitive(dot, _S(".son")) ||
!amuse::CompareCaseInsensitive(dot, _S(".sng")))
{
good = ExtractSNG(argv[1], argv[2]);
}
else
{
good = ExtractAudioGroup(argv[1], argv[2]);
}
}
}
else
{
amuse::Sstat theStat;
if (!amuse::Stat(argv[1], &theStat) && S_ISDIR(theStat.st_mode))
{
amuse::SystemString projectPath(argv[1]);
projectPath += _S("/project.yaml");
fin = amuse::FOpen(projectPath.c_str(), _S("rb"));
if (fin)
{
fclose(fin);
ReportConvType(type);
good = BuildAudioGroup(argv[1], argv[2]);
}
}
}
if (!good)
{
Log.report(logvisor::Error, _S("unable to convert %s to %s"), argv[1], argv[2]);
return 1;
}
return 0;
}

View File

@@ -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())
@@ -782,9 +819,11 @@ struct AppCallback : boo::IApplicationCallback
}
for (const auto& pair : allSongGroups)
{
amuse::Printf(_S(" %d %s (SongGroup) %" PRISize " normal-pages, %" PRISize " drum-pages\n"),
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_normPages.size(),
pair.second.second->m_drumPages.size(),
pair.second.second->m_midiSetups.size());
}
int userSel = 0;

View File

@@ -11,6 +11,7 @@
#ifndef _MSC_VER
#include <strings.h>
#include <sys/stat.h>
#endif
namespace amuse
@@ -30,12 +31,18 @@ namespace amuse
# ifndef _S
# define _S(val) L ## val
# endif
typedef struct _stat Sstat;
static inline int Mkdir(const wchar_t* path, int) {return _wmkdir(path);}
static inline int Stat(const wchar_t* path, Sstat* statout) {return _wstat(path, statout);}
#else
using SystemString = std::string;
using SystemChar = char;
# ifndef _S
# define _S(val) val
# endif
typedef struct stat Sstat;
static inline int Mkdir(const char* path, mode_t mode) {return mkdir(path, mode);}
static inline int Stat(const char* path, Sstat* statout) {return stat(path, statout);}
#endif
#if _WIN32

View File

@@ -0,0 +1,74 @@
#ifndef __AMUSE_DIRECTORY_ENUMERATOR__
#define __AMUSE_DIRECTORY_ENUMERATOR__
#include "Common.hpp"
#include <vector>
namespace amuse
{
struct CaseInsensitiveCompare
{
bool operator()(const std::string& lhs, const std::string& rhs) const
{
#if _WIN32
if (_stricmp(lhs.c_str(), rhs.c_str()) < 0)
#else
if (strcasecmp(lhs.c_str(), rhs.c_str()) < 0)
#endif
return true;
return false;
}
#if _WIN32
bool operator()(const std::wstring& lhs, const std::wstring& rhs) const
{
if (_wcsicmp(lhs.c_str(), rhs.c_str()) < 0)
return true;
return false;
}
#endif
};
class DirectoryEnumerator
{
public:
enum class Mode
{
Native,
DirsSorted,
FilesSorted,
DirsThenFilesSorted
};
struct Entry
{
SystemString m_path;
SystemString m_name;
size_t m_fileSz;
bool m_isDir;
private:
friend class DirectoryEnumerator;
Entry(SystemString&& path, const SystemChar* name, size_t sz, bool isDir)
: m_path(std::move(path)), m_name(name), m_fileSz(sz), m_isDir(isDir) {}
};
private:
std::vector<Entry> m_entries;
public:
DirectoryEnumerator(const SystemString& path, Mode mode=Mode::DirsThenFilesSorted,
bool sizeSort=false, bool reverse=false, bool noHidden=false)
: DirectoryEnumerator(path.c_str(), mode, sizeSort, reverse, noHidden) {}
DirectoryEnumerator(const SystemChar* path, Mode mode=Mode::DirsThenFilesSorted,
bool sizeSort=false, bool reverse=false, bool noHidden=false);
operator bool() const {return m_entries.size() != 0;}
size_t size() const {return m_entries.size();}
std::vector<Entry>::const_iterator begin() const {return m_entries.cbegin();}
std::vector<Entry>::const_iterator end() const {return m_entries.cend();}
};
}
#endif // __AMUSE_DIRECTORY_ENUMERATOR__

View File

@@ -0,0 +1,19 @@
#ifndef __AMUSE_SONGCONVERTER_HPP__
#define __AMUSE_SONGCONVERTER_HPP__
#include <vector>
#include <stdint.h>
namespace amuse
{
class SongConverter
{
public:
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);
};
}
#endif // __AMUSE_SONGCONVERTER_HPP__

View File

@@ -22,34 +22,30 @@ enum class SongPlayState
class SongState
{
friend class Voice;
friend class SongConverter;
/** Song header */
struct Header
{
uint32_t m_version;
uint32_t m_chanIdxOff;
uint32_t m_trackIdxOff;
uint32_t m_regionIdxOff;
uint32_t m_chanMapOff;
uint32_t m_tempoTableOff;
uint32_t m_initialTempo;
uint32_t m_unkOff;
uint32_t m_chanOffs[64];
void swapBig();
} m_header;
/** Channel header */
struct ChanHeader
/** Track region ('clip' in an NLA representation) */
struct TrackRegion
{
uint32_t m_startTick;
uint16_t m_unk1;
uint8_t m_progNum;
uint8_t m_unk1;
uint16_t m_unk2;
uint16_t m_dataIndex;
uint16_t m_unk3;
uint32_t m_startTick2;
uint16_t m_unk4;
uint16_t m_unk5;
uint16_t m_unk6;
uint16_t m_unk7;
void swapBig();
int16_t m_regionIndex;
int16_t m_unk3;
bool indexValid(bool bigEndian) const;
};
/** Tempo change entry */
@@ -60,8 +56,12 @@ class SongState
void swapBig();
};
/** State of a single channel within arrangement */
struct Channel
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 */
struct Track
{
struct Header
{
@@ -73,25 +73,28 @@ class SongState
SongState& m_parent;
uint8_t m_midiChan; /**< MIDI channel number of song channel */
uint32_t m_startTick; /**< Tick to start execution of channel commands */
const TrackRegion* m_curRegion; /**< Pointer to currently-playing track region */
const TrackRegion* m_nextRegion; /**< Pointer to next-queued track region */
const unsigned char* m_dataBase; /**< Base pointer to command data */
const unsigned char* m_data; /**< Pointer to upcoming command data */
const unsigned char* m_data = nullptr; /**< Pointer to upcoming command data */
const unsigned char* m_pitchWheelData = nullptr; /**< Pointer to upcoming pitch data */
const unsigned char* m_modWheelData = nullptr; /**< Pointer to upcoming modulation data */
uint32_t m_lastPitchTick = 0; /**< Last position of pitch wheel change */
int32_t m_lastPitchVal = 0; /**< Last value of pitch */
uint32_t m_lastModTick = 0; /**< Last position of mod wheel change */
int32_t m_lastModVal = 0; /**< Last value of mod */
std::array<uint16_t, 128> m_remNoteLengths = {}; /**< Remaining ticks per note */
std::array<int, 128> m_remNoteLengths; /**< Remaining ticks per note */
int32_t m_waitCountdown = 0; /**< Current wait in ticks */
int32_t m_eventWaitCountdown = 0; /**< Current wait in ticks */
int32_t m_lastN64EventTick = 0; /**< Last command time on this channel (for computing delta times from absolute times in N64 songs) */
Channel(SongState& parent, uint8_t midiChan, uint32_t startTick,
const unsigned char* song, const unsigned char* chan);
Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions);
void setRegion(Sequencer* seq, const TrackRegion* region);
void advanceRegion(Sequencer* seq);
bool advance(Sequencer& seq, int32_t ticks);
};
std::array<std::experimental::optional<Channel>, 64> m_channels;
std::array<std::experimental::optional<Track>, 64> m_tracks;
const uint32_t* m_regionIdx; /**< Table of offsets to song-region data */
/** Current pointer to tempo control, iterated over playback */
const TempoChange* m_tempoPtr = nullptr;
@@ -102,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

View File

@@ -159,6 +159,7 @@ class Voice : public Entity
void _setPan(float pan);
void _setSurroundPan(float span);
void _setPitchWheel(float pitchWheel);
void _notifyCtrlChange(uint8_t ctrl, int8_t val);
public:
~Voice();
@@ -307,12 +308,6 @@ public:
_notifyCtrlChange(ctrl, val);
}
/** Get ModWheel value on voice */
int8_t getModWheel() const
{
return m_state.m_modWheelSel ? m_state.m_modWheelSel.evaluate(*this, m_state) : getCtrlValue(1);
}
/** 'install' external MIDI controller storage */
void installCtrlValues(int8_t* cvs)
{
@@ -321,7 +316,7 @@ public:
}
/** Get MIDI pitch wheel value on voice */
int8_t getPitchWheel() const {return m_curPitchWheel * 127;}
float getPitchWheel() const {return m_curPitchWheel;}
/** Get MIDI aftertouch value on voice */
int8_t getAftertouch() const {return m_curAftertouch;}

View File

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

View File

@@ -7,10 +7,10 @@ IntrusiveAudioGroupData::~IntrusiveAudioGroupData()
{
if (m_owns)
{
delete m_pool;
delete m_proj;
delete m_sdir;
delete m_samp;
delete[] m_pool;
delete[] m_proj;
delete[] m_sdir;
delete[] m_samp;
}
}
@@ -27,10 +27,10 @@ IntrusiveAudioGroupData& IntrusiveAudioGroupData::operator=(IntrusiveAudioGroupD
{
if (m_owns)
{
delete m_pool;
delete m_proj;
delete m_sdir;
delete m_samp;
delete[] m_pool;
delete[] m_proj;
delete[] m_sdir;
delete[] m_samp;
}
m_owns = other.m_owns;

View File

@@ -35,7 +35,7 @@ static void *memmem(const void *haystack, size_t hlen, const void *needle, size_
return NULL;
}
amuse::SystemString StrToSys(const std::string& str)
static amuse::SystemString StrToSys(const std::string& str)
{
std::wstring ret;
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.size(), nullptr, 0);
@@ -45,7 +45,7 @@ amuse::SystemString StrToSys(const std::string& str)
}
#else
amuse::SystemString StrToSys(const std::string& str)
static amuse::SystemString StrToSys(const std::string& str)
{
return str;
}
@@ -725,6 +725,41 @@ static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadRS1PC(F
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)
{
size_t endPos = FileLength(fp);
@@ -890,6 +925,61 @@ static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadRS1N64(
return ret;
}
static std::vector<std::pair<SystemString, ContainerRegistry::SongData>> LoadRS1N64Songs(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 (strstr(ent.name, "SNG"))
{
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;
}
static bool ValidateBFNPC(FILE* fp)
{
size_t endPos = FileLength(fp);
@@ -996,6 +1086,41 @@ static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadBFNPC(F
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)
{
size_t endPos = FileLength(fp);
@@ -1161,6 +1286,61 @@ static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadBFNN64(
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
{
uint64_t offset;
@@ -1499,6 +1679,72 @@ 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;
}
ContainerRegistry::Type ContainerRegistry::DetectContainerType(const SystemChar* path)
{
FILE* fp;
@@ -1847,35 +2093,33 @@ ContainerRegistry::LoadSongs(const SystemChar* path)
return ret;
}
#if 0
if (ValidateRS1PCSongs(fp))
if (ValidateRS1PC(fp))
{
auto ret = LoadRS1PCSongs(fp);
fclose(fp);
return ret;
}
if (ValidateRS1N64Songs(fp))
if (ValidateRS1N64(fp))
{
auto ret = LoadRS1N64Songs(fp);
fclose(fp);
return ret;
}
if (ValidateBFNPCSongs(fp))
if (ValidateBFNPC(fp))
{
auto ret = LoadBFNPCSongs(fp);
fclose(fp);
return ret;
}
if (ValidateBFNN64Songs(fp))
if (ValidateBFNN64(fp))
{
auto ret = LoadBFNN64Songs(fp);
fclose(fp);
return ret;
}
#endif
if (ValidateRS2(fp))
{
@@ -1884,6 +2128,13 @@ ContainerRegistry::LoadSongs(const SystemChar* path)
return ret;
}
if (ValidateStarFoxAdvSongs(fp))
{
auto ret = LoadStarFoxAdvSongs(fp);
fclose(fp);
return ret;
}
fclose(fp);
}

287
lib/DirectoryEnumerator.cpp Normal file
View File

@@ -0,0 +1,287 @@
#ifdef _WIN32
#include <windows.h>
#include <stdio.h>
#else
#include <dirent.h>
#endif
#include <sys/stat.h>
#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
#if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#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
}
}

View File

@@ -72,15 +72,15 @@ Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId,
m_midiSetup = it->second->data();
m_submix = m_engine.addSubmix(smx);
m_submix->makeReverbHi(0.2f, 0.65f, 1.f, 0.5f, 0.f, 0.f);
m_submix->makeReverbHi(0.2f, 0.3f, 1.f, 0.5f, 0.f, 0.f);
}
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId,
const SFXGroupIndex* sfxGroup, Submix* smx)
: Entity(engine, group, groupId), m_sfxGroup(sfxGroup)
{
m_submix = m_engine.addSubmix(smx);
m_submix->makeReverbHi(0.2f, 0.65f, 1.f, 0.5f, 0.f, 0.f);
//m_submix = m_engine.addSubmix(smx);
//m_submix->makeReverbHi(0.2f, 0.3f, 1.f, 0.5f, 0.f, 0.f);
std::map<uint16_t, const SFXGroupIndex::SFXEntry*> sortSFX;
for (const auto& sfx : sfxGroup->m_sfxEntries)
@@ -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);

1484
lib/SongConverter.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,10 @@ static uint32_t DecodeRLE(const unsigned char*& data)
++data;
thisPart = thisPart * 256 + *data;
if (thisPart == 0)
{
++data;
return -1;
}
}
if (thisPart == 32767)
@@ -68,28 +71,17 @@ static uint32_t DecodeTimeRLE(const unsigned char*& data)
void SongState::Header::swapBig()
{
m_version = SBig(m_version);
m_chanIdxOff = SBig(m_chanIdxOff);
m_trackIdxOff = SBig(m_trackIdxOff);
m_regionIdxOff = SBig(m_regionIdxOff);
m_chanMapOff = SBig(m_chanMapOff);
m_tempoTableOff = SBig(m_tempoTableOff);
m_initialTempo = SBig(m_initialTempo);
m_unkOff = SBig(m_unkOff);
for (int i=0 ; i<64 ; ++i)
m_chanOffs[i] = SBig(m_chanOffs[i]);
}
void SongState::ChanHeader::swapBig()
bool SongState::TrackRegion::indexValid(bool bigEndian) const
{
m_startTick = SBig(m_startTick);
m_unk1 = SBig(m_unk1);
m_unk2 = SBig(m_unk2);
m_dataIndex = SBig(m_dataIndex);
m_unk3 = SBig(m_unk3);
m_startTick2 = SBig(m_startTick2);
m_unk4 = SBig(m_unk4);
m_unk5 = SBig(m_unk5);
m_unk6 = SBig(m_unk6);
m_unk7 = SBig(m_unk7);
return (bigEndian ? SBig(m_regionIndex) : m_regionIndex) >= 0;
}
void SongState::TempoChange::swapBig()
@@ -98,57 +90,262 @@ void SongState::TempoChange::swapBig()
m_tempo = SBig(m_tempo);
}
void SongState::Channel::Header::swapBig()
void SongState::Track::Header::swapBig()
{
m_type = SBig(m_type);
m_pitchOff = SBig(m_pitchOff);
m_modOff = SBig(m_modOff);
}
SongState::Channel::Channel(SongState& parent, uint8_t midiChan, uint32_t startTick,
const unsigned char* song, const unsigned char* chan)
: m_parent(parent), m_midiChan(midiChan), m_startTick(startTick), m_dataBase(chan + 12)
SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions)
: m_parent(parent), m_midiChan(midiChan), m_curRegion(nullptr), m_nextRegion(regions)
{
m_data = m_dataBase;
Header header = *reinterpret_cast<const Header*>(chan);
header.swapBig();
if (header.m_type != 8)
{
m_data = nullptr;
return;
}
if (header.m_pitchOff)
m_pitchWheelData = song + header.m_pitchOff;
if (header.m_modOff)
m_modWheelData = song + header.m_modOff;
m_waitCountdown = startTick;
m_lastPitchTick = startTick;
m_lastModTick = startTick;
m_waitCountdown += int32_t(DecodeTimeRLE(m_data));
for (int i=0 ; i<128 ; ++i)
m_remNoteLengths[i] = INT_MIN;
}
void SongState::initialize(const unsigned char* ptr)
void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
{
m_header = *reinterpret_cast<const Header*>(ptr);
m_header.swapBig();
m_curRegion = region;
uint32_t regionIdx = (m_parent.m_bigEndian ? SBig(m_curRegion->m_regionIndex) :
m_curRegion->m_regionIndex);
m_nextRegion = &m_curRegion[1];
/* Initialize all channels */
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);
if (m_parent.m_bigEndian)
header.swapBig();
m_data += 12;
if (header.m_pitchOff)
m_pitchWheelData = m_parent.m_songData + header.m_pitchOff;
if (header.m_modOff)
m_modWheelData = m_parent.m_songData + header.m_modOff;
m_eventWaitCountdown = 0;
m_lastPitchTick = m_parent.m_curTick;
m_lastPitchVal = 0;
m_lastModTick = m_parent.m_curTick;
m_lastModVal = 0;
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_sngVersion == 1)
m_eventWaitCountdown = int32_t(DecodeTimeRLE(m_data));
else
{
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 = absTick;
m_data += 4;
}
}
void SongState::Track::advanceRegion(Sequencer* seq)
{
setRegion(seq, m_nextRegion);
}
int SongState::DetectVersion(const unsigned char* ptr, bool& isBig)
{
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 (m_header.m_chanOffs[i])
if (trackIdx[i])
{
ChanHeader cHeader = *reinterpret_cast<const ChanHeader*>(ptr + m_header.m_chanOffs[i]);
cHeader.swapBig();
const uint32_t* chanIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_chanIdxOff);
const uint8_t* chanMap = reinterpret_cast<const uint8_t*>(ptr + m_header.m_chanMapOff);
m_channels[i].emplace(*this, chanMap[i], cHeader.m_startTick, ptr,
ptr + SBig(chanIdx[cHeader.m_dataIndex]));
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 = &region[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 = &region[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)
m_header.swapBig();
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);
const uint8_t* chanMap = reinterpret_cast<const uint8_t*>(ptr + m_header.m_chanMapOff);
/* Initialize all tracks */
for (int i=0 ; i<64 ; ++i)
{
if (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);
}
else
m_channels[i] = std::experimental::nullopt;
m_tracks[i] = std::experimental::nullopt;
}
/* Initialize tempo */
@@ -160,15 +357,42 @@ void SongState::initialize(const unsigned char* ptr)
m_tempo = m_header.m_initialTempo;
m_curTick = 0;
m_songState = SongPlayState::Playing;
return true;
}
bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
{
if (!m_data)
return true;
int32_t endTick = m_parent.m_curTick + ticks;
/* Advance region if needed */
while (m_nextRegion->indexValid(m_parent.m_bigEndian))
{
uint32_t nextRegTick = (m_parent.m_bigEndian ? SBig(m_nextRegion->m_startTick) :
m_nextRegion->m_startTick);
if (endTick > nextRegTick)
advanceRegion(&seq);
else
break;
}
/* Stop finished notes */
for (int i=0 ; i<128 ; ++i)
{
if (m_remNoteLengths[i] != INT_MIN)
{
m_remNoteLengths[i] -= ticks;
if (m_remNoteLengths[i] <= 0)
{
seq.keyOff(m_midiChan, i, 0);
m_remNoteLengths[i] = INT_MIN;
}
}
}
if (!m_data)
return !m_nextRegion->indexValid(m_parent.m_bigEndian);
/* Update continuous pitch data */
if (m_pitchWheelData)
{
@@ -224,7 +448,7 @@ bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
m_lastModTick = nextTick;
remModTicks -= (nextTick - modTick);
modTick = nextTick;
seq.setCtrlValue(m_midiChan, 1, clamp(0, (m_lastModVal + 8192) * 128 / 16384, 127));
seq.setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127));
continue;
}
remModTicks -= (nextTick - modTick);
@@ -235,61 +459,115 @@ bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
}
}
/* Stop finished notes */
for (int i=0 ; i<128 ; ++i)
/* Loop through as many commands as we can for this time period */
if (m_parent.m_sngVersion == 1)
{
if (m_remNoteLengths[i])
/* Revision */
while (true)
{
if (m_remNoteLengths[i] <= ticks)
/* Advance wait timer if active, returning if waiting */
if (m_eventWaitCountdown)
{
seq.keyOff(m_midiChan, i, 0);
m_remNoteLengths[i] = 0;
m_eventWaitCountdown -= ticks;
ticks = 0;
if (m_eventWaitCountdown > 0)
return false;
}
/* Load next command */
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
{
/* End of channel */
m_data = nullptr;
return !m_nextRegion->indexValid(m_parent.m_bigEndian);
}
else if (m_data[0] & 0x80 && m_data[1] & 0x80)
{
/* Control change */
uint8_t val = m_data[0] & 0x7f;
uint8_t ctrl = m_data[1] & 0x7f;
seq.setCtrlValue(m_midiChan, ctrl, val);
m_data += 2;
}
else if (m_data[0] & 0x80)
{
/* Program change */
uint8_t prog = m_data[0] & 0x7f;
seq.setChanProgram(m_midiChan, prog);
m_data += 2;
}
else
m_remNoteLengths[i] -= ticks;
{
/* Note */
uint8_t note = m_data[0] & 0x7f;
uint8_t vel = m_data[1] & 0x7f;
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);
m_remNoteLengths[note] = length;
m_data += 4;
}
/* Set next delta-time */
m_eventWaitCountdown += int32_t(DecodeTimeRLE(m_data));
}
}
/* Loop through as many commands as we can for this time period */
while (true)
else
{
/* Advance wait timer if active, returning if waiting */
if (m_waitCountdown)
/* Legacy */
while (true)
{
m_waitCountdown -= ticks;
ticks = 0;
if (m_waitCountdown > 0)
return false;
}
/* Advance wait timer if active, returning if waiting */
if (m_eventWaitCountdown)
{
m_eventWaitCountdown -= ticks;
ticks = 0;
if (m_eventWaitCountdown > 0)
return false;
}
/* Load next command */
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
{
/* End of channel */
m_data = nullptr;
return true;
}
else if (m_data[0] & 0x80)
{
/* Control change */
uint8_t val = m_data[0] & 0x7f;
uint8_t ctrl = m_data[1] & 0x7f;
seq.setCtrlValue(m_midiChan, ctrl, val);
m_data += 2;
}
else
{
/* Note */
uint8_t note = m_data[0] & 0x7f;
uint8_t vel = m_data[1] & 0x7f;
uint16_t length = SBig(*reinterpret_cast<const uint16_t*>(m_data + 2));
seq.keyOn(m_midiChan, note, vel);
m_remNoteLengths[note] = length;
/* Load next command */
if (*reinterpret_cast<const uint16_t*>(&m_data[2]) == 0xffff)
{
/* End of channel */
m_data = nullptr;
return !m_nextRegion->indexValid(m_parent.m_bigEndian);
}
else
{
if ((m_data[2] & 0x80) != 0x80)
{
/* Note */
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 vel = m_data[3] & 0x7f;
seq.keyOn(m_midiChan, note, vel);
m_remNoteLengths[note] = length;
}
else if (m_data[2] & 0x80 && m_data[3] & 0x80)
{
/* Control change */
uint8_t val = m_data[2] & 0x7f;
uint8_t ctrl = m_data[3] & 0x7f;
seq.setCtrlValue(m_midiChan, ctrl, val);
}
else if (m_data[2] & 0x80)
{
/* Program change */
uint8_t prog = m_data[2] & 0x7f;
seq.setChanProgram(m_midiChan, prog);
}
m_data += 4;
}
/* Set next delta-time */
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_lastN64EventTick = absTick;
m_data += 4;
}
/* Set next delta-time */
m_waitCountdown += int32_t(DecodeTimeRLE(m_data));
}
return false;
@@ -317,7 +595,8 @@ bool SongState::advance(Sequencer& seq, double dt)
if (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff)
{
TempoChange change = *m_tempoPtr;
change.swapBig();
if (m_bigEndian)
change.swapBig();
if (m_curTick + remTicks > change.m_tick)
remTicks = change.m_tick - m_curTick;
@@ -332,10 +611,10 @@ bool SongState::advance(Sequencer& seq, double dt)
}
}
/* Advance all channels */
for (std::experimental::optional<Channel>& chan : m_channels)
if (chan)
done &= chan->advance(seq, remTicks);
/* Advance all tracks */
for (std::experimental::optional<Track>& trk : m_tracks)
if (trk)
done &= trk->advance(seq, remTicks);
m_curTick += remTicks;

View File

@@ -52,21 +52,21 @@ float SoundMacroState::Evaluator::evaluate(const Voice& vox, const SoundMacroSta
break;
case 129:
/* Aftertouch */
thisValue = vox.getAftertouch();
thisValue = vox.getAftertouch() * (2.f / 127.f);
break;
case 130:
/* LFO1 */
if (vox.m_lfoPeriods[0])
thisValue = (std::sin(vox.m_voiceTime / vox.m_lfoPeriods[0] * 2.f * M_PIF) / 2.f + 1.f) * 127.f;
thisValue = std::sin(vox.m_voiceTime / vox.m_lfoPeriods[0] * 2.f * M_PIF);
break;
case 131:
/* LFO2 */
if (vox.m_lfoPeriods[1])
thisValue = (std::sin(vox.m_voiceTime / vox.m_lfoPeriods[1] * 2.f * M_PIF) / 2.f + 1.f) * 127.f;
thisValue = std::sin(vox.m_voiceTime / vox.m_lfoPeriods[1] * 2.f * M_PIF);
break;
case 132:
/* Surround panning */
thisValue = vox.m_curSpan * 64.f + 64.f;
thisValue = vox.m_curSpan;
break;
case 133:
/* Macro-starting key */
@@ -78,10 +78,13 @@ float SoundMacroState::Evaluator::evaluate(const Voice& vox, const SoundMacroSta
break;
case 135:
/* Time since macro-start (ms) */
thisValue = st.m_execTime * 1000.f;
thisValue = clamp(0.f, float(st.m_execTime * 1000.f), 16383.f);
break;
default:
thisValue = vox.getCtrlValue(comp.m_midiCtrl);
if (comp.m_midiCtrl == 10) /* Centered pan computation */
thisValue = vox.getCtrlValue(comp.m_midiCtrl) * (2.f / 127.f) - 1.f;
else
thisValue = vox.getCtrlValue(comp.m_midiCtrl) * (2.f / 127.f);
break;
}
}

View File

@@ -253,12 +253,12 @@ 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) / 127.f) * m_curVol) : m_curVol;
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) - 64.f) / 64.f;
float evalPan = m_state.m_panSel.evaluate(*this, m_state);
if (evalPan != m_curPan)
{
m_curPan = evalPan;
@@ -267,7 +267,7 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
}
if (m_state.m_spanSel)
{
float evalSpan = (m_state.m_spanSel.evaluate(*this, m_state) - 64.f) / 64.f;
float evalSpan = m_state.m_spanSel.evaluate(*this, m_state);
if (evalSpan != m_curSpan)
{
m_curSpan = evalSpan;
@@ -276,7 +276,7 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
}
if (m_state.m_reverbSel)
{
float evalRev = m_state.m_reverbSel.evaluate(*this, m_state) / 127.f;
float evalRev = m_state.m_reverbSel.evaluate(*this, m_state) / 2.f;
if (evalRev != m_curReverbVol)
{
m_curReverbVol = evalRev;
@@ -287,7 +287,7 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
_setPan(m_curPan);
if (m_state.m_pitchWheelSel)
setPitchWheel(m_state.m_pitchWheelSel.evaluate(*this, m_state) / 127.f);
_setPitchWheel(m_state.m_pitchWheelSel.evaluate(*this, m_state));
/* Process user volume slew */
if (m_engine.m_ampMode == AmplitudeMode::PerSample)
@@ -326,11 +326,11 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
/* Apply tremolo */
if (m_state.m_tremoloSel && (m_tremoloScale || m_tremoloModScale))
{
float t = m_state.m_tremoloSel.evaluate(*this, m_state);
float t = m_state.m_tremoloSel.evaluate(*this, m_state) / 2.f;
if (m_tremoloScale && m_tremoloModScale)
{
float fac = (1.0f - t) + (m_tremoloScale * t);
float modT = getModWheel() / 127.f;
float modT = m_state.m_modWheelSel ? (m_state.m_modWheelSel.evaluate(*this, m_state) / 2.f) : (getCtrlValue(1) / 127.f);
float modFac = (1.0f - modT) + (m_tremoloModScale * modT);
m_nextLevel *= fac * modFac;
}
@@ -341,7 +341,7 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
}
else if (m_tremoloModScale)
{
float modT = getModWheel() / 127.f;
float modT = m_state.m_modWheelSel ? (m_state.m_modWheelSel.evaluate(*this, m_state) / 2.f) : (getCtrlValue(1) / 127.f);
float modFac = (1.0f - modT) + (m_tremoloModScale * modT);
m_nextLevel *= modFac;
}
@@ -451,7 +451,7 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
/* Process per-block evaluators here */
if (m_state.m_pedalSel)
{
bool pedal = m_state.m_pedalSel.evaluate(*this, m_state) >= 64;
bool pedal = m_state.m_pedalSel.evaluate(*this, m_state) >= 1.f;
if (pedal != m_sustained)
setPedal(pedal);
}
@@ -793,7 +793,7 @@ void Voice::startSample(int16_t sampId, int32_t offset)
m_sampleRate = m_curSample->first.m_sampleRate;
m_curPitch = m_curSample->first.m_pitch;
m_pitchDirty = true;
setPitchWheel(m_curPitchWheel);
_setPitchWheel(m_curPitchWheel);
m_backendVoice->resetSampleRate(m_curSample->first.m_sampleRate);
int32_t numSamples = m_curSample->first.m_numSamples & 0xffffff;
@@ -1063,9 +1063,8 @@ void Voice::setPitchAdsr(ObjectId adsrId, int32_t cents)
}
}
void Voice::setPitchWheel(float pitchWheel)
void Voice::_setPitchWheel(float pitchWheel)
{
m_curPitchWheel = amuse::clamp(-1.f, pitchWheel, 1.f);
if (pitchWheel > 0.f)
m_pitchWheelVal = m_pitchWheelUp * m_curPitchWheel;
else if (pitchWheel < 0.f)
@@ -1073,6 +1072,12 @@ void Voice::setPitchWheel(float pitchWheel)
else
m_pitchWheelVal = 0;
m_pitchDirty = true;
}
void Voice::setPitchWheel(float pitchWheel)
{
m_curPitchWheel = amuse::clamp(-1.f, pitchWheel, 1.f);
_setPitchWheel(m_curPitchWheel);
for (std::shared_ptr<Voice>& vox : m_childVoices)
vox->setPitchWheel(pitchWheel);
@@ -1082,7 +1087,7 @@ void Voice::setPitchWheelRange(int8_t up, int8_t down)
{
m_pitchWheelUp = up * 100;
m_pitchWheelDown = down * 100;
setPitchWheel(m_curPitchWheel);
_setPitchWheel(m_curPitchWheel);
}
void Voice::setAftertouch(uint8_t aftertouch)
@@ -1105,7 +1110,7 @@ bool Voice::doPortamento(uint8_t newNote)
pState = true;
break;
case 2:
pState = (m_state.m_portamentoSel ? m_state.m_portamentoSel.evaluate(*this, m_state) : getCtrlValue(65)) >= 64;
pState = m_state.m_portamentoSel ? (m_state.m_portamentoSel.evaluate(*this, m_state) >= 1.f) : (getCtrlValue(65) >= 64);
break;
}