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();
|
||||
if (id >= 1)
|
||||
{
|
||||
proc.loadKit(id - 1);
|
||||
proc.loadPreset(id - 1);
|
||||
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||
updateFileLabel(i);
|
||||
}
|
||||
|
|
@ -298,7 +298,7 @@ TrommelkisteEditor::TrommelkisteEditor(TrommelkisteProcessor& p)
|
|||
addAndMakeVisible(presetCombo);
|
||||
|
||||
presetPrevBtn.onClick = [this] {
|
||||
proc.prevKit();
|
||||
proc.prevPreset();
|
||||
refreshPresetCombo();
|
||||
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||
updateFileLabel(i);
|
||||
|
|
@ -306,13 +306,36 @@ TrommelkisteEditor::TrommelkisteEditor(TrommelkisteProcessor& p)
|
|||
addAndMakeVisible(presetPrevBtn);
|
||||
|
||||
presetNextBtn.onClick = [this] {
|
||||
proc.nextKit();
|
||||
proc.nextPreset();
|
||||
refreshPresetCombo();
|
||||
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||
updateFileLabel(i);
|
||||
};
|
||||
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.setSelectedId(3, dontSendNotification);
|
||||
scaleCombo.setColour(ComboBox::backgroundColourId, Colour(0xFF333333));
|
||||
|
|
@ -449,10 +472,10 @@ void TrommelkisteEditor::updateFileLabel(int idx)
|
|||
void TrommelkisteEditor::refreshPresetCombo()
|
||||
{
|
||||
presetCombo.clear(dontSendNotification);
|
||||
const int n = proc.getNumKits();
|
||||
const int n = proc.getNumPresets();
|
||||
for (int i = 0; i < n; ++i)
|
||||
presetCombo.addItem(proc.getKitList()[i].name, i + 1);
|
||||
presetCombo.setSelectedId(proc.currentKitIndex + 1, dontSendNotification);
|
||||
presetCombo.addItem(proc.getPresetName(i), i + 1);
|
||||
presetCombo.setSelectedId(proc.currentPresetIndex + 1, dontSendNotification);
|
||||
}
|
||||
|
||||
void TrommelkisteEditor::paint(Graphics& g)
|
||||
|
|
@ -606,7 +629,8 @@ void TrommelkisteEditor::resized()
|
|||
presetPrevBtn.setBounds(scaled(150), scaled(14), scaled(20), scaled(20));
|
||||
presetCombo.setBounds(scaled(172), scaled(14), scaled(100), 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
|
||||
const int hk = HDR_KNOB;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ public:
|
|||
void mouseDown(const juce::MouseEvent&) 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:
|
||||
TrommelkisteProcessor& proc;
|
||||
TrommelkisteLookAndFeel lnf;
|
||||
|
|
@ -78,6 +84,7 @@ private:
|
|||
juce::ComboBox presetCombo;
|
||||
juce::TextButton presetPrevBtn{"<"};
|
||||
juce::TextButton presetNextBtn{">"};
|
||||
juce::TextButton presetSaveBtn{"SAVE"};
|
||||
|
||||
// Global effect controls in header
|
||||
KnobSlider globalRingFreq, globalDestruction;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ TrommelkisteProcessor::TrommelkisteProcessor()
|
|||
formatManager.registerBasicFormats();
|
||||
scanSamples();
|
||||
loadKit(0);
|
||||
refreshUserPresets();
|
||||
}
|
||||
|
||||
TrommelkisteProcessor::~TrommelkisteProcessor() {}
|
||||
|
|
@ -605,19 +606,12 @@ static const TrommelkisteProcessor::Kit kits[] = {
|
|||
|
||||
const TrommelkisteProcessor::Kit* TrommelkisteProcessor::getKitList() { return kits; }
|
||||
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)
|
||||
{
|
||||
if (index < 0 || index >= getNumKits())
|
||||
return;
|
||||
|
||||
currentKitIndex = index;
|
||||
const auto& kit = kits[index];
|
||||
|
||||
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 (idx >= getNumKits()) idx = 0;
|
||||
loadKit(idx);
|
||||
if (index < 0 || index >= getNumPresets())
|
||||
return {};
|
||||
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 (idx < 0) idx = getNumKits() - 1;
|
||||
loadKit(idx);
|
||||
if (index < 0 || index >= getNumPresets())
|
||||
return;
|
||||
|
||||
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()
|
||||
|
|
@ -670,7 +807,7 @@ void TrommelkisteProcessor::getStateInformation(juce::MemoryBlock& destData)
|
|||
{
|
||||
auto state = apvts.copyState();
|
||||
|
||||
state.setProperty("kitIndex", currentKitIndex, nullptr);
|
||||
state.setProperty("presetIndex", currentPresetIndex, nullptr);
|
||||
|
||||
juce::ValueTree samples("Samples");
|
||||
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);
|
||||
apvts.replaceState(state);
|
||||
|
||||
currentKitIndex = (int)state.getProperty("kitIndex", 0);
|
||||
currentPresetIndex = (int)state.getProperty("presetIndex",
|
||||
state.getProperty("kitIndex", 0));
|
||||
|
||||
auto samples = state.getChildWithName("Samples");
|
||||
if (samples.isValid())
|
||||
|
|
|
|||
|
|
@ -78,14 +78,29 @@ public:
|
|||
const char* samples[NUM_TRACKS];
|
||||
};
|
||||
|
||||
int currentKitIndex = 0;
|
||||
int currentPresetIndex = 0;
|
||||
void loadKit(int index);
|
||||
void nextKit();
|
||||
void prevKit();
|
||||
int getNumKits() const;
|
||||
juce::String getCurrentKitName() const;
|
||||
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:
|
||||
struct Voice
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue