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