gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit cb7df7ce21a076783e8668364e7462cbe006e3f8
parent 30c101d5e1659b98ac93be69cfefb1aa1fa3cb6d
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Mon, 10 Mar 2025 20:07:25 +0100

add support for MW1 user waves and control tables

Diffstat:
Mdoc/changelog.txt | 6++++++
Msource/xtJucePlugin/xtWaveEditor.cpp | 44+++++++++++++++++++++++++++++++++++++++++++-
Msource/xtLib/xtMidiTypes.h | 12++++++++++++
Msource/xtLib/xtState.cpp | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msource/xtLib/xtState.h | 4++++
5 files changed, 167 insertions(+), 30 deletions(-)

diff --git a/doc/changelog.txt b/doc/changelog.txt @@ -1,5 +1,11 @@ Release Notes +1.4.5 + +Xenia: + +- [Imp] Add support to load MW1 user waves & tables + 1.4.4 NodalRed2x: diff --git a/source/xtJucePlugin/xtWaveEditor.cpp b/source/xtJucePlugin/xtWaveEditor.cpp @@ -239,7 +239,7 @@ namespace xtJucePlugin const auto title = getEditor().getProcessor().getProperties().name + " - "; - const auto sysex = WaveTreeItem::getSysexFromFiles(_files); + auto sysex = WaveTreeItem::getSysexFromFiles(_files); if(sysex.empty()) { @@ -247,6 +247,48 @@ namespace xtJucePlugin return; } + if (sysex.size() == 1) + { + auto s = sysex.front(); + + if (s.size() == xt::Mw1::g_allWavesAndTablesDumpLength && + s.front() == 0xf0 && + s[1] == wLib::IdWaldorf && + s[2] == xt::IdMw1 && + s[4] == xt::Mw1::g_idmAllWavesAndTables) + { + // contains 12 tables and 61 waves + static constexpr uint32_t tableSize = 64 * 4; + static constexpr uint32_t waveSize = 64 * 2; + static_assert(xt::Mw1::g_allWavesAndTablesDumpLength == 5 + 2 + tableSize * 12 + waveSize * 61); + + int32_t off = 5; + for (size_t t=0; t<12; ++t, off += tableSize) + { + std::vector<uint8_t> data = { 0xf0, wLib::IdWaldorf, xt::IdMw1, wLib::IdDeviceOmni, xt::Mw1::g_idmTable, static_cast<uint8_t>(xt::Mw1::g_firstRamTableIndex + t) }; + data.insert(data.end(), s.begin() + off, s.begin() + off + tableSize); + data.push_back(0x00); + data.push_back(0xf7); + sysex.emplace_back(std::move(data)); + } + for (size_t w=0; w<61; ++w, off += waveSize) + { + auto waveIndex = static_cast<uint16_t>(xt::Mw1::g_firstRamWaveIndex + w); + + std::vector<uint8_t> data = { 0xf0, wLib::IdWaldorf, xt::IdMw1, wLib::IdDeviceOmni, xt::Mw1::g_idmWave, + static_cast<uint8_t>(waveIndex >> 12), + static_cast<uint8_t>((waveIndex >> 8) & 0xf), + static_cast<uint8_t>((waveIndex >> 4) & 0xf), + static_cast<uint8_t>(waveIndex & 0xf) }; + data.insert(data.end(), s.begin() + off, s.begin() + off + waveSize); + data.push_back(0x00); + data.push_back(0xf7); + sysex.emplace_back(std::move(data)); + } + sysex.erase(sysex.begin()); + } + } + for (const auto& s : sysex) { xt::TableData table; diff --git a/source/xtLib/xtMidiTypes.h b/source/xtLib/xtMidiTypes.h @@ -186,14 +186,26 @@ namespace xt namespace Mw1 { static constexpr uint32_t g_singleLength = 180; // without sysex header + static constexpr uint32_t g_singleDumpLength = 187; // with sysex header + static constexpr uint32_t g_waveDumpLength = 139; + static constexpr uint32_t g_tableDumpLength = 264; + static constexpr uint32_t g_allWavesAndTablesDumpLength = 10887; + static constexpr uint32_t g_singleNameLength = 16; static constexpr uint32_t g_singleNamePosition = 153; // in a dump including sysex header static constexpr uint32_t g_sysexHeaderSize = 5; static constexpr uint32_t g_sysexFooterSize = 2; static constexpr uint32_t g_idmPresetBank = 0x50; static constexpr uint32_t g_idmCartridgeBank = 0x54; + static constexpr uint32_t g_idmPreset = 0x42; + static constexpr uint32_t g_idmWave = 0x44; + static constexpr uint32_t g_idmTable = 0x45; + static constexpr uint32_t g_idmAllWavesAndTables = 0x53; + + static constexpr uint32_t g_firstRamWaveIndex = 246; + static constexpr uint32_t g_firstRamTableIndex = 32; }; namespace mw2 diff --git a/source/xtLib/xtState.cpp b/source/xtLib/xtState.cpp @@ -769,11 +769,23 @@ namespace xt TableId State::getTableId(const SysEx& _data) { + if (_data.size() == Mw1::g_tableDumpLength) + return TableId(_data[5] + wave::g_firstRamTableIndex - Mw1::g_firstRamTableIndex); return TableId(static_cast<uint16_t>((_data[IdxWaveIndexH] << 7) | _data[IdxWaveIndexL])); } WaveId State::getWaveId(const SysEx& _data) { + if (_data.size() == Mw1::g_waveDumpLength) + { + const uint16_t id = + static_cast<uint16_t>(_data[5] << 12) | + static_cast<uint16_t>(_data[6] << 8) | + static_cast<uint16_t>(_data[7] << 4) | + static_cast<uint16_t>(_data[8]); + + return WaveId(static_cast<uint16_t>(id + wave::g_firstRamWaveIndex - Mw1::g_firstRamWaveIndex)); + } return WaveId(static_cast<uint16_t>((_data[IdxWaveIndexH] << 7) | _data[IdxWaveIndexL])); } @@ -992,42 +1004,70 @@ namespace xt */ } + namespace + { + void extractWaveDataFromSysEx(WaveData& _wave, const SysEx& _sysex, const uint32_t _off) + { + /* + mw2_sysex.pdf: + + "A Wave consists of 128 eight Bit samples, but only the first 64 of them are + stored/transmitted, the second half is same as first except the values are + negated and the order is reversed: + + Wave[64+n] = -Wave[63-n] for n=0..63 + + Note that samples are not two's complement format, to get a signed byte, + the most significant bit must be flipped: + + signed char s = Wave[n] ^ 0x80" + */ + + for(uint32_t i=0; i<_wave.size()>>1; ++i) + { + const auto idx = _off + (i<<1); + auto sample = (_sysex[idx]) << 4 | _sysex[idx+1]; + sample = sample ^ 0x80; + + _wave[i] = static_cast<int8_t>(sample); + _wave[127-i] = static_cast<int8_t>(-sample); + } + } + } + bool State::parseWaveData(WaveData& _wave, const SysEx& _sysex) { if(_sysex.size() != std::tuple_size_v<Wave>) - return false; + return parseMw1WaveData(_wave, _sysex); if(_sysex.front() != 0xf0 || _sysex[1] != wLib::IdWaldorf || _sysex[2] != IdMw2) return false; if(_sysex[4] != static_cast<uint8_t>(SysexCommand::WaveDump) && _sysex[4] != static_cast<uint8_t>(SysexCommand::WaveDumpP)) return false; - /* - mw2_sysex.pdf: - "A Wave consists of 128 eight Bit samples, but only the first 64 of them are - stored/transmitted, the second half is same as first except the values are - negated and the order is reversed: + constexpr auto off = 7; - Wave[64+n] = -Wave[63-n] for n=0..63 + extractWaveDataFromSysEx(_wave, _sysex, off); - Note that samples are not two's complement format, to get a signed byte, - the most significant bit must be flipped: + return true; + } - signed char s = Wave[n] ^ 0x80" - */ + bool State::parseMw1WaveData(WaveData& _wave, const SysEx& _sysex) + { + if (_sysex.size() != Mw1::g_waveDumpLength) + return false; - constexpr auto off = 7; + if(_sysex.front() != 0xf0 || _sysex[1] != wLib::IdWaldorf || _sysex[2] != IdMw1) + return false; - for(uint32_t i=0; i<_wave.size()>>1; ++i) - { - const auto idx = off + (i<<1); - auto sample = (_sysex[idx]) << 4 | _sysex[idx+1]; - sample = sample ^ 0x80; + if(_sysex[4] != Mw1::g_idmWave) + return false; + + constexpr auto off = 9; + + extractWaveDataFromSysEx(_wave, _sysex, off); - _wave[i] = static_cast<int8_t>(sample); - _wave[127-i] = static_cast<int8_t>(-sample); - } return true; } @@ -1077,10 +1117,28 @@ namespace xt return result; } + namespace + { + void extractTableDataFromSysEx(TableData& _table, const SysEx& _sysex, const uint32_t _off) + { + for(uint32_t i=0; i<_table.size(); ++i) + { + const auto i4 = i<<2; + + auto waveIdx = _sysex[i4+_off] << 12; + waveIdx |= _sysex[i4+_off+1] << 8; + waveIdx |= _sysex[i4+_off+2] << 4; + waveIdx |= _sysex[i4+_off+3]; + + _table[i] = WaveId(static_cast<uint16_t>(waveIdx)); + } + } + } + bool State::parseTableData(TableData& _table, const SysEx& _sysex) { if(_sysex.size() != std::tuple_size_v<Table>) - return false; + return parseMw1TableData(_table, _sysex); if(_sysex[0] != 0xf0 || _sysex[1] != wLib::IdWaldorf || _sysex[2] != IdMw2) return false; @@ -1090,16 +1148,31 @@ namespace xt constexpr uint32_t off = 7; - for(uint32_t i=0; i<_table.size(); ++i) - { - const auto i4 = i<<2; + extractTableDataFromSysEx(_table, _sysex, off); + + return true; + } - auto waveIdx = _sysex[i4+off] << 12; - waveIdx |= _sysex[i4+off+1] << 8; - waveIdx |= _sysex[i4+off+2] << 4; - waveIdx |= _sysex[i4+off+3]; + bool State::parseMw1TableData(TableData& _table, const SysEx& _sysex) + { + if (_sysex.size() != Mw1::g_tableDumpLength) + return false; + + if(_sysex[0] != 0xf0 || _sysex[1] != wLib::IdWaldorf || _sysex[2] != IdMw1) + return false; - _table[i] = WaveId(static_cast<uint16_t>(waveIdx)); + extractTableDataFromSysEx(_table, _sysex, 6); + + if (_table[0].rawId() == 0xdead && _table[1].rawId() == 0xbeef) + { + _table[2] = WaveId(_table[2].rawId() + wave::g_firstRamWaveIndex - Mw1::g_firstRamWaveIndex); + } + else + { + for(auto & t : _table) + { + t = WaveId(t.rawId() + wave::g_firstRamWaveIndex - Mw1::g_firstRamWaveIndex); + } } return true; } diff --git a/source/xtLib/xtState.h b/source/xtLib/xtState.h @@ -99,10 +99,14 @@ namespace xt static void createSequencerMultiData(std::vector<uint8_t>& _data); static bool parseWaveData(WaveData& _wave, const SysEx& _sysex); + static bool parseMw1WaveData(WaveData& _wave, const SysEx& _sysex); + static SysEx createWaveData(const WaveData& _wave, uint16_t _waveIndex, bool _preview); static WaveData createinterpolatedTable(const WaveData& _a, const WaveData& _b, uint16_t _indexA, uint16_t _indexB, uint16_t _indexTarget); static bool parseTableData(TableData& _table, const SysEx& _sysex); + static bool parseMw1TableData(TableData& _table, const SysEx& _sysex); + static SysEx createTableData(const TableData& _table, uint32_t _tableIndex, bool _preview); static SysEx createCombinedPatch(const std::vector<SysEx>& _dumps);