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