mirror of
https://github.com/AxioDL/amuse.git
synced 2025-12-08 21:17:49 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52cba61f76 | ||
|
|
fe78a675d7 | ||
|
|
3427515960 | ||
|
|
5ad8c06b99 | ||
|
|
e99dbc7e0a | ||
|
|
d6b9d4fca1 | ||
|
|
c7f093c5ee | ||
|
|
22a8534887 | ||
|
|
0c606fa9b7 | ||
|
|
a0241574ba | ||
|
|
bd10015024 | ||
|
|
a0bb35433a | ||
|
|
3bc47baa1d | ||
|
|
7666b51c50 | ||
|
|
ee29fb4b1e | ||
|
|
ad23f9d0c4 |
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
217
driver/amuseconv.cpp
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
74
include/amuse/DirectoryEnumerator.hpp
Normal file
74
include/amuse/DirectoryEnumerator.hpp
Normal 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__
|
||||
@@ -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;
|
||||
|
||||
19
include/amuse/SongConverter.hpp
Normal file
19
include/amuse/SongConverter.hpp
Normal 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__
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
@@ -159,12 +160,17 @@ 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();
|
||||
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);
|
||||
@@ -307,12 +313,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 +321,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;}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
287
lib/DirectoryEnumerator.cpp
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
1484
lib/SongConverter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 = ®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)
|
||||
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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
187
lib/Voice.cpp
187
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)
|
||||
@@ -253,41 +252,7 @@ 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;
|
||||
|
||||
bool panDirty = false;
|
||||
if (m_state.m_panSel)
|
||||
{
|
||||
float evalPan = (m_state.m_panSel.evaluate(*this, m_state) - 64.f) / 64.f;
|
||||
if (evalPan != m_curPan)
|
||||
{
|
||||
m_curPan = evalPan;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (m_state.m_spanSel)
|
||||
{
|
||||
float evalSpan = (m_state.m_spanSel.evaluate(*this, m_state) - 64.f) / 64.f;
|
||||
if (evalSpan != m_curSpan)
|
||||
{
|
||||
m_curSpan = evalSpan;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (m_state.m_reverbSel)
|
||||
{
|
||||
float evalRev = m_state.m_reverbSel.evaluate(*this, m_state) / 127.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) / 127.f);
|
||||
float evalVol = m_state.m_volumeSel ? (m_state.m_volumeSel.evaluate(*this, m_state) / 2.f * m_curVol) : m_curVol;
|
||||
|
||||
/* Process user volume slew */
|
||||
if (m_engine.m_ampMode == AmplitudeMode::PerSample)
|
||||
@@ -326,11 +291,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 +306,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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
uint32_t Voice::_GetBlockSampleCount(SampleFormat fmt)
|
||||
{
|
||||
switch (fmt)
|
||||
if (m_curSample && refresh)
|
||||
{
|
||||
default:
|
||||
return 1;
|
||||
case Voice::SampleFormat::DSP:
|
||||
return 14;
|
||||
case Voice::SampleFormat::N64:
|
||||
return 64;
|
||||
_setTotalPitch(newPitch + m_pitchSweep1 + m_pitchSweep2 + m_pitchWheelVal, m_needsSlew);
|
||||
m_needsSlew = true;
|
||||
}
|
||||
|
||||
if (dead && (!m_curSample || m_voxState == VoiceState::KeyOff) &&
|
||||
m_sampleEndTrap.macroId == 0xffff &&
|
||||
m_messageTrap.macroId == 0xffff &&
|
||||
(!m_curSample || (m_curSample && m_volAdsr.isComplete())))
|
||||
{
|
||||
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) >= 64;
|
||||
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;
|
||||
}
|
||||
@@ -793,8 +796,9 @@ 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);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1063,9 +1067,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 +1076,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 +1091,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 +1114,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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user