mirror of
https://codeberg.org/armin/trommelkiste.git
synced 2026-09-01 04:10:46 +02:00
Add user preset save/load to the preset menu
This commit is contained in:
parent
ff96ab1a69
commit
143e0bac76
4 changed files with 212 additions and 28 deletions
|
|
@ -290,7 +290,7 @@ TrommelkisteEditor::TrommelkisteEditor(TrommelkisteProcessor& p)
|
||||||
int id = presetCombo.getSelectedId();
|
int id = presetCombo.getSelectedId();
|
||||||
if (id >= 1)
|
if (id >= 1)
|
||||||
{
|
{
|
||||||
proc.loadKit(id - 1);
|
proc.loadPreset(id - 1);
|
||||||
for (int i = 0; i < NUM_TRACKS; ++i)
|
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||||
updateFileLabel(i);
|
updateFileLabel(i);
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +298,7 @@ TrommelkisteEditor::TrommelkisteEditor(TrommelkisteProcessor& p)
|
||||||
addAndMakeVisible(presetCombo);
|
addAndMakeVisible(presetCombo);
|
||||||
|
|
||||||
presetPrevBtn.onClick = [this] {
|
presetPrevBtn.onClick = [this] {
|
||||||
proc.prevKit();
|
proc.prevPreset();
|
||||||
refreshPresetCombo();
|
refreshPresetCombo();
|
||||||
for (int i = 0; i < NUM_TRACKS; ++i)
|
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||||
updateFileLabel(i);
|
updateFileLabel(i);
|
||||||
|
|
@ -306,13 +306,36 @@ TrommelkisteEditor::TrommelkisteEditor(TrommelkisteProcessor& p)
|
||||||
addAndMakeVisible(presetPrevBtn);
|
addAndMakeVisible(presetPrevBtn);
|
||||||
|
|
||||||
presetNextBtn.onClick = [this] {
|
presetNextBtn.onClick = [this] {
|
||||||
proc.nextKit();
|
proc.nextPreset();
|
||||||
refreshPresetCombo();
|
refreshPresetCombo();
|
||||||
for (int i = 0; i < NUM_TRACKS; ++i)
|
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||||
updateFileLabel(i);
|
updateFileLabel(i);
|
||||||
};
|
};
|
||||||
addAndMakeVisible(presetNextBtn);
|
addAndMakeVisible(presetNextBtn);
|
||||||
|
|
||||||
|
presetSaveBtn.onClick = [this]
|
||||||
|
{
|
||||||
|
auto* aw = new juce::AlertWindow("Save Preset", "Enter a name for this preset:",
|
||||||
|
juce::MessageBoxIconType::NoIcon);
|
||||||
|
aw->addTextEditor("name", "", "Preset name");
|
||||||
|
aw->addButton("Save", 1, juce::KeyPress(juce::KeyPress::returnKey));
|
||||||
|
aw->addButton("Cancel", 0, juce::KeyPress(juce::KeyPress::escapeKey));
|
||||||
|
aw->enterModalState(true, juce::ModalCallbackFunction::create(
|
||||||
|
[this, aw](int result)
|
||||||
|
{
|
||||||
|
if (result == 1)
|
||||||
|
{
|
||||||
|
auto name = aw->getTextEditor("name")->getText().trim();
|
||||||
|
if (name.isNotEmpty())
|
||||||
|
{
|
||||||
|
proc.saveUserPreset(name);
|
||||||
|
refreshPresetCombo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), true);
|
||||||
|
};
|
||||||
|
addAndMakeVisible(presetSaveBtn);
|
||||||
|
|
||||||
scaleCombo.addItemList({"100%", "125%", "150%", "175%", "200%"}, 1);
|
scaleCombo.addItemList({"100%", "125%", "150%", "175%", "200%"}, 1);
|
||||||
scaleCombo.setSelectedId(3, dontSendNotification);
|
scaleCombo.setSelectedId(3, dontSendNotification);
|
||||||
scaleCombo.setColour(ComboBox::backgroundColourId, Colour(0xFF333333));
|
scaleCombo.setColour(ComboBox::backgroundColourId, Colour(0xFF333333));
|
||||||
|
|
@ -449,10 +472,10 @@ void TrommelkisteEditor::updateFileLabel(int idx)
|
||||||
void TrommelkisteEditor::refreshPresetCombo()
|
void TrommelkisteEditor::refreshPresetCombo()
|
||||||
{
|
{
|
||||||
presetCombo.clear(dontSendNotification);
|
presetCombo.clear(dontSendNotification);
|
||||||
const int n = proc.getNumKits();
|
const int n = proc.getNumPresets();
|
||||||
for (int i = 0; i < n; ++i)
|
for (int i = 0; i < n; ++i)
|
||||||
presetCombo.addItem(proc.getKitList()[i].name, i + 1);
|
presetCombo.addItem(proc.getPresetName(i), i + 1);
|
||||||
presetCombo.setSelectedId(proc.currentKitIndex + 1, dontSendNotification);
|
presetCombo.setSelectedId(proc.currentPresetIndex + 1, dontSendNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrommelkisteEditor::paint(Graphics& g)
|
void TrommelkisteEditor::paint(Graphics& g)
|
||||||
|
|
@ -606,7 +629,8 @@ void TrommelkisteEditor::resized()
|
||||||
presetPrevBtn.setBounds(scaled(150), scaled(14), scaled(20), scaled(20));
|
presetPrevBtn.setBounds(scaled(150), scaled(14), scaled(20), scaled(20));
|
||||||
presetCombo.setBounds(scaled(172), scaled(14), scaled(100), scaled(20));
|
presetCombo.setBounds(scaled(172), scaled(14), scaled(100), scaled(20));
|
||||||
presetNextBtn.setBounds(scaled(274), scaled(14), scaled(20), scaled(20));
|
presetNextBtn.setBounds(scaled(274), scaled(14), scaled(20), scaled(20));
|
||||||
scaleCombo.setBounds(scaled(298), scaled(14), scaled(52), scaled(20));
|
presetSaveBtn.setBounds(scaled(296), scaled(14), scaled(48), scaled(20));
|
||||||
|
scaleCombo.setBounds(scaled(348), scaled(14), scaled(52), scaled(20));
|
||||||
|
|
||||||
// Header knobs
|
// Header knobs
|
||||||
const int hk = HDR_KNOB;
|
const int hk = HDR_KNOB;
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,12 @@ public:
|
||||||
void mouseDown(const juce::MouseEvent&) override;
|
void mouseDown(const juce::MouseEvent&) override;
|
||||||
void timerCallback() override;
|
void timerCallback() override;
|
||||||
|
|
||||||
|
bool isInterestedInFileDrag(const juce::StringArray& files) override;
|
||||||
|
void fileDragEnter(const juce::StringArray& files, int x, int y) override;
|
||||||
|
void fileDragMove(const juce::StringArray& files, int x, int y) override;
|
||||||
|
void fileDragExit(const juce::StringArray& files) override;
|
||||||
|
void filesDropped(const juce::StringArray& files, int x, int y) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TrommelkisteProcessor& proc;
|
TrommelkisteProcessor& proc;
|
||||||
TrommelkisteLookAndFeel lnf;
|
TrommelkisteLookAndFeel lnf;
|
||||||
|
|
@ -78,6 +84,7 @@ private:
|
||||||
juce::ComboBox presetCombo;
|
juce::ComboBox presetCombo;
|
||||||
juce::TextButton presetPrevBtn{"<"};
|
juce::TextButton presetPrevBtn{"<"};
|
||||||
juce::TextButton presetNextBtn{">"};
|
juce::TextButton presetNextBtn{">"};
|
||||||
|
juce::TextButton presetSaveBtn{"SAVE"};
|
||||||
|
|
||||||
// Global effect controls in header
|
// Global effect controls in header
|
||||||
KnobSlider globalRingFreq, globalDestruction;
|
KnobSlider globalRingFreq, globalDestruction;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ TrommelkisteProcessor::TrommelkisteProcessor()
|
||||||
formatManager.registerBasicFormats();
|
formatManager.registerBasicFormats();
|
||||||
scanSamples();
|
scanSamples();
|
||||||
loadKit(0);
|
loadKit(0);
|
||||||
|
refreshUserPresets();
|
||||||
}
|
}
|
||||||
|
|
||||||
TrommelkisteProcessor::~TrommelkisteProcessor() {}
|
TrommelkisteProcessor::~TrommelkisteProcessor() {}
|
||||||
|
|
@ -605,19 +606,12 @@ static const TrommelkisteProcessor::Kit kits[] = {
|
||||||
|
|
||||||
const TrommelkisteProcessor::Kit* TrommelkisteProcessor::getKitList() { return kits; }
|
const TrommelkisteProcessor::Kit* TrommelkisteProcessor::getKitList() { return kits; }
|
||||||
int TrommelkisteProcessor::getNumKits() const { return 4; }
|
int TrommelkisteProcessor::getNumKits() const { return 4; }
|
||||||
juce::String TrommelkisteProcessor::getCurrentKitName() const
|
|
||||||
{
|
|
||||||
if (currentKitIndex >= 0 && currentKitIndex < getNumKits())
|
|
||||||
return kits[currentKitIndex].name;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrommelkisteProcessor::loadKit(int index)
|
void TrommelkisteProcessor::loadKit(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= getNumKits())
|
if (index < 0 || index >= getNumKits())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
currentKitIndex = index;
|
|
||||||
const auto& kit = kits[index];
|
const auto& kit = kits[index];
|
||||||
|
|
||||||
for (int t = 0; t < NUM_TRACKS; ++t)
|
for (int t = 0; t < NUM_TRACKS; ++t)
|
||||||
|
|
@ -647,18 +641,161 @@ void TrommelkisteProcessor::loadKit(int index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrommelkisteProcessor::nextKit()
|
juce::String TrommelkisteProcessor::getPresetName(int index) const
|
||||||
{
|
{
|
||||||
int idx = currentKitIndex + 1;
|
if (index < 0 || index >= getNumPresets())
|
||||||
if (idx >= getNumKits()) idx = 0;
|
return {};
|
||||||
loadKit(idx);
|
if (index < getNumKits())
|
||||||
|
return kits[index].name;
|
||||||
|
return userPresets[index - getNumKits()].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrommelkisteProcessor::prevKit()
|
void TrommelkisteProcessor::loadPreset(int index)
|
||||||
{
|
{
|
||||||
int idx = currentKitIndex - 1;
|
if (index < 0 || index >= getNumPresets())
|
||||||
if (idx < 0) idx = getNumKits() - 1;
|
return;
|
||||||
loadKit(idx);
|
|
||||||
|
currentPresetIndex = index;
|
||||||
|
if (index < getNumKits())
|
||||||
|
loadKit(index);
|
||||||
|
else
|
||||||
|
loadUserPreset(index - getNumKits());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrommelkisteProcessor::nextPreset()
|
||||||
|
{
|
||||||
|
const int n = getNumPresets();
|
||||||
|
if (n <= 0) return;
|
||||||
|
loadPreset((currentPresetIndex + 1) % n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrommelkisteProcessor::prevPreset()
|
||||||
|
{
|
||||||
|
const int n = getNumPresets();
|
||||||
|
if (n <= 0) return;
|
||||||
|
loadPreset((currentPresetIndex + n - 1) % n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── User presets ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
static juce::File getUserPresetDirectory()
|
||||||
|
{
|
||||||
|
return juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory)
|
||||||
|
.getChildFile("Trommelkiste").getChildFile("Presets");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrommelkisteProcessor::refreshUserPresets()
|
||||||
|
{
|
||||||
|
userPresets.clear();
|
||||||
|
|
||||||
|
auto dir = getUserPresetDirectory();
|
||||||
|
if (! dir.isDirectory())
|
||||||
|
return;
|
||||||
|
|
||||||
|
juce::Array<juce::File> files =
|
||||||
|
dir.findChildFiles(juce::File::findFiles, false, "*.trommelpreset");
|
||||||
|
files.sort();
|
||||||
|
|
||||||
|
for (const auto& f : files)
|
||||||
|
{
|
||||||
|
auto xml = juce::XmlDocument::parse(f);
|
||||||
|
if (xml == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
UserPreset p;
|
||||||
|
p.name = xml->getStringAttribute("name");
|
||||||
|
if (p.name.isEmpty())
|
||||||
|
p.name = f.getFileNameWithoutExtension();
|
||||||
|
p.file = f;
|
||||||
|
userPresets.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrommelkisteProcessor::saveUserPreset(const juce::String& name)
|
||||||
|
{
|
||||||
|
auto dir = getUserPresetDirectory();
|
||||||
|
dir.createDirectory();
|
||||||
|
|
||||||
|
juce::String safeName = name;
|
||||||
|
safeName = safeName.replaceCharacter('\\', '_').replaceCharacter('/', '_');
|
||||||
|
auto file = dir.getChildFile(safeName + ".trommelpreset");
|
||||||
|
|
||||||
|
auto state = apvts.copyState();
|
||||||
|
state.setProperty("name", name, nullptr);
|
||||||
|
|
||||||
|
juce::ValueTree samples("Samples");
|
||||||
|
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||||
|
{
|
||||||
|
juce::ValueTree tr("Track" + juce::String(i));
|
||||||
|
tr.setProperty("path", tracks[i].filePath, nullptr);
|
||||||
|
tr.setProperty("sampleIndex", currentSampleIndex[i], nullptr);
|
||||||
|
if (currentSampleIndex[i] >= 0 && currentSampleIndex[i] < trackSamples[i].size())
|
||||||
|
tr.setProperty("sampleName", trackSamples[i][currentSampleIndex[i]].name, nullptr);
|
||||||
|
samples.appendChild(tr, nullptr);
|
||||||
|
}
|
||||||
|
state.appendChild(samples, nullptr);
|
||||||
|
|
||||||
|
std::unique_ptr<juce::XmlElement> xml(state.createXml());
|
||||||
|
if (! xml->writeToFile(file, {}))
|
||||||
|
return;
|
||||||
|
|
||||||
|
refreshUserPresets();
|
||||||
|
|
||||||
|
for (int i = 0; i < userPresets.size(); ++i)
|
||||||
|
{
|
||||||
|
if (userPresets[i].file == file)
|
||||||
|
{
|
||||||
|
currentPresetIndex = getNumKits() + i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrommelkisteProcessor::loadUserPreset(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= userPresets.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::unique_ptr<juce::XmlElement> xml = juce::XmlDocument::parse(userPresets[index].file);
|
||||||
|
if (xml == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto state = juce::ValueTree::fromXml(*xml);
|
||||||
|
apvts.replaceState(state);
|
||||||
|
|
||||||
|
auto samples = state.getChildWithName("Samples");
|
||||||
|
if (samples.isValid())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||||
|
{
|
||||||
|
auto tr = samples.getChildWithName("Track" + juce::String(i));
|
||||||
|
if (! tr.isValid())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto path = tr.getProperty("path", "").toString();
|
||||||
|
auto sampleName = tr.getProperty("sampleName", "").toString();
|
||||||
|
|
||||||
|
if (path.isNotEmpty())
|
||||||
|
{
|
||||||
|
loadSample(i, juce::File(path));
|
||||||
|
currentSampleIndex[i] = (int)tr.getProperty("sampleIndex", -1);
|
||||||
|
}
|
||||||
|
else if (sampleName.isNotEmpty())
|
||||||
|
{
|
||||||
|
int found = -1;
|
||||||
|
for (int s = 0; s < trackSamples[i].size(); ++s)
|
||||||
|
{
|
||||||
|
if (trackSamples[i][s].name.equalsIgnoreCase(sampleName))
|
||||||
|
{
|
||||||
|
found = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found >= 0)
|
||||||
|
loadSampleByIndex(i, found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
juce::AudioProcessorEditor* TrommelkisteProcessor::createEditor()
|
juce::AudioProcessorEditor* TrommelkisteProcessor::createEditor()
|
||||||
|
|
@ -670,7 +807,7 @@ void TrommelkisteProcessor::getStateInformation(juce::MemoryBlock& destData)
|
||||||
{
|
{
|
||||||
auto state = apvts.copyState();
|
auto state = apvts.copyState();
|
||||||
|
|
||||||
state.setProperty("kitIndex", currentKitIndex, nullptr);
|
state.setProperty("presetIndex", currentPresetIndex, nullptr);
|
||||||
|
|
||||||
juce::ValueTree samples("Samples");
|
juce::ValueTree samples("Samples");
|
||||||
for (int i = 0; i < NUM_TRACKS; ++i)
|
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||||
|
|
@ -697,7 +834,8 @@ void TrommelkisteProcessor::setStateInformation(const void* data, int sizeInByte
|
||||||
auto state = juce::ValueTree::fromXml(*xml);
|
auto state = juce::ValueTree::fromXml(*xml);
|
||||||
apvts.replaceState(state);
|
apvts.replaceState(state);
|
||||||
|
|
||||||
currentKitIndex = (int)state.getProperty("kitIndex", 0);
|
currentPresetIndex = (int)state.getProperty("presetIndex",
|
||||||
|
state.getProperty("kitIndex", 0));
|
||||||
|
|
||||||
auto samples = state.getChildWithName("Samples");
|
auto samples = state.getChildWithName("Samples");
|
||||||
if (samples.isValid())
|
if (samples.isValid())
|
||||||
|
|
|
||||||
|
|
@ -78,14 +78,29 @@ public:
|
||||||
const char* samples[NUM_TRACKS];
|
const char* samples[NUM_TRACKS];
|
||||||
};
|
};
|
||||||
|
|
||||||
int currentKitIndex = 0;
|
int currentPresetIndex = 0;
|
||||||
void loadKit(int index);
|
void loadKit(int index);
|
||||||
void nextKit();
|
|
||||||
void prevKit();
|
|
||||||
int getNumKits() const;
|
int getNumKits() const;
|
||||||
juce::String getCurrentKitName() const;
|
|
||||||
static const Kit* getKitList();
|
static const Kit* getKitList();
|
||||||
|
|
||||||
|
// User presets (saved to disk, shown in the preset menu)
|
||||||
|
struct UserPreset
|
||||||
|
{
|
||||||
|
juce::String name;
|
||||||
|
juce::File file;
|
||||||
|
};
|
||||||
|
|
||||||
|
juce::Array<UserPreset> userPresets;
|
||||||
|
void saveUserPreset(const juce::String& name);
|
||||||
|
void loadUserPreset(int index);
|
||||||
|
void refreshUserPresets();
|
||||||
|
|
||||||
|
int getNumPresets() const { return getNumKits() + userPresets.size(); }
|
||||||
|
juce::String getPresetName(int index) const;
|
||||||
|
void loadPreset(int index);
|
||||||
|
void nextPreset();
|
||||||
|
void prevPreset();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Voice
|
struct Voice
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue