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 6df0da0e47605785bed343638edf5220e34d236b
parent 2bc955ffb2cf57dd8cec2cdbd3e58a7a8ad56a17
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Tue, 24 Sep 2024 22:53:00 +0200

wave editor first realtime preview now working

Diffstat:
Msource/xtJucePlugin/weGraph.cpp | 4++--
Msource/xtJucePlugin/weGraph.h | 2+-
Msource/xtJucePlugin/weGraphData.cpp | 33++++++++++++++++++++++++++++-----
Msource/xtJucePlugin/weGraphData.h | 6+++++-
Msource/xtJucePlugin/xtController.h | 1+
Msource/xtJucePlugin/xtWaveEditor.cpp | 19+++++++++++++++++++
Msource/xtJucePlugin/xtWaveEditor.h | 2++
Msource/xtLib/xtDevice.cpp | 2+-
Msource/xtLib/xtDevice.h | 2++
Msource/xtLib/xtMidiTypes.h | 7++++++-
Msource/xtLib/xtState.cpp | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msource/xtLib/xtState.h | 9++++++++-
Msource/xtLib/xtWavePreview.cpp | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/xtLib/xtWavePreview.h | 15+++++++++++++++
14 files changed, 221 insertions(+), 14 deletions(-)

diff --git a/source/xtJucePlugin/weGraph.cpp b/source/xtJucePlugin/weGraph.cpp @@ -7,7 +7,7 @@ namespace xtJucePlugin { Graph::Graph(WaveEditor& _editor) : m_editor(_editor), m_data(_editor.getGraphData()) { - m_onSourceChanged.set(m_data.onSourceChanged, [this](const xt::WaveData&) + m_onDataChanged.set(m_data.onChanged, [this]() { onSourceChanged(); }); @@ -257,7 +257,7 @@ namespace xtJucePlugin if(i < 0) continue; - if(i >= getDataSize()) + if(i >= static_cast<int32_t>(getDataSize())) return; modifyValue(i, v); diff --git a/source/xtJucePlugin/weGraph.h b/source/xtJucePlugin/weGraph.h @@ -78,7 +78,7 @@ namespace xtJucePlugin WaveEditor& m_editor; GraphData& m_data; - pluginLib::EventListener<xt::WaveData> m_onSourceChanged; + pluginLib::EventListener<> m_onDataChanged; std::set<uint32_t> m_highlightedIndices; uint32_t m_hoveredIndex = InvalidIndex; diff --git a/source/xtJucePlugin/weGraphData.cpp b/source/xtJucePlugin/weGraphData.cpp @@ -4,6 +4,8 @@ namespace xtJucePlugin { + // ReSharper disable CppClangTidyClangDiagnosticFloatEqual + constexpr float g_pi = 3.1415926535f; constexpr auto g_size = std::tuple_size_v<xt::WaveData>; @@ -30,7 +32,7 @@ namespace xtJucePlugin updateFrequenciesAndPhases(); - onSourceChanged(m_source); + sendChangedEvents(); } void GraphData::setData(const uint32_t _index, const float _value) @@ -40,7 +42,7 @@ namespace xtJucePlugin m_data[_index] = _value; m_data[m_data.size() - _index - 1] = -_value; updateFrequenciesAndPhases(); - onSourceChanged(m_source); + sendChangedEvents(); } void GraphData::setFreq(const uint32_t _index, const float _value) @@ -49,7 +51,7 @@ namespace xtJucePlugin return; m_frequencies[_index] = _value; updateDataFromFrequenciesAndPhases(); - onSourceChanged(m_source); + sendChangedEvents(); } void GraphData::setPhase(const uint32_t _index, const float _value) @@ -58,7 +60,7 @@ namespace xtJucePlugin return; m_phases[_index] = _value; updateDataFromFrequenciesAndPhases(); - onSourceChanged(m_source); + sendChangedEvents(); } void GraphData::updateFrequenciesAndPhases() @@ -109,8 +111,29 @@ namespace xtJucePlugin m_fft.perform(m_fftInData.data(), m_fftOutData.data(), true); for(uint32_t i=0; i<m_data.size(); ++i) - { m_data[i] = m_fftOutData[i].real(); + } + + bool GraphData::updateSourceFromData() + { + bool changed = false; + for(size_t i=0; i<m_data.size(); ++i) + { + const auto d = static_cast<int8_t>(std::clamp<int32_t>(static_cast<int32_t>(std::round(m_data[i] * 128.0f)), -127, 127)); + + if(m_source[i] == d) + continue; + m_source[i] = d; + changed = true; } + return changed; + } + + void GraphData::sendChangedEvents() + { + onChanged(); + + if(updateSourceFromData()) + onIntegerChanged(m_source); } } diff --git a/source/xtJucePlugin/weGraphData.h b/source/xtJucePlugin/weGraphData.h @@ -12,7 +12,8 @@ namespace xtJucePlugin class GraphData { public: - pluginLib::Event<xt::WaveData> onSourceChanged; + pluginLib::Event<> onChanged; + pluginLib::Event<xt::WaveData> onIntegerChanged; GraphData(); @@ -21,6 +22,7 @@ namespace xtJucePlugin const auto& getData() const { return m_data; } const auto& getFrequencies() const { return m_frequencies; } const auto& getPhases() const { return m_phases; } + const auto& getSource() const { return m_source; } void setData(uint32_t _index, float _value); void setFreq(uint32_t _index, float _value); @@ -29,6 +31,8 @@ namespace xtJucePlugin private: void updateFrequenciesAndPhases(); void updateDataFromFrequenciesAndPhases(); + bool updateSourceFromData(); + void sendChangedEvents(); xt::WaveData m_source; diff --git a/source/xtJucePlugin/xtController.h b/source/xtJucePlugin/xtController.h @@ -65,6 +65,7 @@ namespace xtJucePlugin bool sendSysEx(MidiPacketType _type) const; bool sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const; + using pluginLib::Controller::sendSysEx; bool isMultiMode() const; void setPlayMode(bool _multiMode); diff --git a/source/xtJucePlugin/xtWaveEditor.cpp b/source/xtJucePlugin/xtWaveEditor.cpp @@ -7,8 +7,10 @@ #include "weGraphFreq.h" #include "weGraphPhase.h" #include "weGraphTime.h" +#include "xtController.h" #include "xtEditor.h" +#include "xtLib/xtState.h" namespace xtJucePlugin { @@ -23,6 +25,11 @@ namespace xtJucePlugin setSelectedWave(_waveIndex, true); }); + + m_graphData.onIntegerChanged.addListener([this](const xt::WaveData& _data) + { + onWaveDataChanged(_data); + }); } WaveEditor::~WaveEditor() @@ -121,6 +128,15 @@ namespace xtJucePlugin m_ledWavetablePreview->setToggleState(_enabled, juce::dontSendNotification); } + void WaveEditor::onWaveDataChanged(const xt::WaveData& _data) const + { + if(m_btWavePreview->getToggleState()) + { + const auto sysex = xt::State::createWaveData(_data, m_editor.getXtController().getCurrentPart(), true); + m_editor.getXtController().sendSysEx(sysex); + } + } + void WaveEditor::onReceiveWave(const pluginLib::MidiPacket::Data& _data, const std::vector<uint8_t>& _msg) { m_data.onReceiveWave(_data, _msg); @@ -148,6 +164,9 @@ namespace xtJucePlugin m_selectedWave = _waveIndex; if(const auto wave = m_data.getWave(_waveIndex)) + { m_graphData.set(*wave); + onWaveDataChanged(*wave); + } } } diff --git a/source/xtJucePlugin/xtWaveEditor.h b/source/xtJucePlugin/xtWaveEditor.h @@ -59,6 +59,8 @@ namespace xtJucePlugin void toggleWavePreview(bool _enabled); void toggleWavetablePreview(bool _enabled); + void onWaveDataChanged(const xt::WaveData& _data) const; + Editor& m_editor; std::unique_ptr<WaveTree> m_waveTree; diff --git a/source/xtLib/xtDevice.cpp b/source/xtLib/xtDevice.cpp @@ -9,7 +9,7 @@ namespace mqLib namespace xt { - Device::Device() : m_state(m_xt), m_sysexRemote(m_xt) + Device::Device() : m_wavePreview(m_xt), m_state(m_xt, m_wavePreview), m_sysexRemote(m_xt) { while(!m_xt.isBootCompleted()) m_xt.process(8); diff --git a/source/xtLib/xtDevice.h b/source/xtLib/xtDevice.h @@ -3,6 +3,7 @@ #include "xt.h" #include "xtState.h" #include "xtSysexRemoteControl.h" +#include "xtWavePreview.h" #include "wLib/wDevice.h" namespace dsp56k @@ -33,6 +34,7 @@ namespace xt private: Xt m_xt; + WavePreview m_wavePreview; State m_state; SysexRemoteControl m_sysexRemote; }; diff --git a/source/xtLib/xtMidiTypes.h b/source/xtLib/xtMidiTypes.h @@ -30,6 +30,8 @@ namespace xt WaveRequestP = 0x09, WaveDumpP = 0x19, WaveParameterChangeP = 0x29, WaveStoreP = 0x39, WaveRecallP = 0x49, WaveCompareP = 0x59, WaveCtlRequestP = 0x0a, WaveCtlDumpP = 0x1a, WaveCtlParameterChangeP = 0x2a, WaveCtlStoreP = 0x3a, WaveCtlRecallP = 0x4a, WaveCtlCompareP = 0x5a, + WavePreviewMode = WaveStoreP, + // emu remote control support EmuLCD = 0x60, EmuLEDs = 0x61, @@ -76,7 +78,10 @@ namespace xt IdxModeParamIndexH = wLib::IdxBuffer, IdxModeParamIndexL = IdxModeParamIndexH, - IdxModeParamValue = wLib::IdxBuffer + IdxModeParamValue = wLib::IdxBuffer, + + IdxWaveIndexH = wLib::IdxBuffer, + IdxWaveIndexL = IdxWaveIndexH + 1 }; enum class GlobalParameter diff --git a/source/xtLib/xtState.cpp b/source/xtLib/xtState.cpp @@ -4,6 +4,7 @@ #include "xtMidiTypes.h" #include "xt.h" +#include "xtWavePreview.h" #include "synthLib/os.h" #include "synthLib/midiToSysex.h" @@ -15,7 +16,7 @@ namespace xt { static_assert(std::size(State::Dumps) == static_cast<uint32_t>(State::DumpType::Count), "data definition missing"); - State::State(Xt& _xt) : m_xt(_xt) + State::State(Xt& _xt, WavePreview& _preview) : m_xt(_xt), m_wavePreview(_preview) { } @@ -160,6 +161,10 @@ namespace xt case SysexCommand::GlobalParameterChange: return modifyDump(DumpType::Global, _data); case SysexCommand::ModeParameterChange: return modifyDump(DumpType::Mode, _data); + case SysexCommand::WaveDumpP: return m_wavePreview.receiveWave(_data); + case SysexCommand::WaveCtlDumpP: return m_wavePreview.receiveWaveControlTable(_data); + case SysexCommand::WavePreviewMode: return m_wavePreview.receiveWavePreviewMode(_data); + /* case SysexCommand::EmuLCD: case SysexCommand::EmuLEDs: case SysexCommand::EmuButtons: @@ -721,7 +726,8 @@ namespace xt for(uint32_t i=0; i<_wave.size()>>1; ++i) { - auto sample = (_sysex[off + (i<<1)]) << 4 | _sysex[off + (i<<1) + 1]; + const auto idx = off + (i<<1); + auto sample = (_sysex[idx]) << 4 | _sysex[idx+1]; sample = sample ^ 0x80; _wave[i] = static_cast<int8_t>(sample); @@ -729,6 +735,31 @@ namespace xt } } + SysEx State::createWaveData(const WaveData& _wave, const uint16_t _waveIndex, const bool _preview) + { + const auto hh = static_cast<uint8_t>(_waveIndex >> 7); + const auto ll = static_cast<uint8_t>(_waveIndex & 0x7f); + + const std::initializer_list<uint8_t> header{0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(_preview ? SysexCommand::WaveDumpP : SysexCommand::WaveDump), hh, ll}; + SysEx sysex{header}; + sysex.reserve(sysex.size() + _wave.size()); + + for(uint32_t i=0; i<_wave.size()>>1; ++i) + { + const auto sample = _wave[i] ^ 0x80; + + sysex.push_back(static_cast<uint8_t>(sample >> 4)); + sysex.push_back(static_cast<uint8_t>(sample & 0xf)); + } + + sysex.push_back(0); + sysex.push_back(0xf7); + + updateChecksum(sysex, static_cast<uint32_t>(std::size(header))); + + return sysex; + } + void State::parseTableData(TableData& _table, const SysEx& _sysex) { constexpr uint32_t off = 7; @@ -746,6 +777,34 @@ namespace xt } } + SysEx State::createTableData(const TableData& _table, uint32_t _tableIndex, bool _preview) + { + const auto hh = static_cast<uint8_t>(_tableIndex >> 7); + const auto ll = static_cast<uint8_t>(_tableIndex & 0x7f); + + const std::initializer_list<uint8_t> header{0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(_preview ? SysexCommand::WaveCtlDumpP : SysexCommand::WaveCtlDump), hh, ll}; + SysEx sysex{header}; + sysex.reserve(sysex.size() + _table.size() * 4); + + for(uint32_t i=0; i<_table.size(); ++i) + { + const auto i4 = i<<2; + + const auto waveIndex = _table[i]; + sysex.push_back((waveIndex >> 12) & 0xf); + sysex.push_back((waveIndex >> 8) & 0xf); + sysex.push_back((waveIndex >> 4) & 0xf); + sysex.push_back((waveIndex ) & 0xf); + } + + sysex.push_back(0); + sysex.push_back(0xf7); + + updateChecksum(sysex, static_cast<uint32_t>(std::size(header))); + + return sysex; + } + void State::onPlayModeChanged() { // if the play mode is changed, force a re-request of the edit buffer for the first single again, because on the device, that edit buffer is shared between multi & single diff --git a/source/xtLib/xtState.h b/source/xtLib/xtState.h @@ -20,6 +20,7 @@ namespace synthLib namespace xt { + class WavePreview; class Xt; using SysEx = wLib::SysEx; @@ -70,7 +71,7 @@ namespace xt using Global = std::array<uint8_t, Dumps[static_cast<uint32_t>(DumpType::Global)].dumpSize>; using Mode = std::array<uint8_t, Dumps[static_cast<uint32_t>(DumpType::Mode)].dumpSize>; - State(Xt& _xt); + State(Xt& _xt, WavePreview& _wavePreview); bool loadState(const SysEx& _sysex); @@ -84,7 +85,10 @@ namespace xt static void createSequencerMultiData(std::vector<uint8_t>& _data); static void parseWaveData(WaveData& _wave, const SysEx& _sysex); + static SysEx createWaveData(const WaveData& _wave, uint16_t _waveIndex, bool _preview); + static void parseTableData(TableData& _table, const SysEx& _sysex); + static SysEx createTableData(const TableData& _table, uint32_t _tableIndex, bool _preview); private: @@ -202,6 +206,9 @@ namespace xt Origin m_sender = Origin::External; bool m_isEditBuffer = false; + // Emulator specific: preview wave editing + WavePreview& m_wavePreview; + synthLib::SMidiEvent m_lastBankSelectMSB; synthLib::SMidiEvent m_lastBankSelectLSB; }; diff --git a/source/xtLib/xtWavePreview.cpp b/source/xtLib/xtWavePreview.cpp @@ -5,9 +5,79 @@ namespace xt { + static constexpr uint32_t g_waveMemBase = 0x20000; + static constexpr uint32_t g_waveMemWaveSize = 256; // 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 + 1 + static constexpr uint32_t g_waveMemWavesPerPart = 64; + static constexpr uint32_t g_waveMemPartBufferSize = g_waveMemWaveSize * g_waveMemWavesPerPart; + WavePreview::WavePreview(Xt& _xt) : m_xt(_xt) , m_dspMem(m_xt.getHardware()->getDSP(0).dsp().memory()) { } + + bool WavePreview::receiveWave(const SysEx& _data) + { + const auto part = _data[SysexIndex::IdxWaveIndexL]; + const auto waveIndex = _data[SysexIndex::IdxWaveIndexH]; + + if(part >= m_partDatas.size()) + return false; + + auto& partData = m_partDatas[part]; + + if(waveIndex >= partData.waves.size()) + return false; + + State::parseWaveData(partData.waves[waveIndex], _data); + + for(uint8_t i=0; i<64; ++i) + sendToDSP(partData.waves[waveIndex], 0, i); + + return true; + } + + bool WavePreview::receiveWaveControlTable(const SysEx& _data) + { + return true; + } + + bool WavePreview::receiveWavePreviewMode(const SysEx& _data) + { + return true; + } + + void WavePreview::sendToDSP(const WaveData& _data, const uint8_t _part, const uint8_t _wave) + { + const auto memBase = g_waveMemBase + _part * g_waveMemPartBufferSize + _wave * g_waveMemWaveSize; + + std::array<int8_t, g_waveMemWaveSize> waveData; + + waveData.back() = 0; + + std::copy(_data.begin(), _data.end(), waveData.begin()); + + // super simple repeating factor-two reduction by averaging two samples. No idea how the hardware does it + uint32_t size = static_cast<uint32_t>(_data.size()) >> 1; + uint32_t readOffset = 0; + uint32_t writeOffset = size<<1; + while(size) + { + for(uint32_t i=0; i<size; ++i) + { + const auto s = (waveData[readOffset+i] + waveData[readOffset+i+1]) >> 1; + waveData[writeOffset] = static_cast<int8_t>(s); + } + readOffset += size<<1; + writeOffset += size; + size >>= 1; + } + + waveData[waveData.size()-1] = waveData[waveData.size()-2] = 0; + + for(uint32_t i=0; i<waveData.size(); ++i) + { + m_dspMem.set(dsp56k::MemArea_Y, memBase + i, static_cast<int32_t>(waveData[i]) << 16); + } + } } diff --git a/source/xtLib/xtWavePreview.h b/source/xtLib/xtWavePreview.h @@ -1,5 +1,7 @@ #pragma once +#include "xtState.h" + namespace dsp56k { class Memory; @@ -12,10 +14,23 @@ namespace xt class WavePreview { public: + struct PartData + { + std::array<WaveData, 64> waves{}; + std::array<uint16_t, 64> control{}; + }; + WavePreview(Xt& _xt); + bool receiveWave(const SysEx& _data); + bool receiveWaveControlTable(const SysEx& _data); + bool receiveWavePreviewMode(const SysEx& _data); + private: + void sendToDSP(const WaveData& _data, uint8_t _part, uint8_t _wave); + Xt& m_xt; dsp56k::Memory& m_dspMem; + std::array<PartData, 8> m_partDatas; }; }