Looping SNG support; bug fixes

This commit is contained in:
Jack Andersen
2018-09-08 11:34:01 -10:00
parent 25aacc9511
commit 81f0a91569
22 changed files with 1035 additions and 579 deletions

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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];

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;