mirror of https://github.com/AxioDL/amuse.git
827 lines
26 KiB
C++
827 lines
26 KiB
C++
|
#include "amuse/SongConverter.hpp"
|
||
|
#include "amuse/SongState.hpp"
|
||
|
#include "amuse/Common.hpp"
|
||
|
#include <stdlib.h>
|
||
|
#include <map>
|
||
|
|
||
|
namespace amuse
|
||
|
{
|
||
|
|
||
|
static inline uint8_t clamp7(uint8_t val) {return std::max(0, std::min(127, int(val)));}
|
||
|
|
||
|
enum class Status
|
||
|
{
|
||
|
NoteOff = 0x80,
|
||
|
NoteOn = 0x90,
|
||
|
NotePressure = 0xA0,
|
||
|
ControlChange = 0xB0,
|
||
|
ProgramChange = 0xC0,
|
||
|
ChannelPressure = 0xD0,
|
||
|
PitchBend = 0xE0,
|
||
|
SysEx = 0xF0,
|
||
|
TimecodeQuarterFrame = 0xF1,
|
||
|
SongPositionPointer = 0xF2,
|
||
|
SongSelect = 0xF3,
|
||
|
TuneRequest = 0xF6,
|
||
|
SysExTerm = 0xF7,
|
||
|
TimingClock = 0xF8,
|
||
|
Start = 0xFA,
|
||
|
Continue = 0xFB,
|
||
|
Stop = 0xFC,
|
||
|
ActiveSensing = 0xFE,
|
||
|
Reset = 0xFF,
|
||
|
};
|
||
|
|
||
|
class IMIDIReader
|
||
|
{
|
||
|
public:
|
||
|
virtual void noteOff(uint8_t chan, uint8_t key, uint8_t velocity)=0;
|
||
|
virtual void noteOn(uint8_t chan, uint8_t key, uint8_t velocity)=0;
|
||
|
virtual void notePressure(uint8_t chan, uint8_t key, uint8_t pressure)=0;
|
||
|
virtual void controlChange(uint8_t chan, uint8_t control, uint8_t value)=0;
|
||
|
virtual void programChange(uint8_t chan, uint8_t program)=0;
|
||
|
virtual void channelPressure(uint8_t chan, uint8_t pressure)=0;
|
||
|
virtual void pitchBend(uint8_t chan, int16_t pitch)=0;
|
||
|
|
||
|
virtual void allSoundOff(uint8_t chan)=0;
|
||
|
virtual void resetAllControllers(uint8_t chan)=0;
|
||
|
virtual void localControl(uint8_t chan, bool on)=0;
|
||
|
virtual void allNotesOff(uint8_t chan)=0;
|
||
|
virtual void omniMode(uint8_t chan, bool on)=0;
|
||
|
virtual void polyMode(uint8_t chan, bool on)=0;
|
||
|
|
||
|
virtual void sysex(const void* data, size_t len)=0;
|
||
|
virtual void timeCodeQuarterFrame(uint8_t message, uint8_t value)=0;
|
||
|
virtual void songPositionPointer(uint16_t pointer)=0;
|
||
|
virtual void songSelect(uint8_t song)=0;
|
||
|
virtual void tuneRequest()=0;
|
||
|
|
||
|
virtual void startSeq()=0;
|
||
|
virtual void continueSeq()=0;
|
||
|
virtual void stopSeq()=0;
|
||
|
|
||
|
virtual void reset()=0;
|
||
|
};
|
||
|
|
||
|
class MIDIDecoder
|
||
|
{
|
||
|
IMIDIReader& m_out;
|
||
|
uint8_t m_status = 0;
|
||
|
bool _readContinuedValue(std::vector<uint8_t>::const_iterator& it,
|
||
|
std::vector<uint8_t>::const_iterator end,
|
||
|
uint32_t& valOut)
|
||
|
{
|
||
|
uint8_t a = *it++;
|
||
|
valOut = a & 0x7f;
|
||
|
|
||
|
if (a & 0x80)
|
||
|
{
|
||
|
if (it == end)
|
||
|
return false;
|
||
|
valOut <<= 7;
|
||
|
a = *it++;
|
||
|
valOut |= a & 0x7f;
|
||
|
|
||
|
if (a & 0x80)
|
||
|
{
|
||
|
if (it == end)
|
||
|
return false;
|
||
|
valOut <<= 7;
|
||
|
a = *it++;
|
||
|
valOut |= a & 0x7f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
public:
|
||
|
MIDIDecoder(IMIDIReader& out) : m_out(out) {}
|
||
|
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 it = begin;
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
|
||
|
uint8_t a = *it++;
|
||
|
uint8_t b;
|
||
|
if (a & 0x80)
|
||
|
m_status = a;
|
||
|
else
|
||
|
it--;
|
||
|
|
||
|
uint8_t chan = m_status & 0xf;
|
||
|
switch (Status(m_status & 0xf0))
|
||
|
{
|
||
|
case Status::NoteOff:
|
||
|
{
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
a = *it++;
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
b = *it++;
|
||
|
m_out.noteOff(chan, clamp7(a), clamp7(b));
|
||
|
break;
|
||
|
}
|
||
|
case Status::NoteOn:
|
||
|
{
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
a = *it++;
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
b = *it++;
|
||
|
m_out.noteOn(chan, clamp7(a), clamp7(b));
|
||
|
break;
|
||
|
}
|
||
|
case Status::NotePressure:
|
||
|
{
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
a = *it++;
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
b = *it++;
|
||
|
m_out.notePressure(chan, clamp7(a), clamp7(b));
|
||
|
break;
|
||
|
}
|
||
|
case Status::ControlChange:
|
||
|
{
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
a = *it++;
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
b = *it++;
|
||
|
m_out.controlChange(chan, clamp7(a), clamp7(b));
|
||
|
break;
|
||
|
}
|
||
|
case Status::ProgramChange:
|
||
|
{
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
a = *it++;
|
||
|
m_out.programChange(chan, clamp7(a));
|
||
|
break;
|
||
|
}
|
||
|
case Status::ChannelPressure:
|
||
|
{
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
a = *it++;
|
||
|
m_out.channelPressure(chan, clamp7(a));
|
||
|
break;
|
||
|
}
|
||
|
case Status::PitchBend:
|
||
|
{
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
a = *it++;
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
b = *it++;
|
||
|
m_out.pitchBend(chan, clamp7(b) * 128 + clamp7(a));
|
||
|
break;
|
||
|
}
|
||
|
case Status::SysEx:
|
||
|
{
|
||
|
switch (Status(m_status & 0xff))
|
||
|
{
|
||
|
case Status::SysEx:
|
||
|
{
|
||
|
uint32_t len;
|
||
|
if (!_readContinuedValue(it, end, len) || end - it < len)
|
||
|
return begin;
|
||
|
m_out.sysex(&*it, len);
|
||
|
break;
|
||
|
}
|
||
|
case Status::TimecodeQuarterFrame:
|
||
|
{
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
a = *it++;
|
||
|
m_out.timeCodeQuarterFrame(a >> 4 & 0x7, a & 0xf);
|
||
|
break;
|
||
|
}
|
||
|
case Status::SongPositionPointer:
|
||
|
{
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
a = *it++;
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
b = *it++;
|
||
|
m_out.songPositionPointer(clamp7(b) * 128 + clamp7(a));
|
||
|
break;
|
||
|
}
|
||
|
case Status::SongSelect:
|
||
|
{
|
||
|
if (it == end)
|
||
|
return begin;
|
||
|
a = *it++;
|
||
|
m_out.songSelect(clamp7(a));
|
||
|
break;
|
||
|
}
|
||
|
case Status::TuneRequest:
|
||
|
m_out.tuneRequest();
|
||
|
break;
|
||
|
case Status::Start:
|
||
|
m_out.startSeq();
|
||
|
break;
|
||
|
case Status::Continue:
|
||
|
m_out.continueSeq();
|
||
|
break;
|
||
|
case Status::Stop:
|
||
|
m_out.stopSeq();
|
||
|
break;
|
||
|
case Status::Reset:
|
||
|
m_out.reset();
|
||
|
break;
|
||
|
case Status::SysExTerm:
|
||
|
case Status::TimingClock:
|
||
|
case Status::ActiveSensing:
|
||
|
default: break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
default: break;
|
||
|
}
|
||
|
|
||
|
return it;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
class MIDIEncoder : public IMIDIReader
|
||
|
{
|
||
|
friend class SongConverter;
|
||
|
std::vector<uint8_t> m_result;
|
||
|
uint8_t m_status = 0;
|
||
|
|
||
|
void _sendMessage(const uint8_t* data, size_t len)
|
||
|
{
|
||
|
if (data[0] == m_status)
|
||
|
{
|
||
|
for (size_t i=1 ; i<len ; ++i)
|
||
|
m_result.push_back(data[i]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (data[0] & 0x80)
|
||
|
m_status = data[0];
|
||
|
for (size_t i=0 ; i<len ; ++i)
|
||
|
m_result.push_back(data[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void _sendContinuedValue(uint32_t val)
|
||
|
{
|
||
|
uint32_t send[3] = {};
|
||
|
uint32_t* ptr = nullptr;
|
||
|
if (val >= 0x4000)
|
||
|
{
|
||
|
ptr = &send[0];
|
||
|
send[0] = 0x80 | ((val / 0x4000) & 0x7f);
|
||
|
send[1] = 0x80;
|
||
|
val &= 0x3fff;
|
||
|
}
|
||
|
|
||
|
if (val >= 0x80)
|
||
|
{
|
||
|
if (!ptr)
|
||
|
ptr = &send[1];
|
||
|
send[1] = 0x80 | ((val / 0x80) & 0x7f);
|
||
|
}
|
||
|
|
||
|
if (!ptr)
|
||
|
ptr = &send[2];
|
||
|
send[2] = val & 0x7f;
|
||
|
|
||
|
size_t len = 3 - (ptr - send);
|
||
|
for (size_t i=0 ; i<len ; ++i)
|
||
|
m_result.push_back(ptr[i]);
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
void noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::NoteOff) | (chan & 0xf)),
|
||
|
uint8_t(key & 0x7f), uint8_t(velocity & 0x7f)};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
void noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::NoteOn) | (chan & 0xf)),
|
||
|
uint8_t(key & 0x7f), uint8_t(velocity & 0x7f)};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
void notePressure(uint8_t chan, uint8_t key, uint8_t pressure)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::NotePressure) | (chan & 0xf)),
|
||
|
uint8_t(key & 0x7f), uint8_t(pressure & 0x7f)};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
void controlChange(uint8_t chan, uint8_t control, uint8_t value)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
|
||
|
uint8_t(control & 0x7f), uint8_t(value & 0x7f)};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
void programChange(uint8_t chan, uint8_t program)
|
||
|
{
|
||
|
uint8_t cmd[2] = {uint8_t(int(Status::ProgramChange) | (chan & 0xf)),
|
||
|
uint8_t(program & 0x7f)};
|
||
|
_sendMessage(cmd, 2);
|
||
|
}
|
||
|
|
||
|
void channelPressure(uint8_t chan, uint8_t pressure)
|
||
|
{
|
||
|
uint8_t cmd[2] = {uint8_t(int(Status::ChannelPressure) | (chan & 0xf)),
|
||
|
uint8_t(pressure & 0x7f)};
|
||
|
_sendMessage(cmd, 2);
|
||
|
}
|
||
|
|
||
|
void pitchBend(uint8_t chan, int16_t pitch)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::PitchBend) | (chan & 0xf)),
|
||
|
uint8_t((pitch % 128) & 0x7f), uint8_t((pitch / 128) & 0x7f)};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
|
||
|
void allSoundOff(uint8_t chan)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
|
||
|
120, 0};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
void resetAllControllers(uint8_t chan)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
|
||
|
121, 0};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
void localControl(uint8_t chan, bool on)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
|
||
|
122, uint8_t(on ? 127 : 0)};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
void allNotesOff(uint8_t chan)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
|
||
|
123, 0};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
void omniMode(uint8_t chan, bool on)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
|
||
|
uint8_t(on ? 125 : 124), 0};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
void polyMode(uint8_t chan, bool on)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
|
||
|
uint8_t(on ? 127 : 126), 0};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
|
||
|
void sysex(const void* data, size_t len)
|
||
|
{
|
||
|
uint8_t cmd = uint8_t(Status::SysEx);
|
||
|
_sendMessage(&cmd, 1);
|
||
|
_sendContinuedValue(len);
|
||
|
for (size_t i=0 ; i<len ; ++i)
|
||
|
m_result.push_back(reinterpret_cast<const uint8_t*>(data)[i]);
|
||
|
cmd = uint8_t(Status::SysExTerm);
|
||
|
_sendMessage(&cmd, 1);
|
||
|
}
|
||
|
|
||
|
void timeCodeQuarterFrame(uint8_t message, uint8_t value)
|
||
|
{
|
||
|
uint8_t cmd[2] = {uint8_t(int(Status::TimecodeQuarterFrame)),
|
||
|
uint8_t((message & 0x7 << 4) | (value & 0xf))};
|
||
|
_sendMessage(cmd, 2);
|
||
|
}
|
||
|
|
||
|
void songPositionPointer(uint16_t pointer)
|
||
|
{
|
||
|
uint8_t cmd[3] = {uint8_t(int(Status::SongPositionPointer)),
|
||
|
uint8_t((pointer % 128) & 0x7f), uint8_t((pointer / 128) & 0x7f)};
|
||
|
_sendMessage(cmd, 3);
|
||
|
}
|
||
|
|
||
|
void songSelect(uint8_t song)
|
||
|
{
|
||
|
uint8_t cmd[2] = {uint8_t(int(Status::TimecodeQuarterFrame)),
|
||
|
uint8_t(song & 0x7f)};
|
||
|
_sendMessage(cmd, 2);
|
||
|
}
|
||
|
|
||
|
void tuneRequest()
|
||
|
{
|
||
|
uint8_t cmd = uint8_t(Status::TuneRequest);
|
||
|
_sendMessage(&cmd, 1);
|
||
|
}
|
||
|
|
||
|
|
||
|
void startSeq()
|
||
|
{
|
||
|
uint8_t cmd = uint8_t(Status::Start);
|
||
|
_sendMessage(&cmd, 1);
|
||
|
}
|
||
|
|
||
|
void continueSeq()
|
||
|
{
|
||
|
uint8_t cmd = uint8_t(Status::Continue);
|
||
|
_sendMessage(&cmd, 1);
|
||
|
}
|
||
|
|
||
|
void stopSeq()
|
||
|
{
|
||
|
uint8_t cmd = uint8_t(Status::Stop);
|
||
|
_sendMessage(&cmd, 1);
|
||
|
}
|
||
|
|
||
|
|
||
|
void reset()
|
||
|
{
|
||
|
uint8_t cmd = uint8_t(Status::Reset);
|
||
|
_sendMessage(&cmd, 1);
|
||
|
}
|
||
|
|
||
|
const std::vector<uint8_t>& getResult() const {return m_result;}
|
||
|
std::vector<uint8_t>& getResult() {return m_result;}
|
||
|
};
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, Target& targetOut)
|
||
|
{
|
||
|
std::vector<uint8_t> ret = {'M', 'T', 'h', 'd'};
|
||
|
uint32_t six32 = SBig(uint32_t(6));
|
||
|
for (int i=0 ; i<4 ; ++i)
|
||
|
ret.push_back(reinterpret_cast<uint8_t*>(&six32)[i]);
|
||
|
|
||
|
ret.push_back(0);
|
||
|
ret.push_back(1);
|
||
|
|
||
|
SongState song;
|
||
|
song.initialize(data);
|
||
|
|
||
|
size_t trkCount = 1;
|
||
|
for (std::experimental::optional<SongState::Track>& trk : song.m_tracks)
|
||
|
if (trk)
|
||
|
++trkCount;
|
||
|
|
||
|
uint16_t trkCount16 = SBig(uint16_t(trkCount));
|
||
|
ret.push_back(reinterpret_cast<uint8_t*>(&trkCount16)[0]);
|
||
|
ret.push_back(reinterpret_cast<uint8_t*>(&trkCount16)[1]);
|
||
|
|
||
|
uint16_t tickDiv16 = SBig(uint16_t(384));
|
||
|
ret.push_back(reinterpret_cast<uint8_t*>(&tickDiv16)[0]);
|
||
|
ret.push_back(reinterpret_cast<uint8_t*>(&tickDiv16)[1]);
|
||
|
|
||
|
/* Intermediate event */
|
||
|
struct Event
|
||
|
{
|
||
|
bool endEvent = false;
|
||
|
bool controlChange = false;
|
||
|
uint8_t channel;
|
||
|
uint8_t noteOrCtrl;
|
||
|
uint8_t velOrVal;
|
||
|
uint16_t length;
|
||
|
|
||
|
bool isPitchBend = false;
|
||
|
int16_t pitchBend;
|
||
|
|
||
|
Event(int16_t pBend) : isPitchBend(true), pitchBend(pBend) {}
|
||
|
|
||
|
Event(bool ctrlCh, uint8_t chan, uint8_t note, uint8_t vel, uint16_t len)
|
||
|
: controlChange(ctrlCh), channel(chan), noteOrCtrl(note), velOrVal(vel), length(len) {}
|
||
|
};
|
||
|
|
||
|
/* Write tempo track */
|
||
|
{
|
||
|
MIDIEncoder encoder;
|
||
|
|
||
|
/* Initial tempo */
|
||
|
encoder._sendContinuedValue(0);
|
||
|
encoder.getResult().push_back(0xff);
|
||
|
encoder.getResult().push_back(0x51);
|
||
|
encoder.getResult().push_back(3);
|
||
|
|
||
|
uint32_t tempo24 = SBig(60000000 / song.m_tempo);
|
||
|
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)
|
||
|
{
|
||
|
SongState::TempoChange change = *song.m_tempoPtr;
|
||
|
if (song.m_bigEndian)
|
||
|
change.swapBig();
|
||
|
|
||
|
encoder._sendContinuedValue(change.m_tick - lastTick);
|
||
|
lastTick = change.m_tick;
|
||
|
encoder.getResult().push_back(0xff);
|
||
|
encoder.getResult().push_back(0x51);
|
||
|
encoder.getResult().push_back(3);
|
||
|
|
||
|
uint32_t tempo24 = SBig(60000000 / change.m_tempo);
|
||
|
for (int i=1 ; i<4 ; ++i)
|
||
|
encoder.getResult().push_back(reinterpret_cast<uint8_t*>(&tempo24)[i]);
|
||
|
|
||
|
++song.m_tempoPtr;
|
||
|
}
|
||
|
|
||
|
encoder.getResult().push_back(0);
|
||
|
encoder.getResult().push_back(0xff);
|
||
|
encoder.getResult().push_back(0x2f);
|
||
|
encoder.getResult().push_back(0);
|
||
|
|
||
|
ret.push_back('M');
|
||
|
ret.push_back('T');
|
||
|
ret.push_back('r');
|
||
|
ret.push_back('k');
|
||
|
uint32_t trkSz = SBig(uint32_t(encoder.getResult().size()));
|
||
|
for (int i=0 ; i<4 ; ++i)
|
||
|
ret.push_back(reinterpret_cast<uint8_t*>(&trkSz)[i]);
|
||
|
ret.insert(ret.cend(), encoder.getResult().begin(), encoder.getResult().end());
|
||
|
}
|
||
|
|
||
|
/* Iterate each SNG track into type-1 MIDI track */
|
||
|
for (std::experimental::optional<SongState::Track>& trk : song.m_tracks)
|
||
|
{
|
||
|
if (trk)
|
||
|
{
|
||
|
std::multimap<int, Event> events;
|
||
|
|
||
|
/* Iterate all regions */
|
||
|
while (trk->m_nextRegion->indexValid())
|
||
|
{
|
||
|
trk->advanceRegion(nullptr);
|
||
|
uint32_t regStart = song.m_bigEndian ? SBig(trk->m_curRegion->m_startTick) : trk->m_curRegion->m_startTick;
|
||
|
|
||
|
/* Update continuous pitch data */
|
||
|
if (trk->m_pitchWheelData)
|
||
|
{
|
||
|
while (true)
|
||
|
{
|
||
|
/* See if there's an upcoming pitch change in this interval */
|
||
|
const unsigned char* ptr = trk->m_pitchWheelData;
|
||
|
uint32_t deltaTicks = DecodeRLE(ptr);
|
||
|
if (deltaTicks != 0xffffffff)
|
||
|
{
|
||
|
int32_t nextTick = trk->m_lastPitchTick + deltaTicks;
|
||
|
int32_t pitchDelta = DecodeContinuousRLE(ptr);
|
||
|
trk->m_lastPitchVal += pitchDelta;
|
||
|
trk->m_pitchWheelData = ptr;
|
||
|
trk->m_lastPitchTick = nextTick;
|
||
|
events.emplace(regStart + nextTick, Event{clamp(0, trk->m_lastPitchVal / 2 + 0x2000, 0x4000)});
|
||
|
}
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Update continuous modulation data */
|
||
|
if (trk->m_modWheelData)
|
||
|
{
|
||
|
while (true)
|
||
|
{
|
||
|
/* See if there's an upcoming modulation change in this interval */
|
||
|
const unsigned char* ptr = trk->m_modWheelData;
|
||
|
uint32_t deltaTicks = DecodeRLE(ptr);
|
||
|
if (deltaTicks != 0xffffffff)
|
||
|
{
|
||
|
int32_t nextTick = trk->m_lastModTick + deltaTicks;
|
||
|
int32_t modDelta = DecodeContinuousRLE(ptr);
|
||
|
trk->m_lastModVal += modDelta;
|
||
|
trk->m_modWheelData = ptr;
|
||
|
trk->m_lastModTick = nextTick;
|
||
|
events.emplace(regStart + nextTick, Event{true, trk->m_midiChan, 1, clamp(0, trk->m_lastModVal * 128 / 16384, 127), 0});
|
||
|
}
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Loop through as many commands as we can for this time period */
|
||
|
if (song.m_header.m_trackIdxOff == 0x18 || song.m_header.m_trackIdxOff == 0x58)
|
||
|
{
|
||
|
/* GameCube */
|
||
|
while (true)
|
||
|
{
|
||
|
/* Load next command */
|
||
|
if (*reinterpret_cast<const uint16_t*>(trk->m_data) == 0xffff)
|
||
|
{
|
||
|
/* End of channel */
|
||
|
trk->m_data = nullptr;
|
||
|
break;
|
||
|
}
|
||
|
else if (trk->m_data[0] & 0x80)
|
||
|
{
|
||
|
/* Control change */
|
||
|
uint8_t val = trk->m_data[0] & 0x7f;
|
||
|
uint8_t ctrl = trk->m_data[1] & 0x7f;
|
||
|
events.emplace(regStart + trk->m_eventWaitCountdown, Event{true, trk->m_midiChan, ctrl, val, 0});
|
||
|
trk->m_data += 2;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Note */
|
||
|
uint8_t note = trk->m_data[0] & 0x7f;
|
||
|
uint8_t vel = trk->m_data[1] & 0x7f;
|
||
|
uint16_t length = (song.m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(trk->m_data + 2)) :
|
||
|
*reinterpret_cast<const uint16_t*>(trk->m_data + 2));
|
||
|
if (length)
|
||
|
events.emplace(regStart + trk->m_eventWaitCountdown, Event{false, trk->m_midiChan, note, vel, length});
|
||
|
trk->m_data += 4;
|
||
|
}
|
||
|
|
||
|
/* Set next delta-time */
|
||
|
trk->m_eventWaitCountdown += int32_t(DecodeTimeRLE(trk->m_data));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* N64 */
|
||
|
while (true)
|
||
|
{
|
||
|
/* Load next command */
|
||
|
if (*reinterpret_cast<const uint32_t*>(trk->m_data) == 0xffff0000)
|
||
|
{
|
||
|
/* End of channel */
|
||
|
trk->m_data = nullptr;
|
||
|
break;
|
||
|
}
|
||
|
else if (trk->m_data[0] & 0x80)
|
||
|
{
|
||
|
/* Control change */
|
||
|
uint8_t val = trk->m_data[0] & 0x7f;
|
||
|
uint8_t ctrl = trk->m_data[1] & 0x7f;
|
||
|
events.emplace(regStart + trk->m_eventWaitCountdown, Event{true, trk->m_midiChan, ctrl, val, 0});
|
||
|
trk->m_data += 2;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ((trk->m_data[2] & 0x80) != 0x80)
|
||
|
{
|
||
|
/* Note */
|
||
|
uint16_t length = (song.m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(trk->m_data)) :
|
||
|
*reinterpret_cast<const uint16_t*>(trk->m_data));
|
||
|
uint8_t note = trk->m_data[2] & 0x7f;
|
||
|
uint8_t vel = trk->m_data[3] & 0x7f;
|
||
|
if (length)
|
||
|
events.emplace(regStart + trk->m_eventWaitCountdown, Event{false, trk->m_midiChan, note, vel, length});
|
||
|
}
|
||
|
trk->m_data += 4;
|
||
|
}
|
||
|
|
||
|
/* Set next delta-time */
|
||
|
int32_t absTick = (song.m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(trk->m_data)) :
|
||
|
*reinterpret_cast<const int32_t*>(trk->m_data));
|
||
|
trk->m_eventWaitCountdown += absTick - trk->m_lastN64EventTick;
|
||
|
trk->m_lastN64EventTick = absTick;
|
||
|
trk->m_data += 4;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Resolve key-off events */
|
||
|
std::multimap<int, Event> offEvents;
|
||
|
for (auto& pair : events)
|
||
|
{
|
||
|
if (!pair.second.controlChange)
|
||
|
{
|
||
|
auto it = offEvents.emplace(pair.first + pair.second.length, pair.second);
|
||
|
it->second.endEvent = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Merge key-off events */
|
||
|
events.insert(offEvents.begin(), offEvents.end());
|
||
|
|
||
|
/* Emit MIDI events */
|
||
|
MIDIEncoder encoder;
|
||
|
int lastTime = 0;
|
||
|
for (auto& pair : events)
|
||
|
{
|
||
|
encoder._sendContinuedValue(pair.first - lastTime);
|
||
|
lastTime = pair.first;
|
||
|
|
||
|
if (pair.second.controlChange)
|
||
|
{
|
||
|
encoder.controlChange(pair.second.channel, pair.second.noteOrCtrl, pair.second.velOrVal);
|
||
|
}
|
||
|
else if (pair.second.isPitchBend)
|
||
|
{
|
||
|
encoder.pitchBend(trk->m_midiChan, pair.second.pitchBend);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (pair.second.endEvent)
|
||
|
encoder.noteOff(pair.second.channel, pair.second.noteOrCtrl, pair.second.velOrVal);
|
||
|
else
|
||
|
encoder.noteOn(pair.second.channel, pair.second.noteOrCtrl, pair.second.velOrVal);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
encoder.getResult().push_back(0);
|
||
|
encoder.getResult().push_back(0xff);
|
||
|
encoder.getResult().push_back(0x2f);
|
||
|
encoder.getResult().push_back(0);
|
||
|
|
||
|
/* Write out */
|
||
|
ret.push_back('M');
|
||
|
ret.push_back('T');
|
||
|
ret.push_back('r');
|
||
|
ret.push_back('k');
|
||
|
uint32_t trkSz = SBig(uint32_t(encoder.getResult().size()));
|
||
|
for (int i=0 ; i<4 ; ++i)
|
||
|
ret.push_back(reinterpret_cast<uint8_t*>(&trkSz)[i]);
|
||
|
ret.insert(ret.cend(), encoder.getResult().begin(), encoder.getResult().end());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
std::vector<uint8_t> SongConverter::MIDIToSong(const unsigned char* data, Target target)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|