2016-05-19 02:19:43 +00:00
|
|
|
#include "amuse/SongState.hpp"
|
|
|
|
#include "amuse/Common.hpp"
|
|
|
|
#include "amuse/Sequencer.hpp"
|
2016-05-19 05:56:45 +00:00
|
|
|
#include <cmath>
|
2016-05-19 02:19:43 +00:00
|
|
|
|
|
|
|
namespace amuse
|
|
|
|
{
|
|
|
|
|
|
|
|
static uint32_t DecodeRLE(const unsigned char*& data)
|
|
|
|
{
|
|
|
|
uint32_t ret = 0;
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
uint32_t thisPart = *data & 0x7f;
|
|
|
|
if (*data & 0x80)
|
|
|
|
{
|
|
|
|
++data;
|
|
|
|
thisPart = thisPart * 256 + *data;
|
|
|
|
if (thisPart == 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (thisPart == 32767)
|
|
|
|
{
|
|
|
|
ret += 32767;
|
|
|
|
data += 2;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret += thisPart;
|
|
|
|
data += 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int32_t DecodeContinuousRLE(const unsigned char*& data)
|
|
|
|
{
|
|
|
|
int32_t ret = int32_t(DecodeRLE(data));
|
|
|
|
if (ret >= 16384)
|
|
|
|
return ret - 32767;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t DecodeTimeRLE(const unsigned char*& data)
|
|
|
|
{
|
|
|
|
uint32_t ret = 0;
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
uint16_t thisPart = SBig(*reinterpret_cast<const uint16_t*>(data));
|
|
|
|
if (thisPart == 0xffff)
|
|
|
|
{
|
|
|
|
ret += 65535;
|
|
|
|
data += 4;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret += thisPart;
|
|
|
|
data += 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SongState::Header::swapBig()
|
|
|
|
{
|
2016-06-20 03:35:57 +00:00
|
|
|
m_trackIdxOff = SBig(m_trackIdxOff);
|
|
|
|
m_regionIdxOff = SBig(m_regionIdxOff);
|
2016-05-19 02:19:43 +00:00
|
|
|
m_chanMapOff = SBig(m_chanMapOff);
|
|
|
|
m_tempoTableOff = SBig(m_tempoTableOff);
|
|
|
|
m_initialTempo = SBig(m_initialTempo);
|
|
|
|
m_unkOff = SBig(m_unkOff);
|
|
|
|
}
|
|
|
|
|
2016-06-20 03:35:57 +00:00
|
|
|
void SongState::TrackRegion::swapBig()
|
2016-05-19 02:19:43 +00:00
|
|
|
{
|
|
|
|
m_startTick = SBig(m_startTick);
|
|
|
|
m_unk1 = SBig(m_unk1);
|
|
|
|
m_unk2 = SBig(m_unk2);
|
2016-06-20 03:35:57 +00:00
|
|
|
m_regionIndex = SBig(m_regionIndex);
|
|
|
|
m_initialPitch = SBig(m_initialPitch);
|
2016-05-19 02:19:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SongState::TempoChange::swapBig()
|
|
|
|
{
|
|
|
|
m_tick = SBig(m_tick);
|
|
|
|
m_tempo = SBig(m_tempo);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SongState::Channel::Header::swapBig()
|
|
|
|
{
|
|
|
|
m_type = SBig(m_type);
|
|
|
|
m_pitchOff = SBig(m_pitchOff);
|
|
|
|
m_modOff = SBig(m_modOff);
|
|
|
|
}
|
|
|
|
|
2016-06-20 03:35:57 +00:00
|
|
|
SongState::Channel::Channel(SongState& parent, uint8_t midiChan, const TrackRegion* regions)
|
|
|
|
: m_parent(parent), m_midiChan(midiChan), m_curRegion(nullptr), m_nextRegion(regions)
|
|
|
|
{}
|
|
|
|
|
|
|
|
void SongState::Channel::setRegion(Sequencer& seq, const TrackRegion* region)
|
2016-05-19 02:19:43 +00:00
|
|
|
{
|
2016-06-20 03:35:57 +00:00
|
|
|
assert(region->m_regionIndex != 0xffff);
|
|
|
|
m_curRegion = region;
|
|
|
|
uint32_t regionIdx = SBig(m_curRegion->m_regionIndex);
|
|
|
|
m_nextRegion = &m_curRegion[1];
|
2016-05-19 02:19:43 +00:00
|
|
|
|
2016-06-20 03:35:57 +00:00
|
|
|
m_data = m_parent.m_songData + SBig(m_parent.m_regionIdx[regionIdx]);
|
|
|
|
|
|
|
|
Header header = *reinterpret_cast<const Header*>(m_data);
|
2016-05-19 02:19:43 +00:00
|
|
|
header.swapBig();
|
2016-06-20 03:35:57 +00:00
|
|
|
assert(header.m_type == 8);
|
|
|
|
m_data += 12;
|
2016-05-19 02:19:43 +00:00
|
|
|
|
|
|
|
if (header.m_pitchOff)
|
2016-06-20 03:35:57 +00:00
|
|
|
m_pitchWheelData = m_parent.m_songData + header.m_pitchOff;
|
2016-05-19 02:19:43 +00:00
|
|
|
if (header.m_modOff)
|
2016-06-20 03:35:57 +00:00
|
|
|
m_modWheelData = m_parent.m_songData + header.m_modOff;
|
|
|
|
|
|
|
|
m_eventWaitCountdown = 0;
|
|
|
|
m_lastPitchTick = m_parent.m_curTick;
|
|
|
|
//m_lastPitchVal = SBig(m_curRegion->m_initialPitch);
|
|
|
|
m_lastPitchVal = 0;
|
|
|
|
seq.setPitchWheel(m_midiChan, clamp(-1.f, m_lastPitchVal / 32768.f, 1.f));
|
|
|
|
m_lastModTick = m_parent.m_curTick;
|
|
|
|
m_lastModVal = 0;
|
|
|
|
seq.setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127));
|
|
|
|
if (m_parent.m_header.m_trackIdxOff == 0x18 || m_parent.m_header.m_trackIdxOff == 0x58)
|
|
|
|
m_eventWaitCountdown = int32_t(DecodeTimeRLE(m_data));
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int32_t absTick = SBig(*reinterpret_cast<const int32_t*>(m_data));
|
|
|
|
m_eventWaitCountdown = absTick;
|
|
|
|
m_lastN64EventTick = absTick;
|
|
|
|
m_data += 4;
|
|
|
|
}
|
|
|
|
}
|
2016-05-19 02:19:43 +00:00
|
|
|
|
2016-06-20 03:35:57 +00:00
|
|
|
void SongState::Channel::advanceRegion(Sequencer& seq)
|
|
|
|
{
|
|
|
|
setRegion(seq, m_nextRegion);
|
2016-05-19 02:19:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SongState::initialize(const unsigned char* ptr)
|
|
|
|
{
|
2016-06-20 03:35:57 +00:00
|
|
|
m_songData = ptr;
|
2016-05-19 02:19:43 +00:00
|
|
|
m_header = *reinterpret_cast<const Header*>(ptr);
|
|
|
|
m_header.swapBig();
|
2016-06-20 03:35:57 +00:00
|
|
|
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);
|
2016-05-19 02:19:43 +00:00
|
|
|
|
|
|
|
/* Initialize all channels */
|
|
|
|
for (int i=0 ; i<64 ; ++i)
|
|
|
|
{
|
2016-06-20 03:35:57 +00:00
|
|
|
if (trackIdx[i])
|
2016-05-19 02:19:43 +00:00
|
|
|
{
|
2016-06-20 03:35:57 +00:00
|
|
|
const TrackRegion* region = reinterpret_cast<const TrackRegion*>(ptr + SBig(trackIdx[i]));
|
|
|
|
m_channels[i].emplace(*this, chanMap[i], region);
|
2016-05-19 02:19:43 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
m_channels[i] = std::experimental::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
m_curTick = 0;
|
|
|
|
m_songState = SongPlayState::Playing;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
|
|
|
|
{
|
|
|
|
int32_t endTick = m_parent.m_curTick + ticks;
|
|
|
|
|
2016-06-20 03:35:57 +00:00
|
|
|
/* Advance region if needed */
|
|
|
|
while (m_nextRegion->m_regionIndex != 0xffff)
|
|
|
|
{
|
|
|
|
uint32_t nextRegTick = SBig(m_nextRegion->m_startTick);
|
|
|
|
if (endTick > nextRegTick)
|
|
|
|
advanceRegion(seq);
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_data)
|
|
|
|
return m_nextRegion->m_regionIndex == 0xffff;
|
|
|
|
|
2016-05-19 02:19:43 +00:00
|
|
|
/* Update continuous pitch data */
|
|
|
|
if (m_pitchWheelData)
|
|
|
|
{
|
|
|
|
int32_t pitchTick = m_parent.m_curTick;
|
|
|
|
int32_t remPitchTicks = ticks;
|
|
|
|
while (pitchTick < endTick)
|
|
|
|
{
|
|
|
|
/* See if there's an upcoming pitch change in this interval */
|
|
|
|
const unsigned char* ptr = m_pitchWheelData;
|
|
|
|
uint32_t deltaTicks = DecodeRLE(ptr);
|
2016-05-21 23:41:45 +00:00
|
|
|
if (deltaTicks != 0xffffffff)
|
2016-05-19 02:19:43 +00:00
|
|
|
{
|
2016-05-21 23:41:45 +00:00
|
|
|
int32_t nextTick = m_lastPitchTick + deltaTicks;
|
2016-05-19 02:19:43 +00:00
|
|
|
if (pitchTick + remPitchTicks > nextTick)
|
|
|
|
{
|
|
|
|
/* Update pitch */
|
|
|
|
int32_t pitchDelta = DecodeContinuousRLE(ptr);
|
|
|
|
m_lastPitchVal += pitchDelta;
|
|
|
|
m_pitchWheelData = ptr;
|
|
|
|
m_lastPitchTick = nextTick;
|
|
|
|
remPitchTicks -= (nextTick - pitchTick);
|
|
|
|
pitchTick = nextTick;
|
2016-05-19 10:12:32 +00:00
|
|
|
seq.setPitchWheel(m_midiChan, clamp(-1.f, m_lastPitchVal / 32768.f, 1.f));
|
2016-05-19 02:19:43 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
remPitchTicks -= (nextTick - pitchTick);
|
|
|
|
pitchTick = nextTick;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
const unsigned char* ptr = m_modWheelData;
|
|
|
|
uint32_t deltaTicks = DecodeRLE(ptr);
|
2016-05-21 23:41:45 +00:00
|
|
|
if (deltaTicks != 0xffffffff)
|
2016-05-19 02:19:43 +00:00
|
|
|
{
|
2016-05-21 23:41:45 +00:00
|
|
|
int32_t nextTick = m_lastModTick + deltaTicks;
|
2016-05-19 02:19:43 +00:00
|
|
|
if (modTick + remModTicks > nextTick)
|
|
|
|
{
|
|
|
|
/* Update modulation */
|
|
|
|
int32_t modDelta = DecodeContinuousRLE(ptr);
|
|
|
|
m_lastModVal += modDelta;
|
|
|
|
m_modWheelData = ptr;
|
|
|
|
m_lastModTick = nextTick;
|
|
|
|
remModTicks -= (nextTick - modTick);
|
|
|
|
modTick = nextTick;
|
2016-06-20 03:35:57 +00:00
|
|
|
seq.setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127));
|
2016-05-19 02:19:43 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
remModTicks -= (nextTick - modTick);
|
|
|
|
modTick = nextTick;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stop finished notes */
|
|
|
|
for (int i=0 ; i<128 ; ++i)
|
|
|
|
{
|
|
|
|
if (m_remNoteLengths[i])
|
|
|
|
{
|
|
|
|
if (m_remNoteLengths[i] <= ticks)
|
|
|
|
{
|
|
|
|
seq.keyOff(m_midiChan, i, 0);
|
|
|
|
m_remNoteLengths[i] = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
m_remNoteLengths[i] -= ticks;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Loop through as many commands as we can for this time period */
|
2016-06-20 03:35:57 +00:00
|
|
|
if (m_parent.m_header.m_trackIdxOff == 0x18 || m_parent.m_header.m_trackIdxOff == 0x58)
|
2016-05-19 02:19:43 +00:00
|
|
|
{
|
2016-06-20 03:35:57 +00:00
|
|
|
/* GameCube */
|
|
|
|
while (true)
|
2016-05-19 02:19:43 +00:00
|
|
|
{
|
2016-06-20 03:35:57 +00:00
|
|
|
/* Advance wait timer if active, returning if waiting */
|
|
|
|
if (m_eventWaitCountdown)
|
|
|
|
{
|
|
|
|
m_eventWaitCountdown -= ticks;
|
|
|
|
ticks = 0;
|
|
|
|
if (m_eventWaitCountdown > 0)
|
|
|
|
return false;
|
|
|
|
}
|
2016-05-19 02:19:43 +00:00
|
|
|
|
2016-06-20 03:35:57 +00:00
|
|
|
/* Load next command */
|
|
|
|
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
|
|
|
|
{
|
|
|
|
/* End of channel */
|
|
|
|
m_data = nullptr;
|
|
|
|
return m_nextRegion->m_regionIndex == 0xffff;
|
|
|
|
}
|
|
|
|
else if (m_data[0] & 0x80)
|
|
|
|
{
|
|
|
|
/* Control change */
|
|
|
|
uint8_t val = m_data[0] & 0x7f;
|
|
|
|
uint8_t ctrl = m_data[1] & 0x7f;
|
|
|
|
seq.setCtrlValue(m_midiChan, ctrl, val);
|
|
|
|
m_data += 2;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Note */
|
|
|
|
uint8_t note = m_data[0] & 0x7f;
|
|
|
|
uint8_t vel = m_data[1] & 0x7f;
|
|
|
|
uint16_t length = SBig(*reinterpret_cast<const uint16_t*>(m_data + 2));
|
|
|
|
seq.keyOn(m_midiChan, note, vel);
|
|
|
|
m_remNoteLengths[note] = length;
|
|
|
|
m_data += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set next delta-time */
|
|
|
|
m_eventWaitCountdown += int32_t(DecodeTimeRLE(m_data));
|
2016-05-19 02:19:43 +00:00
|
|
|
}
|
2016-06-20 03:35:57 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* N64 */
|
|
|
|
while (true)
|
2016-05-19 02:19:43 +00:00
|
|
|
{
|
2016-06-20 03:35:57 +00:00
|
|
|
/* 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 uint32_t*>(m_data) == 0xffff0000)
|
|
|
|
{
|
|
|
|
/* End of channel */
|
|
|
|
m_data = nullptr;
|
|
|
|
return m_nextRegion->m_regionIndex == 0xffff;
|
|
|
|
}
|
|
|
|
else if (m_data[0] & 0x80)
|
|
|
|
{
|
|
|
|
/* Control change */
|
|
|
|
uint8_t val = m_data[0] & 0x7f;
|
|
|
|
uint8_t ctrl = m_data[1] & 0x7f;
|
|
|
|
seq.setCtrlValue(m_midiChan, ctrl, val);
|
|
|
|
m_data += 2;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ((m_data[2] & 0x80) != 0x80)
|
|
|
|
{
|
|
|
|
/* Note */
|
|
|
|
uint16_t length = SBig(*reinterpret_cast<const uint16_t*>(m_data));
|
|
|
|
uint8_t note = m_data[2] & 0x7f;
|
|
|
|
uint8_t vel = m_data[3] & 0x7f;
|
|
|
|
seq.keyOn(m_midiChan, note, vel);
|
|
|
|
m_remNoteLengths[note] = length;
|
|
|
|
}
|
|
|
|
m_data += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set next delta-time */
|
|
|
|
int32_t absTick = SBig(*reinterpret_cast<const int32_t*>(m_data));
|
|
|
|
assert(absTick >= m_lastN64EventTick);
|
|
|
|
m_eventWaitCountdown += absTick - m_lastN64EventTick;
|
|
|
|
m_lastN64EventTick = absTick;
|
2016-05-19 02:19:43 +00:00
|
|
|
m_data += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SongState::advance(Sequencer& seq, double dt)
|
|
|
|
{
|
|
|
|
/* Stopped */
|
|
|
|
if (m_songState == SongPlayState::Stopped)
|
|
|
|
return true;
|
|
|
|
|
2016-05-19 05:27:39 +00:00
|
|
|
bool done = false;
|
|
|
|
m_curDt += dt;
|
|
|
|
while (m_curDt > 0.0)
|
2016-05-19 02:19:43 +00:00
|
|
|
{
|
2016-05-19 05:27:39 +00:00
|
|
|
done = true;
|
|
|
|
|
2016-05-19 02:19:43 +00:00
|
|
|
/* Compute ticks to compute based on current tempo */
|
2016-05-19 05:27:39 +00:00
|
|
|
double ticksPerSecond = m_tempo * 384 / 60;
|
|
|
|
int32_t remTicks = std::ceil(m_curDt * ticksPerSecond);
|
|
|
|
if (!remTicks)
|
|
|
|
break;
|
2016-05-19 02:19:43 +00:00
|
|
|
|
|
|
|
/* See if there's an upcoming tempo change in this interval */
|
|
|
|
if (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff)
|
|
|
|
{
|
|
|
|
TempoChange change = *m_tempoPtr;
|
|
|
|
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;
|
2016-06-01 21:32:48 +00:00
|
|
|
seq.setTempo(m_tempo * 384 / 60);
|
2016-05-19 02:19:43 +00:00
|
|
|
++m_tempoPtr;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Advance all channels */
|
|
|
|
for (std::experimental::optional<Channel>& chan : m_channels)
|
|
|
|
if (chan)
|
|
|
|
done &= chan->advance(seq, remTicks);
|
|
|
|
|
|
|
|
m_curTick += remTicks;
|
|
|
|
|
|
|
|
if (m_tempo == 0)
|
2016-05-19 05:27:39 +00:00
|
|
|
m_curDt = 0.0;
|
2016-05-19 02:19:43 +00:00
|
|
|
else
|
2016-05-19 05:27:39 +00:00
|
|
|
m_curDt -= remTicks / ticksPerSecond;
|
2016-05-19 02:19:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (done)
|
|
|
|
m_songState = SongPlayState::Stopped;
|
|
|
|
return done;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|