#include "amuse/SongConverter.hpp" #include "amuse/SongState.hpp" #include "amuse/Common.hpp" #include #include 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::const_iterator& it, std::vector::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::const_iterator receiveBytes(std::vector::const_iterator begin, std::vector::const_iterator end) { std::vector::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 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= 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(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& getResult() const {return m_result;} std::vector& 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(data)); if (thisPart == 0xffff) { ret += 65535; data += 4; continue; } ret += thisPart; data += 2; break; } return ret; } std::vector SongConverter::SongToMIDI(const unsigned char* data, Target& targetOut) { std::vector ret = {'M', 'T', 'h', 'd'}; uint32_t six32 = SBig(uint32_t(6)); for (int i=0 ; i<4 ; ++i) ret.push_back(reinterpret_cast(&six32)[i]); ret.push_back(0); ret.push_back(1); SongState song; song.initialize(data); size_t trkCount = 1; for (std::experimental::optional& trk : song.m_tracks) if (trk) ++trkCount; uint16_t trkCount16 = SBig(uint16_t(trkCount)); ret.push_back(reinterpret_cast(&trkCount16)[0]); ret.push_back(reinterpret_cast(&trkCount16)[1]); uint16_t tickDiv16 = SBig(uint16_t(384)); ret.push_back(reinterpret_cast(&tickDiv16)[0]); ret.push_back(reinterpret_cast(&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(&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(&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(&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& trk : song.m_tracks) { if (trk) { std::multimap 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(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(trk->m_data + 2)) : *reinterpret_cast(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(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(trk->m_data)) : *reinterpret_cast(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(trk->m_data)) : *reinterpret_cast(trk->m_data)); trk->m_eventWaitCountdown += absTick - trk->m_lastN64EventTick; trk->m_lastN64EventTick = absTick; trk->m_data += 4; } } } /* Resolve key-off events */ std::multimap 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(&trkSz)[i]); ret.insert(ret.cend(), encoder.getResult().begin(), encoder.getResult().end()); } } return ret; } std::vector SongConverter::MIDIToSong(const unsigned char* data, Target target) { } }