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 775ca9ec283073bbe311f0d86c87e953b8d887af
parent 16f6de680dde96d2df9f02a7364a984922b5aba7
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sat,  8 Mar 2025 03:19:06 +0100

add wav file import

Diffstat:
Msource/xtJucePlugin/weGraph.cpp | 25+++++++++++++++++++++++++
Msource/xtJucePlugin/weGraph.h | 8+++++++-
Msource/xtJucePlugin/weWaveTreeItem.cpp | 21++++++++++++++++++++-
Msource/xtJucePlugin/xtWaveEditor.cpp | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/xtJucePlugin/xtWaveEditor.h | 4++++
5 files changed, 134 insertions(+), 2 deletions(-)

diff --git a/source/xtJucePlugin/weGraph.cpp b/source/xtJucePlugin/weGraph.cpp @@ -1,6 +1,7 @@ #include "weGraph.h" #include "xtWaveEditor.h" + #include "dsp56kEmu/fastmath.h" namespace xtJucePlugin @@ -172,6 +173,30 @@ namespace xtJucePlugin return m_lastMouseEvent.get(); } + bool Graph::isInterestedInDragSource(const SourceDetails& _dragSourceDetails) + { + return false; + } + + void Graph::itemDropped(const SourceDetails& _dragSourceDetails) + { + } + + bool Graph::isInterestedInFileDrag(const juce::StringArray& _files) + { + if (_files.size() != 1) + return false; + return _files[0].endsWithIgnoreCase(".wav"); + } + + void Graph::filesDropped(const juce::StringArray& _files, int _x, int _y) + { + if (_files.size() != 1) + return; + if (auto res = m_editor.importWaveFile(_files[0].toStdString())) + m_editor.getGraphData().set(*res); + } + void Graph::onSourceChanged() { repaint(); diff --git a/source/xtJucePlugin/weGraph.h b/source/xtJucePlugin/weGraph.h @@ -10,7 +10,7 @@ namespace xtJucePlugin class GraphData; class WaveEditor; - class Graph : public juce::Component + class Graph : public juce::Component, public juce::DragAndDropTarget, public juce::FileDragAndDropTarget { public: static constexpr uint32_t InvalidIndex = ~0; @@ -57,6 +57,12 @@ namespace xtJucePlugin m_updateHoveredPositionWhileDragging = _enable; } + bool isInterestedInDragSource(const SourceDetails& _dragSourceDetails) override; + void itemDropped(const SourceDetails& _dragSourceDetails) override; + + bool isInterestedInFileDrag(const juce::StringArray& _files) override; + void filesDropped(const juce::StringArray& _files, int _x, int _y) override; + protected: virtual void onSourceChanged(); virtual void onHoveredIndexChanged(uint32_t _index); diff --git a/source/xtJucePlugin/weWaveTreeItem.cpp b/source/xtJucePlugin/weWaveTreeItem.cpp @@ -114,7 +114,12 @@ namespace xtJucePlugin if(xt::wave::isReadOnly(m_waveIndex)) return false; - if(files.size() == 1 && files[0].endsWithIgnoreCase(".mid") || files[0].endsWithIgnoreCase(".syx")) + if (files.size() != 1) + return false; + + const auto& f = files[0]; + + if(f.endsWithIgnoreCase(".mid") || f.endsWithIgnoreCase(".syx") || f.endsWithIgnoreCase(".wav")) return true; return TreeItem::isInterestedInFileDrag(files); @@ -125,6 +130,19 @@ namespace xtJucePlugin if(xt::wave::isReadOnly(m_waveIndex)) return; + if (files.isEmpty()) + return; + + if (files[0].endsWithIgnoreCase(".wav")) + { + if (auto wave = m_editor.importWaveFile(files[0].toStdString())) + { + m_editor.getData().setWave(m_waveIndex, *wave); + m_editor.getData().sendWaveToDevice(m_waveIndex); + } + return; + } + const auto errorTitle = m_editor.getEditor().getProcessor().getProperties().name + " - Error"; std::map<xt::WaveId, xt::WaveData> waves; @@ -135,6 +153,7 @@ namespace xtJucePlugin { if (tables.size() == 1) genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, errorTitle, "This file doesn't contain a Wave but a Control Table, please drop on a User Table slot."); + return; } diff --git a/source/xtJucePlugin/xtWaveEditor.cpp b/source/xtJucePlugin/xtWaveEditor.cpp @@ -19,6 +19,7 @@ #include "juceUiLib/messageBox.h" +#include "synthLib/wavReader.h" #include "synthLib/wavWriter.h" #include "xtLib/xtState.h" @@ -443,4 +444,81 @@ namespace xtJucePlugin 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; + + std::vector<uint8_t> fileData; + + if (!baseLib::filesystem::readFile(fileData, _filename)) + { + genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, productName + " - Error", "Failed to open file\n" + _filename + ".\n\nMake sure that the file exists and " + productName + " has read permissions."); + return {}; + } + + constexpr const char* formatDesc = "\n\nMake sure it is an 8 bit mono PCM file with a size of 64 (half-cycle wave) or 128 (full-cycle wave)"; + + synthLib::Data wavData; + if (!synthLib::WavReader::load(wavData, nullptr, fileData.data(), fileData.size())) + { + genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, productName + " - Error", "Failed to parse data from file\n" + _filename + "." + formatDesc); + return {}; + } + + std::vector<int8_t> data; + + if (wavData.isFloat || wavData.bitsPerSample != 8 || wavData.channels != 1) + { + genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, productName + " - Error", "Unsupported wav format in file\n" + _filename + "." + formatDesc); + return {}; + } + + data.resize(wavData.dataByteSize); + for (size_t i=0; i<wavData.dataByteSize; ++i) + { + data[i] = static_cast<int8_t>(static_cast<const uint8_t*>(wavData.data)[i] - 128); + } + + if (data.size() == 64) + { + xt::WaveData d; + for (size_t i = 0; i < 64; ++i) + { + d[i] = data[i]; + d[127 - i] = static_cast<int8_t>(dsp56k::clamp(-data[i], -128, 127)); + } + return d; + } + + if (data.size() == 128) + { + xt::WaveData d; + for (size_t i = 0; i < 128; ++i) + d[i] = data[i]; + + bool valid = true; + + for (size_t i=0; i<64; ++i) + { + if (d[i] != -d[127 - i]) + { + valid = false; + break; + } + } + + if (valid) + return d; + + genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, productName + " - Error", "Unsupported file\n" + _filename + ".\n\n" + + "An imported file with a size of 128 samples needs to be a full-cycle wave, i.e. the second half of the file needs to be the first half reversed and inverted.\n" + + "Correct the file or import a file of length 64 only (half-cycle wave). In the latter case, building a full cycle wave is done during import." + + formatDesc); + return {}; + } + + genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, productName + " - Error", "Unsupported size of file\n" + _filename + "." + formatDesc); + return {}; + } } diff --git a/source/xtJucePlugin/xtWaveEditor.h b/source/xtJucePlugin/xtWaveEditor.h @@ -64,6 +64,8 @@ namespace xtJucePlugin void exportAsWav(const xt::WaveData& _data); void exportAsWav(const std::string& _filename, const xt::WaveData& _data) const; + std::optional<xt::WaveData> importWaveFile(const std::string& _filename) const; + private: // ComponentMovementWatcher void componentVisibilityChanged() override { checkFirstTimeVisible(); } @@ -99,5 +101,7 @@ namespace xtJucePlugin xt::WaveId m_selectedWave; WaveEditorStyle m_style; + + std::unique_ptr<juce::FileChooser> m_fileChooser; }; }