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 f92083526a22f59b30f6feaf5a7b33ca43e9497d
parent c01b051150f9ff19ae4d28edeba333c4b67d8281
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sat,  8 Mar 2025 04:16:18 +0100

add export ability for single waves or all user waves

Diffstat:
Msource/xtJucePlugin/weWaveCategoryTreeItem.cpp | 52++++++++++++++++++++++++++++++++++++++++++++++++----
Msource/xtJucePlugin/weWaveCategoryTreeItem.h | 10++++++++--
Msource/xtJucePlugin/weWaveTreeItem.cpp | 39+++++++++++++++++++++++++++++++++++++++
Msource/xtJucePlugin/xtWaveEditor.cpp | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msource/xtJucePlugin/xtWaveEditor.h | 6++++++
5 files changed, 208 insertions(+), 28 deletions(-)

diff --git a/source/xtJucePlugin/weWaveCategoryTreeItem.cpp b/source/xtJucePlugin/weWaveCategoryTreeItem.cpp @@ -1,6 +1,8 @@ #include "weWaveCategoryTreeItem.h" #include "weWaveTreeItem.h" +#include "xtWaveEditor.h" + #include "xtLib/xtMidiTypes.h" namespace xtJucePlugin @@ -55,16 +57,58 @@ namespace xtJucePlugin return g_categoryNames[static_cast<uint32_t>(_category)]; } - void WaveCategoryTreeItem::addItems(uint32_t _first, uint32_t _count) + juce::var WaveCategoryTreeItem::getDragSourceDescription() { - for(uint32_t i=0; i<_count; ++i) - addItem(i + _first); + return TreeItem::getDragSourceDescription(); + } + + void WaveCategoryTreeItem::itemClicked(const juce::MouseEvent& _mouseEvent) + { + if (_mouseEvent.mods.isPopupMenu()) + { + if (m_category != WaveCategory::Rom) + { + juce::PopupMenu menu; + menu.addItem("Export all as .syx", [this] + { + exportAll(false); + }); + menu.addItem("Export all as .mid", [this] + { + exportAll(true); + }); + menu.showMenuAsync({}); + return; + } + } + TreeItem::itemClicked(_mouseEvent); + } + + void WaveCategoryTreeItem::addItems(const uint16_t _first, const uint16_t _count) + { + for(uint16_t i=_first; i<_first+_count; ++i) + addItem(i); setOpen(true); } - void WaveCategoryTreeItem::addItem(const uint32_t _index) + void WaveCategoryTreeItem::addItem(const uint16_t _index) { addSubItem(new WaveTreeItem(m_editor, m_category, xt::WaveId(_index))); } + + void WaveCategoryTreeItem::exportAll(const bool _midi) const + { + std::vector<xt::WaveId> waveIds; + + waveIds.reserve(getNumSubItems()); + + for (int i = 0; i < getNumSubItems(); ++i) + { + if (auto* subItem = dynamic_cast<WaveTreeItem*>(getSubItem(i))) + waveIds.push_back(subItem->getWaveId()); + } + + m_editor.exportAsSyxOrMid(waveIds, _midi); + } } diff --git a/source/xtJucePlugin/weWaveCategoryTreeItem.h b/source/xtJucePlugin/weWaveCategoryTreeItem.h @@ -16,9 +16,15 @@ namespace xtJucePlugin static std::string getCategoryName(WaveCategory _category); + juce::var getDragSourceDescription() override; + + void itemClicked(const juce::MouseEvent&) override; + private: - void addItems(uint32_t _first, uint32_t _count); - void addItem(uint32_t _index); + void addItems(uint16_t _first, uint16_t _count); + void addItem(uint16_t _index); + + void exportAll(bool _midi) const; WaveEditor& m_editor; const WaveCategory m_category; diff --git a/source/xtJucePlugin/weWaveTreeItem.cpp b/source/xtJucePlugin/weWaveTreeItem.cpp @@ -202,6 +202,45 @@ namespace xtJucePlugin } }); menu.addSubMenu("Copy to User Wave...", subMenuUW); + menu.addSeparator(); + + if (auto wave = m_editor.getData().getWave(m_waveIndex)) + { + auto w = *wave; + + juce::PopupMenu exportMenu; + + if (xt::wave::isReadOnly(m_waveIndex)) + { + // we cannot export a .mid or .syx that is a rom location as the hardware cannot import it + auto subMenuSyx = WaveEditor::createRamWavesPopupMenu([this, w](const xt::WaveId _id) + { + m_editor.exportAsSyx(_id, w); + }); + exportMenu.addSubMenu(".syx", subMenuSyx); + auto subMenuMid = WaveEditor::createRamWavesPopupMenu([this, w](const xt::WaveId _id) + { + m_editor.exportAsMid(_id, w); + }); + exportMenu.addSubMenu(".mid", subMenuMid); + } + else + { + exportMenu.addItem(".syx", [this, w] + { + m_editor.exportAsSyx(m_waveIndex, w); + }); + exportMenu.addItem(".mid", [this, w] + { + m_editor.exportAsMid(m_waveIndex, w); + }); + } + exportMenu.addItem(".wav", [this, w] + { + m_editor.exportAsWav(w); + }); + menu.addSubMenu("Export as...", exportMenu); + } menu.showMenuAsync({}); } diff --git a/source/xtJucePlugin/xtWaveEditor.cpp b/source/xtJucePlugin/xtWaveEditor.cpp @@ -19,6 +19,7 @@ #include "juceUiLib/messageBox.h" +#include "synthLib/sysexToMidi.h" #include "synthLib/wavReader.h" #include "synthLib/wavWriter.h" @@ -390,19 +391,119 @@ namespace xtJucePlugin menu.showMenuAsync({}); } + void WaveEditor::exportAsSyx(const xt::WaveId& _id, const xt::WaveData& _data) + { + selectExportFileName("Save Wave as .syx", ".syx", [this, _id, _data](const std::string& _filename) + { + exportAsSyxOrMid(_filename, _id, _data, false); + }); + } + + void WaveEditor::exportAsMid(const xt::WaveId& _id, const xt::WaveData& _data) + { + selectExportFileName("Save Wave as .mid", ".mid", [this, _id, _data](const std::string& _filename) + { + exportAsSyxOrMid(_filename, _id, _data, true); + }); + } + + void WaveEditor::exportAsSyxOrMid(const std::string& _filename, const xt::WaveId& _id, const xt::WaveData& _data, bool _midi) const + { + auto sysex = xt::State::createWaveData(_data, _id.rawId(), false); + + bool success; + if (_midi) + success = synthLib::SysexToMidi::write(_filename.c_str(), {sysex}); + else + success = baseLib::filesystem::writeFile(_filename, sysex); + + if (!success) + { + const auto productName = getEditor().getProcessor().getProperties().name; + genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, productName + " - Error", "Failed to create file\n" + _filename + ".\n\nMake sure that the file is not write protected or opened in another application"); + } + } + + void WaveEditor::exportAsSyxOrMid(const std::vector<xt::WaveId>& _ids, bool _midi) + { + selectExportFileName(_midi ? "Save Waves as .mid" : "Save Waves as .syx", _midi ? ".mid" : ".syx", [this, _ids, _midi](const std::string& _filename) + { + std::vector<std::vector<uint8_t>> sysex; + + sysex.reserve(_ids.size()); + + for (const auto& id : _ids) + { + auto wave = m_data.getWave(id); + if (!wave) + { + const auto productName = getEditor().getProcessor().getProperties().name; + genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, productName + " - Error", "Failed to export wave " + WaveTreeItem::getWaveName(id) + ".\n\nThe wave is not available."); + continue; + } + + sysex.push_back(xt::State::createWaveData(*wave, id.rawId(), false)); + } + + bool success; + + if (_midi) + { + success = synthLib::SysexToMidi::write(_filename.c_str(), sysex); + } + else + { + std::vector<uint8_t> data; + for (const auto& s : sysex) + data.insert(data.end(), s.begin(), s.end()); + success = baseLib::filesystem::writeFile(_filename, data); + } + + if (!success) + { + const auto productName = getEditor().getProcessor().getProperties().name; + genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, productName + " - Error", "Failed to create file\n" + _filename + ".\n\nMake sure that the file is not write protected or opened in another application"); + } + }); + } + void WaveEditor::exportAsWav(const xt::WaveData& _data) { + selectExportFileName("Save Wave as .wav", ".wav", [this, _data](const std::string& _filename) + { + exportAsWav(_filename, _data); + }); + } + + void WaveEditor::exportAsWav(const std::string& _filename, const xt::WaveData& _data) const + { + synthLib::WavWriter w; + + // 8 bit waves are unsigned + std::array<uint8_t, std::tuple_size_v<xt::WaveData>> data; + for (size_t i=0; i<_data.size(); ++i) + data[i] = static_cast<uint8_t>(_data[i] + 128); + + if (!w.write(_filename, 8, false, 1, 32000, data.data(), sizeof(_data))) + { + const auto productName = getEditor().getProcessor().getProperties().name; + genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, productName + " - Error", "Failed to create file\n" + _filename + ".\n\nMake sure that the file is not write protected or opened in another application"); + } + } + + void WaveEditor::selectExportFileName(const std::string& _title, const std::string& _extension, const std::function<void(const std::string&)>& _callback) + { const auto& config = m_editor.getProcessor().getConfig(); - constexpr const char* configKey = "xt_wav_save_path"; + constexpr const char* configKey = "xt_export_path"; const auto path = config.getValue(configKey, {}); - m_fileChooser = std::make_unique<juce::FileChooser>("Save Wave as .wav", path, "*.wav", true); + m_fileChooser = std::make_unique<juce::FileChooser>(_title, path, '*' + _extension, true); constexpr auto flags = juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::FileChooserFlags::canSelectFiles; - auto onFileChosen = [this, _data](const juce::FileChooser& _chooser) + auto onFileChosen = [this, _callback](const juce::FileChooser& _chooser) { if (_chooser.getResults().isEmpty()) return; @@ -412,16 +513,16 @@ namespace xtJucePlugin if (!result.existsAsFile()) { - exportAsWav(result.getFullPathName().toStdString(), _data); + _callback(result.getFullPathName().toStdString()); } else { genericUI::MessageBox::showYesNo(juce::MessageBoxIconType::WarningIcon, "File exists", "Do you want to overwrite the existing file?", - [this, _data, result](const genericUI::MessageBox::Result _result) + [this, _callback, result](const genericUI::MessageBox::Result _result) { if (_result == genericUI::MessageBox::Result::Yes) { - exportAsWav(result.getFullPathName().toStdString(), _data); + _callback(result.getFullPathName().toStdString()); } }); } @@ -429,22 +530,6 @@ namespace xtJucePlugin m_fileChooser->launchAsync(flags, onFileChosen); } - void WaveEditor::exportAsWav(const std::string& _filename, const xt::WaveData& _data) const - { - synthLib::WavWriter w; - - // 8 bit waves are unsigned - std::array<uint8_t, std::tuple_size_v<xt::WaveData>> data; - for (size_t i=0; i<_data.size(); ++i) - data[i] = static_cast<uint8_t>(_data[i] + 128); - - if (!w.write(_filename, 8, false, 1, 32000, data.data(), sizeof(_data))) - { - const auto productName = getEditor().getProcessor().getProperties().name; - genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, productName + " - Error", "Failed to create file\n" + _filename + ".\n\nMake sure that the file is not write protected or opened in another application"); - } - } - std::optional<xt::WaveData> WaveEditor::importWaveFile(const std::string& _filename) const { const auto productName = getEditor().getProcessor().getProperties().name; diff --git a/source/xtJucePlugin/xtWaveEditor.h b/source/xtJucePlugin/xtWaveEditor.h @@ -61,9 +61,15 @@ namespace xtJucePlugin void openGraphPopupMenu(const Graph& _graph, const juce::MouseEvent& _event); + void exportAsSyx(const xt::WaveId& _id, const xt::WaveData& _data); + void exportAsMid(const xt::WaveId& _id, const xt::WaveData& _data); + void exportAsSyxOrMid(const std::string& _filename, const xt::WaveId& _id, const xt::WaveData& _data, bool _midi) const; + void exportAsSyxOrMid(const std::vector<xt::WaveId>& _ids, bool _midi); void exportAsWav(const xt::WaveData& _data); void exportAsWav(const std::string& _filename, const xt::WaveData& _data) const; + void selectExportFileName(const std::string& _title, const std::string& _extension, const std::function<void(const std::string&)>&); + std::optional<xt::WaveData> importWaveFile(const std::string& _filename) const; private: