mirror of
https://codeberg.org/armin/trommelkiste.git
synced 2026-09-01 04:10:46 +02:00
560 lines
18 KiB
C++
560 lines
18 KiB
C++
|
|
#include "PluginProcessor.h"
|
||
|
|
#include "PluginEditor.h"
|
||
|
|
#include <BinaryData.h>
|
||
|
|
#include <SampleLibrary.h>
|
||
|
|
|
||
|
|
using namespace juce;
|
||
|
|
|
||
|
|
TrommelkisteProcessor::TrommelkisteProcessor()
|
||
|
|
: AudioProcessor(BusesProperties()
|
||
|
|
.withOutput("Output", juce::AudioChannelSet::stereo(), true)),
|
||
|
|
apvts(*this, nullptr, "Parameters", createParameterLayout())
|
||
|
|
{
|
||
|
|
formatManager.registerBasicFormats();
|
||
|
|
scanSamples();
|
||
|
|
loadKit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
TrommelkisteProcessor::~TrommelkisteProcessor() {}
|
||
|
|
|
||
|
|
std::optional<juce::String> TrommelkisteProcessor::getNameForMidiNoteNumber(int note, int)
|
||
|
|
{
|
||
|
|
static const char* names[] = {"BD", "RS", "SN", "CL", "CH", "PH", "OH", "RD", "LT", "MT"};
|
||
|
|
int t = note - BASE_NOTE;
|
||
|
|
if (t >= 0 && t < NUM_TRACKS)
|
||
|
|
return juce::String(names[t]);
|
||
|
|
return std::nullopt;
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::prepareToPlay(double sr, int)
|
||
|
|
{
|
||
|
|
currentSampleRate = sr;
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::releaseResources() {}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi)
|
||
|
|
{
|
||
|
|
juce::ScopedNoDenormals nd;
|
||
|
|
const int numSamples = buffer.getNumSamples();
|
||
|
|
buffer.clear();
|
||
|
|
|
||
|
|
for (int i = 0; i < NUM_TRACKS; ++i)
|
||
|
|
trackActive[i] = false;
|
||
|
|
|
||
|
|
for (const auto metadata : midi)
|
||
|
|
{
|
||
|
|
auto msg = metadata.getMessage();
|
||
|
|
if (msg.isNoteOn())
|
||
|
|
{
|
||
|
|
int t = msg.getNoteNumber() - BASE_NOTE;
|
||
|
|
if (t >= 0 && t < NUM_TRACKS)
|
||
|
|
handleNoteOn(t, msg.getFloatVelocity());
|
||
|
|
}
|
||
|
|
else if (msg.isNoteOff())
|
||
|
|
{
|
||
|
|
int t = msg.getNoteNumber() - BASE_NOTE;
|
||
|
|
if (t >= 0 && t < NUM_TRACKS)
|
||
|
|
handleNoteOff(t);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
auto* outL = buffer.getWritePointer(0);
|
||
|
|
auto* outR = buffer.getNumChannels() > 1 ? buffer.getWritePointer(1) : nullptr;
|
||
|
|
|
||
|
|
for (auto& v : voices)
|
||
|
|
{
|
||
|
|
if (!v.active)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
const int ti = v.trackIndex;
|
||
|
|
trackActive[ti] = true;
|
||
|
|
auto& track = tracks[ti];
|
||
|
|
const juce::SpinLock::ScopedLockType sl(track.lock);
|
||
|
|
|
||
|
|
const int bufLen = track.buffer.getNumSamples();
|
||
|
|
if (bufLen == 0)
|
||
|
|
{
|
||
|
|
v.active = false;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
const juce::String p = "t" + juce::String(ti) + "_";
|
||
|
|
const float level = apvts.getRawParameterValue(p + "level")->load();
|
||
|
|
const float length = apvts.getRawParameterValue(p + "length")->load();
|
||
|
|
const float velAmt = apvts.getRawParameterValue(p + "velocity")->load();
|
||
|
|
const float pitchSemitones = apvts.getRawParameterValue(p + "pitch")->load();
|
||
|
|
const float toneHz = apvts.getRawParameterValue(p + "tone")->load();
|
||
|
|
const float pan = apvts.getRawParameterValue(p + "pan")->load();
|
||
|
|
const int decayType = (int)apvts.getRawParameterValue(p + "decay")->load();
|
||
|
|
|
||
|
|
float pitchRatio = std::pow(2.0f, pitchSemitones / 12.0f)
|
||
|
|
* (float)track.fileSampleRate / (float)currentSampleRate;
|
||
|
|
|
||
|
|
const float maxSamples = length * (float)currentSampleRate;
|
||
|
|
const float toneA = (toneHz < 19000.0f)
|
||
|
|
? std::exp(-2.0f * juce::MathConstants<float>::pi * toneHz / (float)currentSampleRate)
|
||
|
|
: 0.0f;
|
||
|
|
|
||
|
|
const float effVel = 1.0f - velAmt + velAmt * v.noteVelocity;
|
||
|
|
const float gain = level * effVel;
|
||
|
|
|
||
|
|
const float panAngle = (pan + 1.0f) * 0.25f * juce::MathConstants<float>::pi;
|
||
|
|
const float panL = std::cos(panAngle) * juce::Decibels::decibelsToGain(-3.0f);
|
||
|
|
const float panR = std::sin(panAngle) * juce::Decibels::decibelsToGain(-3.0f);
|
||
|
|
|
||
|
|
const float* sData = track.buffer.getReadPointer(0);
|
||
|
|
const bool hasR = track.buffer.getNumChannels() > 1;
|
||
|
|
const float* sDataR = hasR ? track.buffer.getReadPointer(1) : nullptr;
|
||
|
|
|
||
|
|
for (int s = 0; s < numSamples; ++s)
|
||
|
|
{
|
||
|
|
if (!v.active)
|
||
|
|
break;
|
||
|
|
|
||
|
|
float env = 1.0f;
|
||
|
|
if (decayType == 0)
|
||
|
|
{
|
||
|
|
env = 1.0f - (float)v.samplesPlayed / maxSamples;
|
||
|
|
if (env <= 0.0f) { v.active = false; break; }
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (v.samplesPlayed >= (int)maxSamples) { v.active = false; break; }
|
||
|
|
}
|
||
|
|
|
||
|
|
int pos0 = (int)v.position;
|
||
|
|
if (pos0 >= bufLen) { v.active = false; break; }
|
||
|
|
const float frac = v.position - (float)pos0;
|
||
|
|
int pos1 = pos0 + 1;
|
||
|
|
if (pos1 >= bufLen) pos1 = bufLen - 1;
|
||
|
|
|
||
|
|
float smplL = sData[pos0] + (sData[pos1] - sData[pos0]) * frac;
|
||
|
|
float smplR = hasR ? (sDataR[pos0] + (sDataR[pos1] - sDataR[pos0]) * frac) : smplL;
|
||
|
|
|
||
|
|
if (toneA > 0.0f)
|
||
|
|
{
|
||
|
|
v.filterStateL = (1.0f - toneA) * smplL + toneA * v.filterStateL;
|
||
|
|
smplL = v.filterStateL;
|
||
|
|
v.filterStateR = (1.0f - toneA) * smplR + toneA * v.filterStateR;
|
||
|
|
smplR = v.filterStateR;
|
||
|
|
}
|
||
|
|
|
||
|
|
smplL *= env * gain;
|
||
|
|
smplR *= env * gain;
|
||
|
|
|
||
|
|
outL[s] += smplL * panL;
|
||
|
|
if (outR)
|
||
|
|
outR[s] += smplR * panR;
|
||
|
|
|
||
|
|
v.position += pitchRatio;
|
||
|
|
++v.samplesPlayed;
|
||
|
|
|
||
|
|
if (v.position >= (float)bufLen)
|
||
|
|
v.active = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
int TrommelkisteProcessor::findFreeVoice(int trackIndex)
|
||
|
|
{
|
||
|
|
for (int i = 0; i < MAX_VOICES; ++i)
|
||
|
|
if (!voices[i].active)
|
||
|
|
return i;
|
||
|
|
|
||
|
|
int oldest = 0, bestAge = -1;
|
||
|
|
for (int i = 0; i < MAX_VOICES; ++i)
|
||
|
|
{
|
||
|
|
if (voices[i].trackIndex == trackIndex && voices[i].samplesPlayed > bestAge)
|
||
|
|
{
|
||
|
|
bestAge = voices[i].samplesPlayed;
|
||
|
|
oldest = i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (bestAge >= 0)
|
||
|
|
return oldest;
|
||
|
|
|
||
|
|
oldest = 0;
|
||
|
|
bestAge = -1;
|
||
|
|
for (int i = 0; i < MAX_VOICES; ++i)
|
||
|
|
{
|
||
|
|
if (voices[i].samplesPlayed > bestAge)
|
||
|
|
{
|
||
|
|
bestAge = voices[i].samplesPlayed;
|
||
|
|
oldest = i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return oldest;
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::handleNoteOn(int trackIndex, float velocity)
|
||
|
|
{
|
||
|
|
int idx = findFreeVoice(trackIndex);
|
||
|
|
auto& v = voices[idx];
|
||
|
|
v.active = true;
|
||
|
|
v.trackIndex = trackIndex;
|
||
|
|
v.position = 0.0f;
|
||
|
|
v.samplesPlayed = 0;
|
||
|
|
v.filterStateL = 0.0f;
|
||
|
|
v.filterStateR = 0.0f;
|
||
|
|
v.noteVelocity = velocity;
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::handleNoteOff(int trackIndex)
|
||
|
|
{
|
||
|
|
for (auto& v : voices)
|
||
|
|
{
|
||
|
|
if (v.active && v.trackIndex == trackIndex)
|
||
|
|
{
|
||
|
|
const int decayType = (int)apvts.getRawParameterValue(
|
||
|
|
"t" + juce::String(trackIndex) + "_decay")->load();
|
||
|
|
if (decayType == 1)
|
||
|
|
v.active = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::loadSample(int trackIndex, const juce::File& file)
|
||
|
|
{
|
||
|
|
if (trackIndex < 0 || trackIndex >= NUM_TRACKS)
|
||
|
|
return;
|
||
|
|
|
||
|
|
std::unique_ptr<juce::AudioFormatReader> reader(formatManager.createReaderFor(file));
|
||
|
|
if (reader == nullptr)
|
||
|
|
return;
|
||
|
|
|
||
|
|
juce::AudioBuffer<float> newBuf(reader->numChannels, (int)reader->lengthInSamples);
|
||
|
|
reader->read(&newBuf, 0, (int)reader->lengthInSamples, 0, true, true);
|
||
|
|
|
||
|
|
{
|
||
|
|
const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock);
|
||
|
|
tracks[trackIndex].buffer = std::move(newBuf);
|
||
|
|
tracks[trackIndex].fileSampleRate = (int)reader->sampleRate;
|
||
|
|
tracks[trackIndex].filePath = file.getFullPathName();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::clearSample(int trackIndex)
|
||
|
|
{
|
||
|
|
if (trackIndex < 0 || trackIndex >= NUM_TRACKS)
|
||
|
|
return;
|
||
|
|
const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock);
|
||
|
|
tracks[trackIndex].buffer.setSize(1, 0);
|
||
|
|
tracks[trackIndex].filePath = "";
|
||
|
|
}
|
||
|
|
|
||
|
|
static void readAudioFromBuffer(juce::AudioFormatManager& fmtMgr,
|
||
|
|
juce::AudioBuffer<float>& dest,
|
||
|
|
int& destSampleRate,
|
||
|
|
const char* data, int size)
|
||
|
|
{
|
||
|
|
auto memStream = std::make_unique<juce::MemoryInputStream>(data, size, false);
|
||
|
|
std::unique_ptr<juce::AudioFormatReader> reader(fmtMgr.createReaderFor(std::move(memStream)));
|
||
|
|
if (reader == nullptr)
|
||
|
|
return;
|
||
|
|
dest.setSize((int)reader->numChannels, (int)reader->lengthInSamples);
|
||
|
|
reader->read(&dest, 0, (int)reader->lengthInSamples, 0, true, true);
|
||
|
|
destSampleRate = (int)reader->sampleRate;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Sample library ─────────────────────────────────────────────
|
||
|
|
|
||
|
|
static bool matchesPrefix(const juce::String& name, const juce::String& prefix)
|
||
|
|
{
|
||
|
|
return name.toUpperCase().startsWith(prefix.toUpperCase());
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::scanSamples()
|
||
|
|
{
|
||
|
|
static const char* prefixes[][8] = {
|
||
|
|
{ "BT", nullptr }, // 0 BD
|
||
|
|
{ "RIM", nullptr }, // 1 RS
|
||
|
|
{ "ST", "STAT", nullptr }, // 2 SN
|
||
|
|
{ "HANDCLP", nullptr }, // 3 CL
|
||
|
|
{ "HHCD", nullptr }, // 4 CH
|
||
|
|
{ "CLOP", nullptr }, // 5 PH
|
||
|
|
{ "HHOD", nullptr }, // 6 OH
|
||
|
|
{ "RIDE", nullptr }, // 7 RD
|
||
|
|
{ "LT", nullptr }, // 8 LT
|
||
|
|
{ "MT", nullptr }, // 9 MT
|
||
|
|
};
|
||
|
|
|
||
|
|
for (int t = 0; t < NUM_TRACKS; ++t)
|
||
|
|
{
|
||
|
|
trackSamples[t].clear();
|
||
|
|
currentSampleIndex[t] = -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (int i = 0; i < numEmbeddedSamples; ++i)
|
||
|
|
{
|
||
|
|
const auto& es = embeddedSamples[i];
|
||
|
|
juce::String name(es.name);
|
||
|
|
|
||
|
|
for (int t = 0; t < NUM_TRACKS; ++t)
|
||
|
|
{
|
||
|
|
for (int p = 0; prefixes[t][p] != nullptr; ++p)
|
||
|
|
{
|
||
|
|
if (matchesPrefix(name, prefixes[t][p]))
|
||
|
|
{
|
||
|
|
SampleRef ref;
|
||
|
|
ref.name = name;
|
||
|
|
ref.data = es.data;
|
||
|
|
ref.dataSize = es.size;
|
||
|
|
trackSamples[t].add(ref);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
struct SampleRefSorter
|
||
|
|
{
|
||
|
|
int compareElements(const SampleRef& a, const SampleRef& b) const { return a.name.compare(b.name); }
|
||
|
|
};
|
||
|
|
SampleRefSorter sorter;
|
||
|
|
for (int t = 0; t < NUM_TRACKS; ++t)
|
||
|
|
trackSamples[t].sort(sorter);
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::loadSampleByIndex(int trackIndex, int sampleIndex)
|
||
|
|
{
|
||
|
|
if (trackIndex < 0 || trackIndex >= NUM_TRACKS)
|
||
|
|
return;
|
||
|
|
if (sampleIndex < 0 || sampleIndex >= trackSamples[trackIndex].size())
|
||
|
|
return;
|
||
|
|
|
||
|
|
currentSampleIndex[trackIndex] = sampleIndex;
|
||
|
|
const auto& ref = trackSamples[trackIndex][sampleIndex];
|
||
|
|
|
||
|
|
if (ref.data != nullptr && ref.dataSize > 0)
|
||
|
|
{
|
||
|
|
const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock);
|
||
|
|
readAudioFromBuffer(formatManager, tracks[trackIndex].buffer,
|
||
|
|
tracks[trackIndex].fileSampleRate,
|
||
|
|
ref.data, ref.dataSize);
|
||
|
|
tracks[trackIndex].filePath = "";
|
||
|
|
}
|
||
|
|
else if (ref.file.existsAsFile())
|
||
|
|
{
|
||
|
|
loadSample(trackIndex, ref.file);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::nextSample(int trackIndex)
|
||
|
|
{
|
||
|
|
if (trackIndex < 0 || trackIndex >= NUM_TRACKS)
|
||
|
|
return;
|
||
|
|
const int n = trackSamples[trackIndex].size();
|
||
|
|
if (n == 0) return;
|
||
|
|
|
||
|
|
int idx = currentSampleIndex[trackIndex] + 1;
|
||
|
|
if (idx >= n) idx = 0;
|
||
|
|
loadSampleByIndex(trackIndex, idx);
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::prevSample(int trackIndex)
|
||
|
|
{
|
||
|
|
if (trackIndex < 0 || trackIndex >= NUM_TRACKS)
|
||
|
|
return;
|
||
|
|
const int n = trackSamples[trackIndex].size();
|
||
|
|
if (n == 0) return;
|
||
|
|
|
||
|
|
int idx = currentSampleIndex[trackIndex] - 1;
|
||
|
|
if (idx < 0) idx = n - 1;
|
||
|
|
loadSampleByIndex(trackIndex, idx);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Kit / Preset system ───────────────────────────────────────
|
||
|
|
|
||
|
|
static const TrommelkisteProcessor::Kit kits[] = {
|
||
|
|
{ "Classic", {
|
||
|
|
"BT0AADA", "RIM63", "ST0T0SA", "HANDCLP2", "HHCD6",
|
||
|
|
"CLOP2", "HHOD6", "RIDED6", "LT0DA", "MT0DA" } },
|
||
|
|
{ "Punchy", {
|
||
|
|
"BT7A0D3", "RIM127", "ST7T7S3", "HANDCLP1", "HHCD2",
|
||
|
|
"CLOP1", "HHOD2", "RIDED2", "LT7D3", "MT7D3" } },
|
||
|
|
{ "Soft", {
|
||
|
|
"BT0A0D7", "RIM63", "ST0T0S7", "HANDCLP2", "HHCD8",
|
||
|
|
"CLOP4", "HHOD8", "RIDED8", "LT0D7", "MT0D7" } },
|
||
|
|
{ "Raw", {
|
||
|
|
"BTAAADA", "RIM127", "STATASA", "HANDCLP1", "HHCDA",
|
||
|
|
"CLOP3", "HHODA", "RIDEDA", "LTADA", "MTADA" } },
|
||
|
|
};
|
||
|
|
|
||
|
|
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)
|
||
|
|
{
|
||
|
|
if (kit.samples[t] == nullptr || kit.samples[t][0] == '\0')
|
||
|
|
{
|
||
|
|
currentSampleIndex[t] = -1;
|
||
|
|
clearSample(t);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
juce::String target(kit.samples[t]);
|
||
|
|
int found = -1;
|
||
|
|
for (int s = 0; s < trackSamples[t].size(); ++s)
|
||
|
|
{
|
||
|
|
if (trackSamples[t][s].name.equalsIgnoreCase(target))
|
||
|
|
{
|
||
|
|
found = s;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (found >= 0)
|
||
|
|
loadSampleByIndex(t, found);
|
||
|
|
else
|
||
|
|
clearSample(t);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::nextKit()
|
||
|
|
{
|
||
|
|
int idx = currentKitIndex + 1;
|
||
|
|
if (idx >= getNumKits()) idx = 0;
|
||
|
|
loadKit(idx);
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::prevKit()
|
||
|
|
{
|
||
|
|
int idx = currentKitIndex - 1;
|
||
|
|
if (idx < 0) idx = getNumKits() - 1;
|
||
|
|
loadKit(idx);
|
||
|
|
}
|
||
|
|
|
||
|
|
juce::AudioProcessorEditor* TrommelkisteProcessor::createEditor()
|
||
|
|
{
|
||
|
|
return new TrommelkisteEditor(*this);
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::getStateInformation(juce::MemoryBlock& destData)
|
||
|
|
{
|
||
|
|
auto state = apvts.copyState();
|
||
|
|
|
||
|
|
state.setProperty("kitIndex", currentKitIndex, 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());
|
||
|
|
copyXmlToBinary(*xml, destData);
|
||
|
|
}
|
||
|
|
|
||
|
|
void TrommelkisteProcessor::setStateInformation(const void* data, int sizeInBytes)
|
||
|
|
{
|
||
|
|
std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
|
||
|
|
if (xml == nullptr)
|
||
|
|
return;
|
||
|
|
|
||
|
|
auto state = juce::ValueTree::fromXml(*xml);
|
||
|
|
apvts.replaceState(state);
|
||
|
|
|
||
|
|
currentKitIndex = (int)state.getProperty("kitIndex", 0);
|
||
|
|
|
||
|
|
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::AudioProcessorValueTreeState::ParameterLayout TrommelkisteProcessor::createParameterLayout()
|
||
|
|
{
|
||
|
|
AudioProcessorValueTreeState::ParameterLayout layout;
|
||
|
|
const StringArray decayTypes = {"Saw", "Gate"};
|
||
|
|
|
||
|
|
for (int i = 0; i < NUM_TRACKS; ++i)
|
||
|
|
{
|
||
|
|
const String p = "t" + String(i) + "_";
|
||
|
|
const String tn = "T" + String(i + 1) + " ";
|
||
|
|
|
||
|
|
layout.add(std::make_unique<AudioParameterFloat>(
|
||
|
|
p + "level", tn + "Level",
|
||
|
|
NormalisableRange<float>(0.0f, 1.0f), 0.8f));
|
||
|
|
|
||
|
|
layout.add(std::make_unique<AudioParameterFloat>(
|
||
|
|
p + "length", tn + "Length",
|
||
|
|
NormalisableRange<float>(0.01f, 2.0f, 0.001f, 0.45f), 0.5f));
|
||
|
|
|
||
|
|
layout.add(std::make_unique<AudioParameterFloat>(
|
||
|
|
p + "velocity", tn + "Velocity",
|
||
|
|
NormalisableRange<float>(0.0f, 1.0f), 1.0f));
|
||
|
|
|
||
|
|
layout.add(std::make_unique<AudioParameterFloat>(
|
||
|
|
p + "pitch", tn + "Pitch",
|
||
|
|
NormalisableRange<float>(-12.0f, 12.0f, 0.1f), 0.0f));
|
||
|
|
|
||
|
|
layout.add(std::make_unique<AudioParameterFloat>(
|
||
|
|
p + "tone", tn + "Tone",
|
||
|
|
NormalisableRange<float>(20.0f, 20000.0f, 1.0f, 0.25f), 20000.0f));
|
||
|
|
|
||
|
|
layout.add(std::make_unique<AudioParameterFloat>(
|
||
|
|
p + "pan", tn + "Pan",
|
||
|
|
NormalisableRange<float>(-1.0f, 1.0f), 0.0f));
|
||
|
|
|
||
|
|
layout.add(std::make_unique<AudioParameterChoice>(
|
||
|
|
p + "decay", tn + "Decay",
|
||
|
|
decayTypes, 0));
|
||
|
|
}
|
||
|
|
|
||
|
|
return layout;
|
||
|
|
}
|
||
|
|
|
||
|
|
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
||
|
|
{
|
||
|
|
return new TrommelkisteProcessor();
|
||
|
|
}
|