mirror of
https://github.com/AxioDL/amuse.git
synced 2025-12-08 13:14:58 +00:00
Looping SNG support; bug fixes
This commit is contained in:
@@ -10,8 +10,8 @@ namespace amuse
|
||||
|
||||
void AudioGroup::assign(const AudioGroupData& data)
|
||||
{
|
||||
m_proj = AudioGroupProject::CreateAudioGroupProject(data);
|
||||
m_pool = AudioGroupPool::CreateAudioGroupPool(data);
|
||||
m_proj = AudioGroupProject::CreateAudioGroupProject(data);
|
||||
m_sdir = AudioGroupSampleDirectory::CreateAudioGroupSampleDirectory(data);
|
||||
m_samp = data.getSamp();
|
||||
}
|
||||
|
||||
@@ -119,6 +119,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
|
||||
ObjectHeader<DNAE> objHead;
|
||||
atInt64 startPos = r.position();
|
||||
objHead.read(r);
|
||||
if (SoundMacroId::CurNameDB)
|
||||
SoundMacroId::CurNameDB->registerPair(
|
||||
NameDB::generateName(objHead.objectId, NameDB::Type::SoundMacro), objHead.objectId);
|
||||
auto& macro = ret.m_soundMacros[objHead.objectId.id];
|
||||
macro = MakeObj<SoundMacro>();
|
||||
macro->template readCmds<DNAE>(r, objHead.size - 8);
|
||||
@@ -134,6 +137,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
|
||||
ObjectHeader<DNAE> objHead;
|
||||
atInt64 startPos = r.position();
|
||||
objHead.read(r);
|
||||
if (TableId::CurNameDB)
|
||||
TableId::CurNameDB->registerPair(
|
||||
NameDB::generateName(objHead.objectId, NameDB::Type::Table), objHead.objectId);
|
||||
auto& ptr = ret.m_tables[objHead.objectId.id];
|
||||
switch (objHead.size)
|
||||
{
|
||||
@@ -163,6 +169,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
|
||||
ObjectHeader<DNAE> objHead;
|
||||
atInt64 startPos = r.position();
|
||||
objHead.read(r);
|
||||
if (KeymapId::CurNameDB)
|
||||
KeymapId::CurNameDB->registerPair(
|
||||
NameDB::generateName(objHead.objectId, NameDB::Type::Keymap), objHead.objectId);
|
||||
auto& km = ret.m_keymaps[objHead.objectId.id];
|
||||
km = MakeObj<std::array<Keymap, 128>>();
|
||||
for (int i = 0; i < 128; ++i)
|
||||
@@ -183,6 +192,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
|
||||
ObjectHeader<DNAE> objHead;
|
||||
atInt64 startPos = r.position();
|
||||
objHead.read(r);
|
||||
if (LayersId::CurNameDB)
|
||||
LayersId::CurNameDB->registerPair(
|
||||
NameDB::generateName(objHead.objectId, NameDB::Type::Layer), objHead.objectId);
|
||||
auto& lm = ret.m_layers[objHead.objectId.id];
|
||||
lm = MakeObj<std::vector<LayerMapping>>();
|
||||
uint32_t count;
|
||||
|
||||
@@ -103,6 +103,7 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag)
|
||||
if (GroupId::CurNameDB)
|
||||
GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId);
|
||||
|
||||
#if 0
|
||||
/* Sound Macros */
|
||||
r.seek(header.soundMacroIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
@@ -127,6 +128,7 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag)
|
||||
r.seek(header.layerIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<athena::Big>(LayersId::CurNameDB, r, NameDB::Type::Layer);
|
||||
#endif
|
||||
|
||||
if (header.type == GroupType::Song)
|
||||
{
|
||||
@@ -203,6 +205,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
|
||||
|
||||
GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId);
|
||||
|
||||
#if 0
|
||||
/* Sound Macros */
|
||||
r.seek(subDataOff + header.soundMacroIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
@@ -227,6 +230,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
|
||||
r.seek(subDataOff + header.layerIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<DNAE>(LayersId::CurNameDB, r, NameDB::Type::Layer);
|
||||
#endif
|
||||
|
||||
if (header.type == GroupType::Song)
|
||||
{
|
||||
@@ -288,7 +292,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
|
||||
|
||||
/* MIDI setups */
|
||||
r.seek(subDataOff + header.midiSetupsOff, athena::Begin);
|
||||
while (r.position() < groupBegin + header.groupEndOff)
|
||||
while (r.position() + 4 < groupBegin + header.groupEndOff)
|
||||
{
|
||||
uint16_t songId;
|
||||
athena::io::Read<athena::io::PropType::None>::Do<decltype(songId), DNAE>({}, songId, r);
|
||||
|
||||
@@ -210,6 +210,29 @@ void AudioGroupSampleDirectory::EntryData::loadLooseDSP(SystemStringView dspPath
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::EntryData::loadLooseVADPCM(SystemStringView vadpcmPath)
|
||||
{
|
||||
athena::io::FileReader r(vadpcmPath);
|
||||
if (!r.hasError())
|
||||
{
|
||||
VADPCMHeader header;
|
||||
header.read(r);
|
||||
m_pitch = header.m_pitchSampleRate >> 24;
|
||||
m_sampleRate = header.m_pitchSampleRate & 0xffff;
|
||||
m_numSamples = header.m_numSamples & 0xffff;
|
||||
m_numSamples |= atUint32(SampleFormat::N64) << 24;
|
||||
m_loopStartSample = header.m_loopStartSample;
|
||||
m_loopLengthSamples = header.m_loopLengthSamples;
|
||||
|
||||
uint32_t dataLen = 256 + (m_numSamples + 63) / 64 * 40;
|
||||
m_looseData.reset(new uint8_t[dataLen]);
|
||||
r.readUBytesToBuf(m_looseData.get(), dataLen);
|
||||
|
||||
memcpy(&m_ADPCMParms, m_looseData.get(), 256);
|
||||
m_ADPCMParms.swapBigVADPCM();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::EntryData::loadLooseWAV(SystemStringView wavPath)
|
||||
{
|
||||
athena::io::FileReader r(wavPath);
|
||||
@@ -263,9 +286,11 @@ void AudioGroupSampleDirectory::Entry::loadLooseData(SystemStringView basePath)
|
||||
{
|
||||
SystemString wavPath = SystemString(basePath) + _S(".wav");
|
||||
SystemString dspPath = SystemString(basePath) + _S(".dsp");
|
||||
Sstat wavStat, dspStat;
|
||||
SystemString vadpcmPath = SystemString(basePath) + _S(".vadpcm");
|
||||
Sstat wavStat, dspStat, vadpcmStat;
|
||||
bool wavValid = !Stat(wavPath.c_str(), &wavStat) && S_ISREG(wavStat.st_mode);
|
||||
bool dspValid = !Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode);
|
||||
bool vadpcmValid = !Stat(vadpcmPath.c_str(), &vadpcmStat) && S_ISREG(vadpcmStat.st_mode);
|
||||
|
||||
if (wavValid && dspValid)
|
||||
{
|
||||
@@ -274,6 +299,20 @@ void AudioGroupSampleDirectory::Entry::loadLooseData(SystemStringView basePath)
|
||||
else
|
||||
wavValid = false;
|
||||
}
|
||||
if (wavValid && vadpcmValid)
|
||||
{
|
||||
if (wavStat.st_mtime > vadpcmStat.st_mtime)
|
||||
vadpcmValid = false;
|
||||
else
|
||||
wavValid = false;
|
||||
}
|
||||
if (dspValid && vadpcmValid)
|
||||
{
|
||||
if (dspStat.st_mtime > vadpcmStat.st_mtime)
|
||||
vadpcmValid = false;
|
||||
else
|
||||
dspValid = false;
|
||||
}
|
||||
|
||||
EntryData& curData = *m_data;
|
||||
|
||||
@@ -283,6 +322,12 @@ void AudioGroupSampleDirectory::Entry::loadLooseData(SystemStringView basePath)
|
||||
m_data->loadLooseDSP(dspPath);
|
||||
m_data->m_looseModTime = dspStat.st_mtime;
|
||||
}
|
||||
else if (vadpcmValid && (!curData.m_looseData || vadpcmStat.st_mtime > curData.m_looseModTime))
|
||||
{
|
||||
m_data = MakeObj<EntryData>();
|
||||
m_data->loadLooseVADPCM(vadpcmPath);
|
||||
m_data->m_looseModTime = vadpcmStat.st_mtime;
|
||||
}
|
||||
else if (wavValid && (!curData.m_looseData || wavStat.st_mtime > curData.m_looseModTime))
|
||||
{
|
||||
m_data = MakeObj<EntryData>();
|
||||
@@ -295,12 +340,14 @@ SampleFileState AudioGroupSampleDirectory::Entry::getFileState(SystemStringView
|
||||
{
|
||||
SystemString wavPath = SystemString(basePath) + _S(".wav");
|
||||
SystemString dspPath = SystemString(basePath) + _S(".dsp");
|
||||
Sstat wavStat, dspStat;
|
||||
SystemString vadpcmPath = SystemString(basePath) + _S(".vadpcm");
|
||||
Sstat wavStat, dspStat, vadpcmStat;
|
||||
bool wavValid = !Stat(wavPath.c_str(), &wavStat) && S_ISREG(wavStat.st_mode);
|
||||
bool dspValid = !Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode);
|
||||
bool vadpcmValid = !Stat(vadpcmPath.c_str(), &vadpcmStat) && S_ISREG(vadpcmStat.st_mode);
|
||||
|
||||
EntryData& curData = *m_data;
|
||||
if (!wavValid && !dspValid)
|
||||
if (!wavValid && !dspValid && !vadpcmValid)
|
||||
{
|
||||
if (!curData.m_looseData)
|
||||
return SampleFileState::NoData;
|
||||
@@ -321,6 +368,30 @@ SampleFileState AudioGroupSampleDirectory::Entry::getFileState(SystemStringView
|
||||
*pathOut = dspPath;
|
||||
return SampleFileState::CompressedRecent;
|
||||
}
|
||||
if (wavValid && vadpcmValid)
|
||||
{
|
||||
if (wavStat.st_mtime > vadpcmStat.st_mtime)
|
||||
{
|
||||
if (pathOut)
|
||||
*pathOut = wavPath;
|
||||
return SampleFileState::WAVRecent;
|
||||
}
|
||||
if (pathOut)
|
||||
*pathOut = vadpcmPath;
|
||||
return SampleFileState::CompressedRecent;
|
||||
}
|
||||
if (dspValid && vadpcmValid)
|
||||
{
|
||||
if (dspStat.st_mtime > vadpcmStat.st_mtime)
|
||||
{
|
||||
if (pathOut)
|
||||
*pathOut = dspPath;
|
||||
return SampleFileState::CompressedNoWAV;
|
||||
}
|
||||
if (pathOut)
|
||||
*pathOut = vadpcmPath;
|
||||
return SampleFileState::CompressedNoWAV;
|
||||
}
|
||||
|
||||
if (dspValid)
|
||||
{
|
||||
@@ -328,6 +399,12 @@ SampleFileState AudioGroupSampleDirectory::Entry::getFileState(SystemStringView
|
||||
*pathOut = dspPath;
|
||||
return SampleFileState::CompressedNoWAV;
|
||||
}
|
||||
if (vadpcmValid)
|
||||
{
|
||||
if (pathOut)
|
||||
*pathOut = vadpcmPath;
|
||||
return SampleFileState::CompressedNoWAV;
|
||||
}
|
||||
if (pathOut)
|
||||
*pathOut = wavPath;
|
||||
return SampleFileState::WAVNoCompressed;
|
||||
@@ -341,7 +418,7 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataDSP(SystemStringView dsp
|
||||
DSPADPCMHeader head;
|
||||
head.read(r);
|
||||
|
||||
if (m_loopLengthSamples != 0)
|
||||
if (isLooped())
|
||||
{
|
||||
uint32_t block = getLoopStartSample() / 14;
|
||||
uint32_t rem = getLoopStartSample() % 14;
|
||||
@@ -384,6 +461,22 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataDSP(SystemStringView dsp
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::EntryData::patchMetadataVADPCM(SystemStringView vadpcmPath)
|
||||
{
|
||||
athena::io::FileWriter w(vadpcmPath, false);
|
||||
if (!w.hasError())
|
||||
{
|
||||
w.seek(0, athena::Begin);
|
||||
VADPCMHeader header;
|
||||
header.m_pitchSampleRate = m_pitch << 24;
|
||||
header.m_pitchSampleRate |= m_sampleRate & 0xffff;
|
||||
header.m_numSamples = m_numSamples;
|
||||
header.m_loopStartSample = m_loopStartSample;
|
||||
header.m_loopLengthSamples = m_loopLengthSamples;
|
||||
header.write(w);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::EntryData::patchMetadataWAV(SystemStringView wavPath)
|
||||
{
|
||||
athena::io::FileReader r(wavPath);
|
||||
@@ -445,7 +538,7 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataWAV(SystemStringView wav
|
||||
WAVSampleChunk smpl;
|
||||
smpl.smplPeriod = 1000000000 / fmt.sampleRate;
|
||||
smpl.midiNote = m_pitch;
|
||||
if (m_loopLengthSamples != 0)
|
||||
if (isLooped())
|
||||
{
|
||||
smpl.numSampleLoops = 1;
|
||||
smpl.additionalDataSize = 0;
|
||||
@@ -488,7 +581,7 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataWAV(SystemStringView wav
|
||||
WAVSampleChunk smpl;
|
||||
smpl.smplPeriod = 1000000000 / fmt.sampleRate;
|
||||
smpl.midiNote = m_pitch;
|
||||
if (m_loopLengthSamples != 0)
|
||||
if (isLooped())
|
||||
{
|
||||
smpl.numSampleLoops = 1;
|
||||
smpl.additionalDataSize = 0;
|
||||
@@ -530,9 +623,11 @@ void AudioGroupSampleDirectory::Entry::patchSampleMetadata(SystemStringView base
|
||||
{
|
||||
SystemString wavPath = SystemString(basePath) + _S(".wav");
|
||||
SystemString dspPath = SystemString(basePath) + _S(".dsp");
|
||||
Sstat wavStat, dspStat;
|
||||
SystemString vadpcmPath = SystemString(basePath) + _S(".vadpcm");
|
||||
Sstat wavStat, dspStat, vadpcmStat;
|
||||
bool wavValid = !Stat(wavPath.c_str(), &wavStat) && S_ISREG(wavStat.st_mode);
|
||||
bool dspValid = !Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode);
|
||||
bool vadpcmValid = !Stat(vadpcmPath.c_str(), &vadpcmStat) && S_ISREG(vadpcmStat.st_mode);
|
||||
|
||||
EntryData& curData = *m_data;
|
||||
|
||||
@@ -542,6 +637,12 @@ void AudioGroupSampleDirectory::Entry::patchSampleMetadata(SystemStringView base
|
||||
SetAudioFileTime(wavPath, wavStat);
|
||||
}
|
||||
|
||||
if (vadpcmValid)
|
||||
{
|
||||
curData.patchMetadataVADPCM(vadpcmPath);
|
||||
SetAudioFileTime(vadpcmPath, vadpcmStat);
|
||||
}
|
||||
|
||||
if (dspValid)
|
||||
{
|
||||
curData.patchMetadataDSP(dspPath);
|
||||
@@ -559,9 +660,19 @@ AudioGroupSampleDirectory AudioGroupSampleDirectory::CreateAudioGroupSampleDirec
|
||||
if (ent.m_name.size() < 4)
|
||||
continue;
|
||||
SystemString baseName;
|
||||
SystemString basePath;
|
||||
if (!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".dsp")) ||
|
||||
!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".wav")))
|
||||
{
|
||||
baseName = SystemString(ent.m_name.begin(), ent.m_name.begin() + ent.m_name.size() - 4);
|
||||
basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 4);
|
||||
}
|
||||
else if (ent.m_name.size() > 7 &&
|
||||
!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 7, _S(".vadpcm")))
|
||||
{
|
||||
baseName = SystemString(ent.m_name.begin(), ent.m_name.begin() + ent.m_name.size() - 7);
|
||||
basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 7);
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
@@ -574,7 +685,6 @@ AudioGroupSampleDirectory AudioGroupSampleDirectory::CreateAudioGroupSampleDirec
|
||||
|
||||
auto& entry = ret.m_entries[sampleId];
|
||||
entry = MakeObj<Entry>();
|
||||
SystemString basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 4);
|
||||
entry->loadLooseData(basePath);
|
||||
}
|
||||
|
||||
@@ -598,7 +708,7 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
|
||||
|
||||
SampleFormat fmt = SampleFormat(ent.m_numSamples >> 24);
|
||||
uint32_t numSamples = ent.m_numSamples & 0xffffff;
|
||||
if (ent.m_loopLengthSamples)
|
||||
if (ent.isLooped())
|
||||
{
|
||||
WAVHeaderLoop header;
|
||||
header.fmtChunk.sampleRate = ent.m_sampleRate;
|
||||
@@ -652,19 +762,19 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
|
||||
else if (fmt == SampleFormat::N64)
|
||||
{
|
||||
uint32_t remSamples = numSamples;
|
||||
uint32_t numFrames = (remSamples + 31) / 32;
|
||||
uint32_t numFrames = (remSamples + 63) / 64;
|
||||
const unsigned char* cur = samp + sizeof(ADPCMParms::VADPCMParms);
|
||||
for (uint32_t i = 0; i < numFrames; ++i)
|
||||
{
|
||||
int16_t decomp[32] = {};
|
||||
unsigned thisSamples = std::min(remSamples, 32u);
|
||||
int16_t decomp[64] = {};
|
||||
unsigned thisSamples = std::min(remSamples, 64u);
|
||||
N64MusyXDecompressFrame(decomp, cur, ent.m_ADPCMParms.vadpcm.m_coefs, thisSamples);
|
||||
remSamples -= thisSamples;
|
||||
cur += 16;
|
||||
cur += 40;
|
||||
w.writeBytes(decomp, thisSamples * 2);
|
||||
}
|
||||
|
||||
dataLen = sizeof(ADPCMParms::VADPCMParms) + (numSamples + 31) / 32 * 16;
|
||||
dataLen = sizeof(ADPCMParms::VADPCMParms) + (numSamples + 63) / 64 * 40;
|
||||
}
|
||||
else if (fmt == SampleFormat::PCM)
|
||||
{
|
||||
@@ -732,7 +842,7 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
|
||||
header.x0_num_samples = numSamples;
|
||||
header.x4_num_nibbles = DSPSampleToNibble(numSamples);
|
||||
header.x8_sample_rate = ent.m_sampleRate;
|
||||
header.xc_loop_flag = atUint16(ent.m_loopLengthSamples != 0);
|
||||
header.xc_loop_flag = atUint16(ent.isLooped());
|
||||
if (header.xc_loop_flag)
|
||||
{
|
||||
header.x10_loop_start_nibble = DSPSampleToNibble(ent.getLoopStartSample());
|
||||
@@ -757,7 +867,14 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
|
||||
{
|
||||
path += _S(".vadpcm");
|
||||
athena::io::FileWriter w(path);
|
||||
dataLen = sizeof(ADPCMParms::VADPCMParms) + (numSamples + 31) / 32 * 16;
|
||||
VADPCMHeader header;
|
||||
header.m_pitchSampleRate = ent.m_pitch << 24;
|
||||
header.m_pitchSampleRate |= ent.m_sampleRate & 0xffff;
|
||||
header.m_numSamples = ent.m_numSamples;
|
||||
header.m_loopStartSample = ent.m_loopStartSample;
|
||||
header.m_loopLengthSamples = ent.m_loopLengthSamples;
|
||||
header.write(w);
|
||||
dataLen = 256 + (numSamples + 63) / 64 * 40;
|
||||
w.writeUBytes(samp, dataLen);
|
||||
}
|
||||
else if (fmt == SampleFormat::PCM_PC || fmt == SampleFormat::PCM)
|
||||
@@ -779,7 +896,7 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
|
||||
header.x0_num_samples = numSamples;
|
||||
header.x4_num_nibbles = DSPSampleToNibble(numSamples);
|
||||
header.x8_sample_rate = ent.m_sampleRate;
|
||||
header.xc_loop_flag = atUint16(ent.m_loopLengthSamples != 0);
|
||||
header.xc_loop_flag = atUint16(ent.isLooped());
|
||||
header.m_pitch = ent.m_pitch;
|
||||
if (header.xc_loop_flag)
|
||||
{
|
||||
@@ -864,9 +981,19 @@ void AudioGroupSampleDirectory::reloadSampleData(SystemStringView groupPath)
|
||||
if (ent.m_name.size() < 4)
|
||||
continue;
|
||||
SystemString baseName;
|
||||
SystemString basePath;
|
||||
if (!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".dsp")) ||
|
||||
!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".wav")))
|
||||
{
|
||||
baseName = SystemString(ent.m_name.begin(), ent.m_name.begin() + ent.m_name.size() - 4);
|
||||
basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 4);
|
||||
}
|
||||
else if (ent.m_name.size() > 7 &&
|
||||
!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 7, _S(".vadpcm")))
|
||||
{
|
||||
baseName = SystemString(ent.m_name.begin(), ent.m_name.begin() + ent.m_name.size() - 7);
|
||||
basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 7);
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
@@ -883,7 +1010,6 @@ void AudioGroupSampleDirectory::reloadSampleData(SystemStringView groupPath)
|
||||
|
||||
auto& entry = m_entries[sampleId];
|
||||
entry = MakeObj<Entry>();
|
||||
SystemString basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 4);
|
||||
entry->loadLooseData(basePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1960,8 +1960,11 @@ std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> ContainerRegistry:
|
||||
{
|
||||
std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> ret;
|
||||
|
||||
const SystemChar* sep = std::max(StrRChr(path, _S('/')), StrRChr(path, _S('\\')));
|
||||
SystemString baseName(sep + 1, dot - sep - 1);
|
||||
SystemString baseName;
|
||||
if (const SystemChar* sep = std::max(StrRChr(path, _S('/')), StrRChr(path, _S('\\'))))
|
||||
baseName = SystemString(sep + 1, dot - sep - 1);
|
||||
else
|
||||
baseName = SystemString(path, dot - path);
|
||||
|
||||
/* Project */
|
||||
SystemChar projPath[1024];
|
||||
|
||||
@@ -441,7 +441,7 @@ void Engine::removeListener(Listener* listener)
|
||||
}
|
||||
|
||||
/** Start song playing from loaded audio groups */
|
||||
ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, ObjToken<Studio> smx)
|
||||
ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, bool loop, ObjToken<Studio> smx)
|
||||
{
|
||||
std::pair<AudioGroup*, const SongGroupIndex*> songGrp = _findSongGroup(groupId);
|
||||
if (songGrp.second)
|
||||
@@ -451,7 +451,7 @@ ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsign
|
||||
return {};
|
||||
|
||||
if (arrData)
|
||||
(*ret)->playSong(arrData);
|
||||
(*ret)->playSong(arrData, loop);
|
||||
return *ret;
|
||||
}
|
||||
|
||||
@@ -468,7 +468,7 @@ ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsign
|
||||
}
|
||||
|
||||
ObjToken<Sequencer> Engine::seqPlay(const AudioGroup* group, GroupId groupId, SongId songId,
|
||||
const unsigned char* arrData, ObjToken<Studio> smx)
|
||||
const unsigned char* arrData, bool loop, ObjToken<Studio> smx)
|
||||
{
|
||||
const SongGroupIndex* sgIdx = group->getProj().getSongGroupIndex(groupId);
|
||||
if (sgIdx)
|
||||
@@ -478,7 +478,7 @@ ObjToken<Sequencer> Engine::seqPlay(const AudioGroup* group, GroupId groupId, So
|
||||
return {};
|
||||
|
||||
if (arrData)
|
||||
(*ret)->playSong(arrData);
|
||||
(*ret)->playSong(arrData, loop);
|
||||
return *ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
|
||||
if (m_parent->m_songGroup)
|
||||
{
|
||||
oid = m_page->objId;
|
||||
res = (*ret)->loadPageObject(oid, m_parent->m_ticksPerSec, note, velocity, m_ctrlVals[1]);
|
||||
res = (*ret)->loadPageObject(oid, m_ticksPerSec, note, velocity, m_ctrlVals[1]);
|
||||
}
|
||||
else if (m_parent->m_sfxMappings.size())
|
||||
{
|
||||
@@ -265,7 +265,7 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
|
||||
const SFXGroupIndex::SFXEntry* sfxEntry = m_parent->m_sfxMappings[lookupIdx];
|
||||
oid = sfxEntry->objId;
|
||||
note = sfxEntry->defKey;
|
||||
res = (*ret)->loadPageObject(oid, m_parent->m_ticksPerSec, note, velocity, m_ctrlVals[1]);
|
||||
res = (*ret)->loadPageObject(oid, m_ticksPerSec, note, velocity, m_ctrlVals[1]);
|
||||
}
|
||||
else
|
||||
return {};
|
||||
@@ -446,7 +446,16 @@ void Sequencer::setPitchWheel(uint8_t chan, float pitchWheel)
|
||||
m_chanStates[chan].setPitchWheel(pitchWheel);
|
||||
}
|
||||
|
||||
void Sequencer::setTempo(double ticksPerSec) { m_ticksPerSec = ticksPerSec; }
|
||||
void Sequencer::setTempo(uint8_t chan, double ticksPerSec)
|
||||
{
|
||||
m_chanStates[chan].m_ticksPerSec = ticksPerSec;
|
||||
}
|
||||
|
||||
void Sequencer::setTempo(double ticksPerSec)
|
||||
{
|
||||
for (auto& c : m_chanStates)
|
||||
c.m_ticksPerSec = ticksPerSec;
|
||||
}
|
||||
|
||||
void Sequencer::ChannelState::allOff()
|
||||
{
|
||||
@@ -598,12 +607,12 @@ void Sequencer::sendMacroMessage(ObjectId macroId, int32_t val)
|
||||
chan.sendMacroMessage(macroId, val);
|
||||
}
|
||||
|
||||
void Sequencer::playSong(const unsigned char* arrData, bool dieOnEnd)
|
||||
void Sequencer::playSong(const unsigned char* arrData, bool loop, bool dieOnEnd)
|
||||
{
|
||||
m_arrData = arrData;
|
||||
m_dieOnEnd = dieOnEnd;
|
||||
m_songState.initialize(arrData);
|
||||
setTempo(m_songState.getTempo() * 384 / 60);
|
||||
m_songState.initialize(arrData, loop);
|
||||
setTempo(m_songState.getInitialTempo() * 384 / 60.0);
|
||||
m_state = SequencerState::Playing;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,17 +83,28 @@ struct Event
|
||||
class MIDIDecoder
|
||||
{
|
||||
int m_tick = 0;
|
||||
std::vector<std::pair<int, std::multimap<int, Event>>> m_results[16];
|
||||
std::vector<std::multimap<int, Event>> m_results[16];
|
||||
std::multimap<int, int> m_tempos;
|
||||
std::array<std::multimap<int, Event>::iterator, 128> m_notes[16];
|
||||
int m_minLoopStart[16];
|
||||
int m_minLoopEnd[16];
|
||||
|
||||
void _addProgramChange(int chan, int prog)
|
||||
bool isEmptyIterator(int chan, std::multimap<int, Event>::iterator it) const
|
||||
{
|
||||
for (const auto& res : m_results[chan])
|
||||
if (res.end() == it)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void _addRegionChange(int chan)
|
||||
{
|
||||
auto& results = m_results[chan];
|
||||
results.reserve(2);
|
||||
results.emplace_back();
|
||||
results.back().first = prog;
|
||||
for (size_t i = 0; i < 128; ++i)
|
||||
m_notes[chan][i] = results.back().second.end();
|
||||
if (results.size() == 1)
|
||||
for (size_t i = 0; i < 128; ++i)
|
||||
m_notes[chan][i] = results.back().end();
|
||||
}
|
||||
|
||||
uint8_t m_status = 0;
|
||||
@@ -125,8 +136,15 @@ class MIDIDecoder
|
||||
}
|
||||
|
||||
public:
|
||||
MIDIDecoder()
|
||||
{
|
||||
std::fill(std::begin(m_minLoopStart), std::end(m_minLoopStart), INT_MAX);
|
||||
std::fill(std::begin(m_minLoopEnd), std::end(m_minLoopEnd), INT_MAX);
|
||||
}
|
||||
|
||||
std::vector<uint8_t>::const_iterator receiveBytes(std::vector<uint8_t>::const_iterator begin,
|
||||
std::vector<uint8_t>::const_iterator end)
|
||||
std::vector<uint8_t>::const_iterator end,
|
||||
int loopStart[16] = nullptr, int loopEnd[16] = nullptr)
|
||||
{
|
||||
std::vector<uint8_t>::const_iterator it = begin;
|
||||
while (it != end)
|
||||
@@ -146,7 +164,7 @@ public:
|
||||
{
|
||||
/* Meta events */
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
|
||||
uint32_t length;
|
||||
@@ -168,25 +186,36 @@ public:
|
||||
uint8_t chan = m_status & 0xf;
|
||||
auto& results = m_results[chan];
|
||||
|
||||
/* Not actually used as such for now */
|
||||
if (results.empty())
|
||||
_addProgramChange(chan, 0);
|
||||
std::multimap<int, Event>& res = results.back().second;
|
||||
if (loopEnd && loopEnd[chan] != INT_MAX && m_tick >= loopEnd[chan])
|
||||
break;
|
||||
|
||||
/* Split region at loop start point */
|
||||
if (loopStart && loopStart[chan] != INT_MAX && m_tick >= loopStart[chan])
|
||||
{
|
||||
_addRegionChange(chan);
|
||||
loopStart[chan] = INT_MAX;
|
||||
}
|
||||
else if (results.empty())
|
||||
{
|
||||
_addRegionChange(chan);
|
||||
}
|
||||
|
||||
std::multimap<int, Event>& res = results.back();
|
||||
|
||||
switch (Status(m_status & 0xf0))
|
||||
{
|
||||
case Status::NoteOff:
|
||||
{
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
b = *it++;
|
||||
|
||||
uint8_t notenum = clamp7(a);
|
||||
std::multimap<int, Event>::iterator note = m_notes[chan][notenum];
|
||||
if (note != res.end())
|
||||
if (!isEmptyIterator(chan, note))
|
||||
{
|
||||
note->second.length = m_tick - note->first;
|
||||
m_notes[chan][notenum] = res.end();
|
||||
@@ -196,16 +225,16 @@ public:
|
||||
case Status::NoteOn:
|
||||
{
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
b = *it++;
|
||||
|
||||
uint8_t notenum = clamp7(a);
|
||||
uint8_t vel = clamp7(b);
|
||||
std::multimap<int, Event>::iterator note = m_notes[chan][notenum];
|
||||
if (note != res.end())
|
||||
if (!isEmptyIterator(chan, note))
|
||||
note->second.length = m_tick - note->first;
|
||||
|
||||
if (vel != 0)
|
||||
@@ -218,28 +247,33 @@ public:
|
||||
case Status::NotePressure:
|
||||
{
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
b = *it++;
|
||||
break;
|
||||
}
|
||||
case Status::ControlChange:
|
||||
{
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
b = *it++;
|
||||
res.emplace(m_tick, Event{CtrlEvent{}, chan, clamp7(a), clamp7(b), 0});
|
||||
if (a == 0x66)
|
||||
m_minLoopStart[chan] = std::min(m_tick, m_minLoopStart[chan]);
|
||||
else if (a == 0x67)
|
||||
m_minLoopEnd[chan] = std::min(m_tick, m_minLoopEnd[chan]);
|
||||
else
|
||||
res.emplace(m_tick, Event{CtrlEvent{}, chan, clamp7(a), clamp7(b), 0});
|
||||
break;
|
||||
}
|
||||
case Status::ProgramChange:
|
||||
{
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
res.emplace(m_tick, Event{ProgEvent{}, chan, a});
|
||||
break;
|
||||
@@ -247,17 +281,17 @@ public:
|
||||
case Status::ChannelPressure:
|
||||
{
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
break;
|
||||
}
|
||||
case Status::PitchBend:
|
||||
{
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
b = *it++;
|
||||
res.emplace(m_tick, Event{PitchEvent{}, chan, clamp7(b) * 128 + clamp7(a)});
|
||||
break;
|
||||
@@ -270,30 +304,30 @@ public:
|
||||
{
|
||||
uint32_t len;
|
||||
if (!_readContinuedValue(it, end, len) || end - it < len)
|
||||
return begin;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
case Status::TimecodeQuarterFrame:
|
||||
{
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
break;
|
||||
}
|
||||
case Status::SongPositionPointer:
|
||||
{
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
b = *it++;
|
||||
break;
|
||||
}
|
||||
case Status::SongSelect:
|
||||
{
|
||||
if (it == end)
|
||||
return begin;
|
||||
break;
|
||||
a = *it++;
|
||||
break;
|
||||
}
|
||||
@@ -315,11 +349,14 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, std::multimap<int, Event>>>& getResults(int chan) { return m_results[chan]; }
|
||||
std::vector<std::multimap<int, Event>>& getResults(int chan) { return m_results[chan]; }
|
||||
std::multimap<int, int>& getTempos() { return m_tempos; }
|
||||
int getMinLoopStart(int chan) const { return m_minLoopStart[chan]; }
|
||||
int getMinLoopEnd(int chan) const { return m_minLoopEnd[chan]; }
|
||||
};
|
||||
|
||||
class MIDIEncoder
|
||||
@@ -653,7 +690,7 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
|
||||
ret.push_back(1);
|
||||
|
||||
SongState song;
|
||||
if (!song.initialize(data))
|
||||
if (!song.initialize(data, false))
|
||||
return {};
|
||||
versionOut = song.m_sngVersion;
|
||||
isBig = song.m_bigEndian;
|
||||
@@ -681,15 +718,18 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
|
||||
encoder.getResult().push_back(0x51);
|
||||
encoder.getResult().push_back(3);
|
||||
|
||||
uint32_t tempo24 = SBig(60000000 / song.m_tempo);
|
||||
uint32_t tempo24 = SBig(60000000 / (song.m_header.m_initialTempo & 0x7fffffff));
|
||||
for (int i = 1; i < 4; ++i)
|
||||
encoder.getResult().push_back(reinterpret_cast<uint8_t*>(&tempo24)[i]);
|
||||
|
||||
/* Write out tempo changes */
|
||||
int lastTick = 0;
|
||||
while (song.m_tempoPtr && song.m_tempoPtr->m_tick != 0xffffffff)
|
||||
const SongState::TempoChange* tempoPtr = nullptr;
|
||||
if (song.m_header.m_tempoTableOff)
|
||||
tempoPtr = reinterpret_cast<const SongState::TempoChange*>(song.m_songData + song.m_header.m_tempoTableOff);
|
||||
while (tempoPtr && tempoPtr->m_tick != 0xffffffff)
|
||||
{
|
||||
SongState::TempoChange change = *song.m_tempoPtr;
|
||||
SongState::TempoChange change = *tempoPtr;
|
||||
if (song.m_bigEndian)
|
||||
change.swapBig();
|
||||
|
||||
@@ -703,7 +743,7 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
|
||||
for (int i = 1; i < 4; ++i)
|
||||
encoder.getResult().push_back(reinterpret_cast<uint8_t*>(&tempo24)[i]);
|
||||
|
||||
++song.m_tempoPtr;
|
||||
++tempoPtr;
|
||||
}
|
||||
|
||||
encoder.getResult().push_back(0);
|
||||
@@ -721,6 +761,8 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
|
||||
ret.insert(ret.cend(), encoder.getResult().begin(), encoder.getResult().end());
|
||||
}
|
||||
|
||||
bool loopsAdded = false;
|
||||
|
||||
/* Iterate each SNG track into type-1 MIDI track */
|
||||
for (SongState::Track& trk : song.m_tracks)
|
||||
{
|
||||
@@ -733,7 +775,7 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
|
||||
while (trk.m_nextRegion->indexValid(song.m_bigEndian))
|
||||
{
|
||||
std::multimap<int, Event> events;
|
||||
trk.advanceRegion(nullptr);
|
||||
trk.advanceRegion();
|
||||
uint32_t regStart =
|
||||
song.m_bigEndian ? SBig(trk.m_curRegion->m_startTick) : trk.m_curRegion->m_startTick;
|
||||
|
||||
@@ -900,6 +942,17 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
|
||||
}
|
||||
}
|
||||
|
||||
/* Add loop events */
|
||||
if (!loopsAdded && trk.m_nextRegion->indexLoop(song.m_bigEndian) != -1)
|
||||
{
|
||||
uint32_t loopEnd =
|
||||
song.m_bigEndian ? SBig(trk.m_nextRegion->m_startTick) : trk.m_nextRegion->m_startTick;
|
||||
allEvents.emplace(trk.m_loopStartTick, Event{CtrlEvent{}, trk.m_midiChan, 0x66, 0, 0});
|
||||
allEvents.emplace(loopEnd, Event{CtrlEvent{}, trk.m_midiChan, 0x67, 0, 0});
|
||||
if (!(song.m_header.m_initialTempo & 0x80000000))
|
||||
loopsAdded = true;
|
||||
}
|
||||
|
||||
/* Emit MIDI events */
|
||||
int lastTime = 0;
|
||||
for (auto& pair : allEvents)
|
||||
@@ -1021,6 +1074,55 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
std::vector<Region> regions;
|
||||
int curRegionOff = 0;
|
||||
|
||||
/* Pre-iterate to extract loop events */
|
||||
int loopStart[16];
|
||||
int loopEnd[16];
|
||||
int loopChanCount = 0;
|
||||
{
|
||||
int loopChanIdx = -1;
|
||||
for (int c = 0; c < 16; ++c)
|
||||
{
|
||||
loopStart[c] = INT_MAX;
|
||||
loopEnd[c] = INT_MAX;
|
||||
std::vector<uint8_t>::const_iterator tmpIt = it;
|
||||
for (int i = 0; i < header.count; ++i)
|
||||
{
|
||||
if (memcmp(&*tmpIt, "MTrk", 4))
|
||||
return {};
|
||||
tmpIt += 4;
|
||||
uint32_t length = SBig(*reinterpret_cast<const uint32_t*>(&*tmpIt));
|
||||
tmpIt += 4;
|
||||
|
||||
std::vector<uint8_t>::const_iterator begin = tmpIt;
|
||||
std::vector<uint8_t>::const_iterator end = tmpIt + length;
|
||||
tmpIt = end;
|
||||
|
||||
MIDIDecoder dec;
|
||||
dec.receiveBytes(begin, end);
|
||||
loopStart[c] = std::min(dec.getMinLoopStart(c), loopStart[c]);
|
||||
loopEnd[c] = std::min(dec.getMinLoopEnd(c), loopEnd[c]);
|
||||
}
|
||||
if (loopStart[c] == INT_MAX || loopEnd[c] == INT_MAX)
|
||||
{
|
||||
loopStart[c] = INT_MAX;
|
||||
loopEnd[c] = INT_MAX;
|
||||
}
|
||||
else
|
||||
{
|
||||
++loopChanCount;
|
||||
loopChanIdx = c;
|
||||
}
|
||||
}
|
||||
if (loopChanCount == 1)
|
||||
{
|
||||
for (int c = 0; c < 16; ++c)
|
||||
{
|
||||
loopStart[c] = loopStart[loopChanIdx];
|
||||
loopEnd[c] = loopEnd[loopChanIdx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < header.count; ++i)
|
||||
{
|
||||
if (memcmp(&*it, "MTrk", 4))
|
||||
@@ -1069,25 +1171,29 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
it = end;
|
||||
|
||||
MIDIDecoder dec;
|
||||
dec.receiveBytes(begin, end);
|
||||
int tmpLoopStart[16];
|
||||
int tmpLoopEnd[16];
|
||||
std::copy(std::begin(loopStart), std::end(loopStart), std::begin(tmpLoopStart));
|
||||
std::copy(std::begin(loopEnd), std::end(loopEnd), std::begin(tmpLoopEnd));
|
||||
dec.receiveBytes(begin, end, tmpLoopStart, tmpLoopEnd);
|
||||
|
||||
for (int c = 0; c < 16; ++c)
|
||||
{
|
||||
std::vector<std::pair<int, std::multimap<int, Event>>>& results = dec.getResults(c);
|
||||
int lastTrackStartTick = 0;
|
||||
std::vector<std::multimap<int, Event>>& results = dec.getResults(c);
|
||||
bool didChanInit = false;
|
||||
for (auto& prog : results)
|
||||
int lastEventTick = 0;
|
||||
for (auto& chanRegion : results)
|
||||
{
|
||||
bool didInit = false;
|
||||
int startTick = 0;
|
||||
int lastEventTick = 0;
|
||||
lastEventTick = 0;
|
||||
int lastPitchTick = 0;
|
||||
int lastPitchVal = 0;
|
||||
int lastModTick = 0;
|
||||
int lastModVal = 0;
|
||||
Region region;
|
||||
|
||||
for (auto& event : prog.second)
|
||||
for (auto& event : chanRegion)
|
||||
{
|
||||
uint32_t eventTick = event.first * 384 / header.div;
|
||||
|
||||
@@ -1097,7 +1203,6 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
{
|
||||
didInit = true;
|
||||
startTick = eventTick;
|
||||
lastTrackStartTick = startTick;
|
||||
lastEventTick = startTick;
|
||||
lastPitchTick = startTick;
|
||||
lastPitchVal = 0;
|
||||
@@ -1322,7 +1427,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
reg.m_unk1 = 0xff;
|
||||
reg.m_unk2 = 0;
|
||||
reg.m_regionIndex = SBig(uint16_t(regIdx));
|
||||
reg.m_unk3 = 0;
|
||||
reg.m_loopToRegion = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1331,7 +1436,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
reg.m_unk1 = 0xff;
|
||||
reg.m_unk2 = 0;
|
||||
reg.m_regionIndex = uint16_t(regIdx);
|
||||
reg.m_unk3 = 0;
|
||||
reg.m_loopToRegion = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1341,23 +1446,37 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
/* Terminating region header */
|
||||
regionBuf.emplace_back();
|
||||
SongState::TrackRegion& reg = regionBuf.back();
|
||||
|
||||
uint32_t termStartTick = 0;
|
||||
int16_t termRegionIdx = -1;
|
||||
int16_t termLoopToRegion = 0;
|
||||
if (loopEnd[c] != INT_MAX)
|
||||
{
|
||||
termStartTick = loopEnd[c];
|
||||
if (lastEventTick >= loopStart[c])
|
||||
{
|
||||
termRegionIdx = -2;
|
||||
termLoopToRegion = results.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (big)
|
||||
{
|
||||
reg.m_startTick = SBig(uint32_t(lastTrackStartTick));
|
||||
reg.m_startTick = SBig(termStartTick);
|
||||
reg.m_progNum = 0xff;
|
||||
reg.m_unk1 = 0xff;
|
||||
reg.m_unk2 = 0;
|
||||
reg.m_regionIndex = -1;
|
||||
reg.m_unk3 = 0;
|
||||
reg.m_regionIndex = SBig(termRegionIdx);
|
||||
reg.m_loopToRegion = SBig(termLoopToRegion);
|
||||
}
|
||||
else
|
||||
{
|
||||
reg.m_startTick = uint32_t(lastTrackStartTick);
|
||||
reg.m_startTick = termStartTick;
|
||||
reg.m_progNum = 0xff;
|
||||
reg.m_unk1 = 0xff;
|
||||
reg.m_unk2 = 0;
|
||||
reg.m_regionIndex = -1;
|
||||
reg.m_unk3 = 0;
|
||||
reg.m_regionIndex = termRegionIdx;
|
||||
reg.m_loopToRegion = termLoopToRegion;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1366,17 +1485,29 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
if (version == 1)
|
||||
{
|
||||
SongState::Header head;
|
||||
head.m_trackIdxOff = 0x18;
|
||||
head.m_regionIdxOff = 0x18 + 4 * 64 + regionBuf.size() * 12;
|
||||
head.m_initialTempo = initTempo;
|
||||
head.m_loopStartTicks[0] = 0;
|
||||
if (loopChanCount == 1)
|
||||
{
|
||||
head.m_loopStartTicks[0] = loopStart[0] == INT_MAX ? 0 : loopStart[0];
|
||||
}
|
||||
else if (loopChanCount > 1)
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
head.m_loopStartTicks[i] = loopStart[i] == INT_MAX ? 0 : loopStart[i];
|
||||
head.m_initialTempo |= 0x80000000;
|
||||
}
|
||||
size_t headSz = (head.m_initialTempo & 0x80000000) ? 0x58 : 0x18;
|
||||
head.m_trackIdxOff = headSz;
|
||||
head.m_regionIdxOff = headSz + 4 * 64 + regionBuf.size() * 12;
|
||||
head.m_chanMapOff = head.m_regionIdxOff + 4 * regionDataIdxArr.size() + curRegionOff;
|
||||
head.m_tempoTableOff = tempoBuf.size() ? head.m_chanMapOff + 64 : 0;
|
||||
head.m_initialTempo = initTempo;
|
||||
head.m_unkOff = 0;
|
||||
head.m_chanMapOff2 = head.m_chanMapOff;
|
||||
|
||||
uint32_t regIdxOff = head.m_regionIdxOff;
|
||||
if (big)
|
||||
head.swapBig();
|
||||
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), 0x18, 0)) = head;
|
||||
head.swapToBig();
|
||||
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), headSz, 0)) = head;
|
||||
|
||||
for (int i = 0; i < 64; ++i)
|
||||
{
|
||||
@@ -1388,7 +1519,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
|
||||
uint32_t idx = trackRegionIdxArr[i];
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
|
||||
big ? SBig(uint32_t(0x18 + 4 * 64 + idx * 12)) : uint32_t(0x18 + 4 * 64 + idx * 12);
|
||||
big ? SBig(uint32_t(headSz + 4 * 64 + idx * 12)) : uint32_t(headSz + 4 * 64 + idx * 12);
|
||||
}
|
||||
|
||||
for (SongState::TrackRegion& reg : regionBuf)
|
||||
@@ -1442,17 +1573,29 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
else
|
||||
{
|
||||
SongState::Header head;
|
||||
head.m_trackIdxOff = 0x18 + regionBuf.size() * 12;
|
||||
head.m_initialTempo = initTempo;
|
||||
head.m_loopStartTicks[0] = 0;
|
||||
if (loopChanCount == 1)
|
||||
{
|
||||
head.m_loopStartTicks[0] = loopStart[0] == INT_MAX ? 0 : loopStart[0];
|
||||
}
|
||||
else if (loopChanCount > 1)
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
head.m_loopStartTicks[i] = loopStart[i] == INT_MAX ? 0 : loopStart[i];
|
||||
head.m_initialTempo |= 0x80000000;
|
||||
}
|
||||
size_t headSz = (head.m_initialTempo & 0x80000000) ? 0x58 : 0x18;
|
||||
head.m_trackIdxOff = headSz + regionBuf.size() * 12;
|
||||
head.m_regionIdxOff = head.m_trackIdxOff + 4 * 64 + 64 + curRegionOff;
|
||||
head.m_chanMapOff = head.m_trackIdxOff + 4 * 64;
|
||||
head.m_tempoTableOff = tempoBuf.size() ? head.m_regionIdxOff + 4 * regionDataIdxArr.size() : 0;
|
||||
head.m_initialTempo = initTempo;
|
||||
head.m_unkOff = 0;
|
||||
head.m_chanMapOff2 = head.m_chanMapOff;
|
||||
|
||||
uint32_t chanMapOff = head.m_chanMapOff;
|
||||
if (big)
|
||||
head.swapBig();
|
||||
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), 0x18, 0)) = head;
|
||||
head.swapToBig();
|
||||
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), headSz, 0)) = head;
|
||||
|
||||
for (SongState::TrackRegion& reg : regionBuf)
|
||||
*reinterpret_cast<SongState::TrackRegion*>(&*ret.insert(ret.cend(), 12, 0)) = reg;
|
||||
@@ -1467,7 +1610,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
|
||||
|
||||
uint32_t idx = trackRegionIdxArr[i];
|
||||
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
|
||||
big ? SBig(uint32_t(0x18 + 4 * 64 + idx * 12)) : uint32_t(0x18 + 4 * 64 + idx * 12);
|
||||
big ? SBig(uint32_t(headSz + 4 * 64 + idx * 12)) : uint32_t(headSz + 4 * 64 + idx * 12);
|
||||
}
|
||||
|
||||
memmove(&*ret.insert(ret.cend(), 64, 0), chanMap.data(), 64);
|
||||
|
||||
@@ -75,14 +75,68 @@ static uint32_t DecodeTime(const unsigned char*& data)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SongState::Header::swapBig()
|
||||
void SongState::Header::swapFromBig()
|
||||
{
|
||||
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);
|
||||
if (m_initialTempo & 0x80000000)
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
m_loopStartTicks[i] = SBig(m_loopStartTicks[i]);
|
||||
m_chanMapOff2 = SBig(m_chanMapOff2);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_loopStartTicks[0] = SBig(m_loopStartTicks[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void SongState::Header::swapToBig()
|
||||
{
|
||||
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);
|
||||
if (m_initialTempo & 0x00000080)
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
m_loopStartTicks[i] = SBig(m_loopStartTicks[i]);
|
||||
m_chanMapOff2 = SBig(m_chanMapOff2);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_loopStartTicks[0] = SBig(m_loopStartTicks[0]);
|
||||
}
|
||||
}
|
||||
|
||||
SongState::Header& SongState::Header::operator=(const Header& other)
|
||||
{
|
||||
m_trackIdxOff = other.m_trackIdxOff;
|
||||
m_regionIdxOff = other.m_regionIdxOff;
|
||||
m_chanMapOff = other.m_chanMapOff;
|
||||
m_tempoTableOff = other.m_tempoTableOff;
|
||||
m_initialTempo = other.m_initialTempo;
|
||||
if (SBig(m_initialTempo) & 0x80000000)
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
m_loopStartTicks[i] = other.m_loopStartTicks[i];
|
||||
m_chanMapOff2 = other.m_chanMapOff2;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_loopStartTicks[0] = other.m_loopStartTicks[0];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool SongState::TrackRegion::indexDone(bool bigEndian, bool loop) const
|
||||
{
|
||||
int16_t idx = (bigEndian ? SBig(m_regionIndex) : m_regionIndex);
|
||||
return loop ? (idx == -1) : (idx < 0);
|
||||
}
|
||||
|
||||
bool SongState::TrackRegion::indexValid(bool bigEndian) const
|
||||
@@ -90,6 +144,13 @@ bool SongState::TrackRegion::indexValid(bool bigEndian) const
|
||||
return (bigEndian ? SBig(m_regionIndex) : m_regionIndex) >= 0;
|
||||
}
|
||||
|
||||
int SongState::TrackRegion::indexLoop(bool bigEndian) const
|
||||
{
|
||||
if ((bigEndian ? SBig(m_regionIndex) : m_regionIndex) != -2)
|
||||
return -1;
|
||||
return (bigEndian ? SBig(m_loopToRegion) : m_loopToRegion);
|
||||
}
|
||||
|
||||
void SongState::TempoChange::swapBig()
|
||||
{
|
||||
m_tick = SBig(m_tick);
|
||||
@@ -103,12 +164,14 @@ void SongState::Track::Header::swapBig()
|
||||
m_modOff = SBig(m_modOff);
|
||||
}
|
||||
|
||||
SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions)
|
||||
: m_parent(&parent), m_midiChan(midiChan), m_curRegion(nullptr), m_nextRegion(regions)
|
||||
SongState::Track::Track(SongState& parent, uint8_t midiChan, uint32_t loopStart, const TrackRegion* regions, uint32_t tempo)
|
||||
: m_parent(&parent), m_midiChan(midiChan), m_initRegion(regions), m_curRegion(nullptr),
|
||||
m_nextRegion(regions), m_loopStartTick(loopStart), m_tempo(tempo)
|
||||
{
|
||||
resetTempo();
|
||||
}
|
||||
|
||||
void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
|
||||
void SongState::Track::setRegion(const TrackRegion* region)
|
||||
{
|
||||
m_curRegion = region;
|
||||
uint32_t regionIdx = (m_parent->m_bigEndian ? SBig(m_curRegion->m_regionIndex) : m_curRegion->m_regionIndex);
|
||||
@@ -125,13 +188,14 @@ void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
|
||||
m_pitchWheelData = nullptr;
|
||||
m_nextPitchTick = 0x7fffffff;
|
||||
m_nextPitchDelta = 0;
|
||||
m_pitchVal = 0;
|
||||
if (header.m_pitchOff)
|
||||
{
|
||||
m_pitchWheelData = m_parent->m_songData + header.m_pitchOff;
|
||||
if (m_pitchWheelData[0] != 0x80 || m_pitchWheelData[1] != 0x00)
|
||||
{
|
||||
auto delta = DecodeDelta(m_pitchWheelData);
|
||||
m_nextPitchTick = m_parent->m_curTick + delta.first;
|
||||
m_nextPitchTick = m_curTick + delta.first;
|
||||
m_nextPitchDelta = delta.second;
|
||||
}
|
||||
}
|
||||
@@ -139,27 +203,23 @@ void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
|
||||
m_modWheelData = nullptr;
|
||||
m_nextModTick = 0x7fffffff;
|
||||
m_nextModDelta = 0;
|
||||
m_modVal = 0;
|
||||
if (header.m_modOff)
|
||||
{
|
||||
m_modWheelData = m_parent->m_songData + header.m_modOff;
|
||||
if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00)
|
||||
{
|
||||
auto delta = DecodeDelta(m_modWheelData);
|
||||
m_nextModTick = m_parent->m_curTick + delta.first;
|
||||
m_nextModTick = m_curTick + delta.first;
|
||||
m_nextModDelta = delta.second;
|
||||
}
|
||||
}
|
||||
|
||||
m_eventWaitCountdown = 0;
|
||||
m_pitchVal = 0;
|
||||
m_modVal = 0;
|
||||
if (seq)
|
||||
{
|
||||
seq->setPitchWheel(m_midiChan, clamp(-1.f, m_pitchVal / 32768.f, 1.f));
|
||||
seq->setCtrlValue(m_midiChan, 1, clamp(0, m_modVal * 128 / 16384, 127));
|
||||
}
|
||||
if (m_parent->m_sngVersion == 1)
|
||||
{
|
||||
m_eventWaitCountdown = int32_t(DecodeTime(m_data));
|
||||
}
|
||||
else
|
||||
{
|
||||
int32_t absTick = (m_parent->m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data))
|
||||
@@ -170,14 +230,14 @@ void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
|
||||
}
|
||||
}
|
||||
|
||||
void SongState::Track::advanceRegion(Sequencer* seq) { setRegion(seq, m_nextRegion); }
|
||||
void SongState::Track::advanceRegion() { setRegion(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();
|
||||
header.swapFromBig();
|
||||
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);
|
||||
|
||||
@@ -347,8 +407,9 @@ int SongState::DetectVersion(const unsigned char* ptr, bool& isBig)
|
||||
return v;
|
||||
}
|
||||
|
||||
bool SongState::initialize(const unsigned char* ptr)
|
||||
bool SongState::initialize(const unsigned char* ptr, bool loop)
|
||||
{
|
||||
m_loop = loop;
|
||||
m_sngVersion = DetectVersion(ptr, m_bigEndian);
|
||||
if (m_sngVersion < 0)
|
||||
return false;
|
||||
@@ -356,7 +417,7 @@ bool SongState::initialize(const unsigned char* ptr)
|
||||
m_songData = ptr;
|
||||
m_header = *reinterpret_cast<const Header*>(ptr);
|
||||
if (m_bigEndian)
|
||||
m_header.swapBig();
|
||||
m_header.swapFromBig();
|
||||
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);
|
||||
@@ -368,35 +429,69 @@ bool SongState::initialize(const unsigned char* ptr)
|
||||
{
|
||||
const TrackRegion* region =
|
||||
reinterpret_cast<const TrackRegion*>(ptr + (m_bigEndian ? SBig(trackIdx[i]) : trackIdx[i]));
|
||||
m_tracks[i] = Track(*this, chanMap[i], region);
|
||||
uint8_t chan = chanMap[i];
|
||||
uint32_t loopStart =
|
||||
(m_header.m_initialTempo & 0x80000000) ? m_header.m_loopStartTicks[chan] : m_header.m_loopStartTicks[0];
|
||||
m_tracks[i] = Track(*this, chan, loopStart, region, m_header.m_initialTempo & 0x7fffffff);
|
||||
}
|
||||
else
|
||||
m_tracks[i] = Track();
|
||||
}
|
||||
|
||||
/* Initialize tempo */
|
||||
if (m_header.m_tempoTableOff)
|
||||
m_tempoPtr = reinterpret_cast<const TempoChange*>(ptr + m_header.m_tempoTableOff);
|
||||
else
|
||||
m_tempoPtr = nullptr;
|
||||
|
||||
m_tempo = m_header.m_initialTempo & 0x7fffffff;
|
||||
m_curTick = 0;
|
||||
m_songState = SongPlayState::Playing;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
|
||||
void SongState::Track::resetTempo()
|
||||
{
|
||||
int32_t endTick = m_parent->m_curTick + ticks;
|
||||
if (m_parent->m_header.m_tempoTableOff)
|
||||
m_tempoPtr = reinterpret_cast<const TempoChange*>(m_parent->m_songData + m_parent->m_header.m_tempoTableOff);
|
||||
else
|
||||
m_tempoPtr = nullptr;
|
||||
}
|
||||
|
||||
bool SongState::Track::advance(Sequencer& seq, double dt)
|
||||
{
|
||||
m_remDt += dt;
|
||||
|
||||
/* Compute ticks to compute based on current tempo */
|
||||
double ticksPerSecond = m_tempo * 384 / 60;
|
||||
uint32_t ticks = uint32_t(std::floor(m_remDt * ticksPerSecond));
|
||||
|
||||
/* See if there's an upcoming tempo change in this interval */
|
||||
while (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff)
|
||||
{
|
||||
TempoChange change = *m_tempoPtr;
|
||||
if (m_parent->m_bigEndian)
|
||||
change.swapBig();
|
||||
|
||||
if (m_curTick + ticks > change.m_tick)
|
||||
ticks = change.m_tick - m_curTick;
|
||||
|
||||
if (ticks <= 0)
|
||||
{
|
||||
/* Turn over tempo */
|
||||
m_tempo = change.m_tempo & 0x7fffffff;
|
||||
ticksPerSecond = m_tempo * 384 / 60;
|
||||
ticks = uint32_t(std::floor(m_remDt * ticksPerSecond));
|
||||
seq.setTempo(m_midiChan, m_tempo * 384 / 60.0);
|
||||
++m_tempoPtr;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
m_remDt -= ticks / ticksPerSecond;
|
||||
uint32_t endTick = 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);
|
||||
uint32_t nextRegTick = (m_parent->m_bigEndian ?
|
||||
SBig(m_nextRegion->m_startTick) : m_nextRegion->m_startTick);
|
||||
if (uint32_t(endTick) > nextRegTick)
|
||||
advanceRegion(&seq);
|
||||
advanceRegion();
|
||||
else
|
||||
break;
|
||||
}
|
||||
@@ -412,184 +507,208 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_data)
|
||||
return !m_nextRegion->indexValid(m_parent->m_bigEndian);
|
||||
|
||||
/* Update continuous pitch data */
|
||||
if (m_pitchWheelData)
|
||||
if (m_data)
|
||||
{
|
||||
int32_t pitchTick = m_parent->m_curTick;
|
||||
int32_t remPitchTicks = ticks;
|
||||
while (pitchTick < endTick)
|
||||
/* Update continuous pitch data */
|
||||
if (m_pitchWheelData)
|
||||
{
|
||||
/* See if there's an upcoming pitch change in this interval */
|
||||
int32_t nextTick = m_nextPitchTick;
|
||||
if (pitchTick + remPitchTicks > nextTick)
|
||||
int32_t pitchTick = m_curTick;
|
||||
int32_t remPitchTicks = ticks;
|
||||
while (pitchTick < endTick)
|
||||
{
|
||||
/* Update pitch */
|
||||
m_pitchVal += m_nextPitchDelta;
|
||||
seq.setPitchWheel(m_midiChan, clamp(-1.f, m_pitchVal / 8191.f, 1.f));
|
||||
if (m_pitchWheelData[0] != 0x80 || m_pitchWheelData[1] != 0x00)
|
||||
/* See if there's an upcoming pitch change in this interval */
|
||||
int32_t nextTick = m_nextPitchTick;
|
||||
if (pitchTick + remPitchTicks > nextTick)
|
||||
{
|
||||
auto delta = DecodeDelta(m_pitchWheelData);
|
||||
m_nextPitchTick += delta.first;
|
||||
m_nextPitchDelta = delta.second;
|
||||
/* Update pitch */
|
||||
m_pitchVal += m_nextPitchDelta;
|
||||
seq.setPitchWheel(m_midiChan, clamp(-1.f, m_pitchVal / 8191.f, 1.f));
|
||||
if (m_pitchWheelData[0] != 0x80 || m_pitchWheelData[1] != 0x00)
|
||||
{
|
||||
auto delta = DecodeDelta(m_pitchWheelData);
|
||||
m_nextPitchTick += delta.first;
|
||||
m_nextPitchDelta = delta.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nextPitchTick = 0x7fffffff;
|
||||
}
|
||||
}
|
||||
remPitchTicks -= (nextTick - pitchTick);
|
||||
pitchTick = nextTick;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update continuous modulation data */
|
||||
if (m_modWheelData)
|
||||
{
|
||||
int32_t modTick = m_curTick;
|
||||
int32_t remModTicks = ticks;
|
||||
while (modTick < endTick)
|
||||
{
|
||||
/* See if there's an upcoming modulation change in this interval */
|
||||
int32_t nextTick = m_nextModTick;
|
||||
if (modTick + remModTicks > nextTick)
|
||||
{
|
||||
/* Update modulation */
|
||||
m_modVal += m_nextModDelta;
|
||||
seq.setCtrlValue(m_midiChan, 1, int8_t(clamp(0, m_modVal / 127, 127)));
|
||||
if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00)
|
||||
{
|
||||
auto delta = DecodeDelta(m_modWheelData);
|
||||
m_nextModTick += delta.first;
|
||||
m_nextModDelta = delta.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nextModTick = 0x7fffffff;
|
||||
}
|
||||
}
|
||||
remModTicks -= (nextTick - modTick);
|
||||
modTick = nextTick;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop through as many commands as we can for this time period */
|
||||
if (m_parent->m_sngVersion == 1)
|
||||
{
|
||||
/* Revision */
|
||||
while (true)
|
||||
{
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
if (m_eventWaitCountdown)
|
||||
{
|
||||
m_eventWaitCountdown -= ticks;
|
||||
ticks = 0;
|
||||
if (m_eventWaitCountdown > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
m_data = nullptr;
|
||||
break;
|
||||
}
|
||||
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_nextPitchTick = 0x7fffffff;
|
||||
}
|
||||
}
|
||||
remPitchTicks -= (nextTick - pitchTick);
|
||||
pitchTick = nextTick;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update continuous modulation data */
|
||||
if (m_modWheelData)
|
||||
{
|
||||
int32_t modTick = m_parent->m_curTick;
|
||||
int32_t remModTicks = ticks;
|
||||
while (modTick < endTick)
|
||||
{
|
||||
/* See if there's an upcoming modulation change in this interval */
|
||||
int32_t nextTick = m_nextModTick;
|
||||
if (modTick + remModTicks > nextTick)
|
||||
{
|
||||
/* Update modulation */
|
||||
m_modVal += m_nextModDelta;
|
||||
seq.setCtrlValue(m_midiChan, 1, clamp(0, m_modVal / 128, 127));
|
||||
if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00)
|
||||
{
|
||||
auto delta = DecodeDelta(m_modWheelData);
|
||||
m_nextModTick += delta.first;
|
||||
m_nextModDelta = delta.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nextModTick = 0x7fffffff;
|
||||
}
|
||||
}
|
||||
remModTicks -= (nextTick - modTick);
|
||||
modTick = nextTick;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop through as many commands as we can for this time period */
|
||||
if (m_parent->m_sngVersion == 1)
|
||||
{
|
||||
/* Revision */
|
||||
while (true)
|
||||
{
|
||||
/* 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 !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
|
||||
{
|
||||
/* 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);
|
||||
if (length == 0)
|
||||
seq.keyOff(m_midiChan, note, 0);
|
||||
m_remNoteLengths[note] = length;
|
||||
m_data += 4;
|
||||
}
|
||||
|
||||
/* Set next delta-time */
|
||||
m_eventWaitCountdown += int32_t(DecodeTime(m_data));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Legacy */
|
||||
while (true)
|
||||
{
|
||||
/* 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[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;
|
||||
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);
|
||||
if (length == 0)
|
||||
seq.keyOff(m_midiChan, note, 0);
|
||||
m_remNoteLengths[note] = length;
|
||||
m_data += 4;
|
||||
}
|
||||
else if (m_data[2] & 0x80 && m_data[3] & 0x80)
|
||||
|
||||
/* Set next delta-time */
|
||||
m_eventWaitCountdown += int32_t(DecodeTime(m_data));
|
||||
}
|
||||
} else
|
||||
{
|
||||
/* Legacy */
|
||||
while (true)
|
||||
{
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
if (m_eventWaitCountdown)
|
||||
{
|
||||
/* Control change */
|
||||
uint8_t val = m_data[2] & 0x7f;
|
||||
uint8_t ctrl = m_data[3] & 0x7f;
|
||||
seq.setCtrlValue(m_midiChan, ctrl, val);
|
||||
m_eventWaitCountdown -= ticks;
|
||||
ticks = 0;
|
||||
if (m_eventWaitCountdown > 0)
|
||||
break;
|
||||
}
|
||||
else if (m_data[2] & 0x80)
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(&m_data[2]) == 0xffff)
|
||||
{
|
||||
/* Program change */
|
||||
uint8_t prog = m_data[2] & 0x7f;
|
||||
seq.setChanProgram(m_midiChan, prog);
|
||||
/* End of channel */
|
||||
m_data = nullptr;
|
||||
break;
|
||||
}
|
||||
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);
|
||||
if (length == 0)
|
||||
seq.keyOff(m_midiChan, note, 0);
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
m_curTick = endTick;
|
||||
|
||||
/* Handle loop end */
|
||||
if (m_parent->m_loop)
|
||||
{
|
||||
int loopTo;
|
||||
if ((loopTo = m_nextRegion->indexLoop(m_parent->m_bigEndian)) != -1)
|
||||
{
|
||||
uint32_t loopEndTick = (m_parent->m_bigEndian ?
|
||||
SBig(m_nextRegion->m_startTick) : m_nextRegion->m_startTick);
|
||||
if (uint32_t(endTick) > loopEndTick)
|
||||
{
|
||||
m_nextRegion = &m_initRegion[loopTo];
|
||||
m_curRegion = nullptr;
|
||||
m_data = nullptr;
|
||||
m_curTick = m_loopStartTick;
|
||||
resetTempo();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_data)
|
||||
return m_nextRegion->indexDone(m_parent->m_bigEndian, m_parent->m_loop);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -599,50 +718,11 @@ bool SongState::advance(Sequencer& seq, double dt)
|
||||
if (m_songState == SongPlayState::Stopped)
|
||||
return true;
|
||||
|
||||
bool done = false;
|
||||
m_curDt += dt;
|
||||
while (m_curDt > 0.0)
|
||||
{
|
||||
done = true;
|
||||
|
||||
/* Compute ticks to compute based on current tempo */
|
||||
double ticksPerSecond = m_tempo * 384 / 60;
|
||||
int32_t remTicks = std::ceil(m_curDt * ticksPerSecond);
|
||||
if (!remTicks)
|
||||
break;
|
||||
|
||||
/* See if there's an upcoming tempo change in this interval */
|
||||
if (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff)
|
||||
{
|
||||
TempoChange change = *m_tempoPtr;
|
||||
if (m_bigEndian)
|
||||
change.swapBig();
|
||||
|
||||
if (m_curTick + remTicks > change.m_tick)
|
||||
remTicks = change.m_tick - m_curTick;
|
||||
|
||||
if (remTicks <= 0)
|
||||
{
|
||||
/* Turn over tempo */
|
||||
m_tempo = change.m_tempo & 0x7fffffff;
|
||||
seq.setTempo(m_tempo * 384 / 60);
|
||||
++m_tempoPtr;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Advance all tracks */
|
||||
for (Track& trk : m_tracks)
|
||||
if (trk)
|
||||
done &= trk.advance(seq, remTicks);
|
||||
|
||||
m_curTick += remTicks;
|
||||
|
||||
if (m_tempo == 0)
|
||||
m_curDt = 0.0;
|
||||
else
|
||||
m_curDt -= remTicks / ticksPerSecond;
|
||||
}
|
||||
/* Advance all tracks */
|
||||
bool done = true;
|
||||
for (Track& trk : m_tracks)
|
||||
if (trk)
|
||||
done &= trk.advance(seq, dt);
|
||||
|
||||
if (done)
|
||||
m_songState = SongPlayState::Stopped;
|
||||
|
||||
@@ -84,7 +84,7 @@ bool Voice::_checkSamplePos(bool& looped)
|
||||
|
||||
if (m_curSamplePos >= m_lastSamplePos)
|
||||
{
|
||||
if (m_curSample->m_loopLengthSamples)
|
||||
if (m_curSample->isLooped())
|
||||
{
|
||||
/* Turn over looped sample */
|
||||
m_curSamplePos = m_curSample->m_loopStartSample;
|
||||
@@ -493,7 +493,10 @@ void Voice::preSupplyAudio(double dt)
|
||||
m_vibratoTime += dt;
|
||||
float vibrato = TriangleWave(m_vibratoTime / m_vibratoPeriod);
|
||||
if (m_vibratoModWheel)
|
||||
newPitch += m_vibratoModLevel * vibrato * (m_state.m_curMod / 127.f);
|
||||
{
|
||||
int32_t range = m_vibratoModLevel ? m_vibratoModLevel : m_vibratoLevel;
|
||||
newPitch += range * vibrato * (m_state.m_curMod / 127.f);
|
||||
}
|
||||
else
|
||||
newPitch += m_vibratoLevel * vibrato;
|
||||
refresh = true;
|
||||
@@ -1002,7 +1005,7 @@ void Voice::startSample(SampleId sampId, int32_t offset)
|
||||
int32_t numSamples = m_curSample->getNumSamples();
|
||||
if (offset)
|
||||
{
|
||||
if (m_curSample->m_loopLengthSamples)
|
||||
if (m_curSample->isLooped())
|
||||
{
|
||||
if (offset > int32_t(m_curSample->m_loopStartSample))
|
||||
offset =
|
||||
@@ -1020,9 +1023,8 @@ void Voice::startSample(SampleId sampId, int32_t offset)
|
||||
if (m_curFormat == SampleFormat::DSP_DRUM)
|
||||
m_curFormat = SampleFormat::DSP;
|
||||
|
||||
m_lastSamplePos = m_curSample->m_loopLengthSamples
|
||||
? (m_curSample->m_loopStartSample + m_curSample->m_loopLengthSamples)
|
||||
: numSamples;
|
||||
m_lastSamplePos = m_curSample->isLooped()
|
||||
? (m_curSample->m_loopStartSample + m_curSample->m_loopLengthSamples) : numSamples;
|
||||
if (m_lastSamplePos)
|
||||
--m_lastSamplePos;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user