mirror of
https://codeberg.org/armin/trommelkiste.git
synced 2026-09-01 04:10:46 +02:00
recent
This commit is contained in:
parent
2e5fe1750f
commit
044e3b7a52
7 changed files with 618 additions and 47 deletions
|
|
@ -29,6 +29,24 @@ std::optional<juce::String> TrommelkisteProcessor::getNameForMidiNoteNumber(int
|
|||
void TrommelkisteProcessor::prepareToPlay(double sr, int)
|
||||
{
|
||||
currentSampleRate = sr;
|
||||
|
||||
juce::dsp::ProcessSpec spec;
|
||||
spec.sampleRate = sr;
|
||||
spec.maximumBlockSize = 512;
|
||||
spec.numChannels = 2;
|
||||
reverb.prepare(spec);
|
||||
reverbParams.roomSize = 0.5f;
|
||||
reverbParams.damping = 0.5f;
|
||||
reverbParams.wetLevel = 0.33f;
|
||||
reverbParams.dryLevel = 0.8f;
|
||||
reverbParams.width = 1.0f;
|
||||
reverbParams.freezeMode = 0.0f;
|
||||
reverb.setParameters(reverbParams);
|
||||
|
||||
const int delaySamples = DELAY_BUFFER_SECONDS * (int)sr;
|
||||
delayBuffer.setSize(2, delaySamples);
|
||||
delayBuffer.clear();
|
||||
delayWritePos = 0;
|
||||
}
|
||||
|
||||
void TrommelkisteProcessor::releaseResources() {}
|
||||
|
|
@ -42,6 +60,34 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
|
|||
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||
trackActive[i] = false;
|
||||
|
||||
// Shuffle: add ghost notes (bar-quantized)
|
||||
const float globalShuffleAmt = apvts.getRawParameterValue("global_shuffleAmount")->load();
|
||||
const float globalGroove = apvts.getRawParameterValue("global_groove")->load();
|
||||
const int shuffleDivIdx = (int)apvts.getRawParameterValue("global_shuffleDiv")->load();
|
||||
// 0=OFF, 1=every 4 bars, 2=every 8, 3=every 16, 4=every 32
|
||||
static constexpr int shuffleDivBars[] = { 0, 4, 8, 16, 32 };
|
||||
const int divBars = shuffleDivBars[juce::jlimit(0, 4, shuffleDivIdx)];
|
||||
|
||||
// Try to get bar position from play head
|
||||
bool hasTransport = false;
|
||||
int64_t currentBar = 0;
|
||||
if (auto* ph = getPlayHead())
|
||||
{
|
||||
auto info = ph->getPosition();
|
||||
if (info.hasValue() && info->getPpqPosition().hasValue())
|
||||
{
|
||||
hasTransport = true;
|
||||
double ppq = *info->getPpqPosition();
|
||||
double beatsPerBar = 4.0;
|
||||
if (info->getTimeSignature().hasValue())
|
||||
beatsPerBar = (double)(*info->getTimeSignature()).numerator;
|
||||
currentBar = (int64_t)(ppq / beatsPerBar);
|
||||
}
|
||||
}
|
||||
|
||||
const bool barAllowed = (divBars == 0) || !hasTransport || (currentBar % divBars == 0);
|
||||
|
||||
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
|
||||
for (const auto metadata : midi)
|
||||
{
|
||||
auto msg = metadata.getMessage();
|
||||
|
|
@ -49,7 +95,32 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
|
|||
{
|
||||
int t = msg.getNoteNumber() - BASE_NOTE;
|
||||
if (t >= 0 && t < NUM_TRACKS)
|
||||
{
|
||||
handleNoteOn(t, msg.getFloatVelocity());
|
||||
|
||||
const float shuffleAmt = apvts.getRawParameterValue(
|
||||
"t" + juce::String(t) + "_shuffle")->load();
|
||||
const float effShuffle = shuffleAmt * globalShuffleAmt;
|
||||
|
||||
if (effShuffle > 0.0f && barAllowed)
|
||||
{
|
||||
// Random note repeat on same drum
|
||||
if (dist(rng) < effShuffle * 0.5f)
|
||||
handleNoteOn(t, msg.getFloatVelocity() * effShuffle * 0.7f);
|
||||
|
||||
// Ghost note on random other drum
|
||||
if (dist(rng) < effShuffle * 0.9f)
|
||||
{
|
||||
int ghostTrack = t;
|
||||
while (ghostTrack == t)
|
||||
ghostTrack = std::uniform_int_distribution<int>(0, NUM_TRACKS - 1)(rng);
|
||||
handleNoteOn(ghostTrack, msg.getFloatVelocity() * effShuffle);
|
||||
}
|
||||
}
|
||||
|
||||
if (shuffleAmt > 0.0f && globalGroove > 0.0f)
|
||||
handleNoteOn(t, msg.getFloatVelocity() * (1.0f + globalGroove * 0.3f * shuffleAmt));
|
||||
}
|
||||
}
|
||||
else if (msg.isNoteOff())
|
||||
{
|
||||
|
|
@ -62,6 +133,12 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
|
|||
auto* outL = buffer.getWritePointer(0);
|
||||
auto* outR = buffer.getNumChannels() > 1 ? buffer.getWritePointer(1) : nullptr;
|
||||
|
||||
// Temporary send buffers
|
||||
juce::AudioBuffer<float> delaySend(2, numSamples);
|
||||
juce::AudioBuffer<float> reverbSend(2, numSamples);
|
||||
delaySend.clear();
|
||||
reverbSend.clear();
|
||||
|
||||
for (auto& v : voices)
|
||||
{
|
||||
if (!v.active)
|
||||
|
|
@ -86,7 +163,11 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
|
|||
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();
|
||||
const bool decayGate = apvts.getRawParameterValue(p + "decay")->load() > 0.5f;
|
||||
const float delaySendAmt = apvts.getRawParameterValue(p + "delaySend")->load();
|
||||
const float reverbSendAmt = apvts.getRawParameterValue(p + "reverbSend")->load();
|
||||
const float ringModAmt = apvts.getRawParameterValue(p + "ringMod")->load();
|
||||
const float destructionAmt = apvts.getRawParameterValue(p + "destruction")->load();
|
||||
|
||||
float pitchRatio = std::pow(2.0f, pitchSemitones / 12.0f)
|
||||
* (float)track.fileSampleRate / (float)currentSampleRate;
|
||||
|
|
@ -107,13 +188,17 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
|
|||
const bool hasR = track.buffer.getNumChannels() > 1;
|
||||
const float* sDataR = hasR ? track.buffer.getReadPointer(1) : nullptr;
|
||||
|
||||
const float globalRingFreq = apvts.getRawParameterValue("global_ringFreq")->load();
|
||||
const float globalDestr = apvts.getRawParameterValue("global_destruction")->load();
|
||||
const float ringModInc = 2.0f * juce::MathConstants<float>::pi * globalRingFreq / (float)currentSampleRate;
|
||||
|
||||
for (int s = 0; s < numSamples; ++s)
|
||||
{
|
||||
if (!v.active)
|
||||
break;
|
||||
|
||||
float env = 1.0f;
|
||||
if (decayType == 0)
|
||||
if (!decayGate)
|
||||
{
|
||||
env = 1.0f - (float)v.samplesPlayed / maxSamples;
|
||||
if (env <= 0.0f) { v.active = false; break; }
|
||||
|
|
@ -140,6 +225,30 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
|
|||
smplR = v.filterStateR;
|
||||
}
|
||||
|
||||
// Ring modulation
|
||||
if (ringModAmt > 0.0f)
|
||||
{
|
||||
float mod = std::sin(v.ringModPhase);
|
||||
smplL = smplL * (1.0f - ringModAmt + ringModAmt * mod);
|
||||
smplR = smplR * (1.0f - ringModAmt + ringModAmt * mod);
|
||||
v.ringModPhase += ringModInc;
|
||||
if (v.ringModPhase >= 2.0f * juce::MathConstants<float>::pi)
|
||||
v.ringModPhase -= 2.0f * juce::MathConstants<float>::pi;
|
||||
}
|
||||
|
||||
// Destruction (soft clip + bitcrush)
|
||||
const float effDestr = juce::jmin(1.0f, destructionAmt + globalDestr);
|
||||
if (effDestr > 0.0f)
|
||||
{
|
||||
float drive = 1.0f + effDestr * 19.0f;
|
||||
smplL = std::tanh(smplL * drive);
|
||||
smplR = std::tanh(smplR * drive);
|
||||
float bits = juce::jmax(2.0f, 16.0f - effDestr * 14.0f);
|
||||
float levels = std::pow(2.0f, bits);
|
||||
smplL = std::round(smplL * levels) / levels;
|
||||
smplR = std::round(smplR * levels) / levels;
|
||||
}
|
||||
|
||||
smplL *= env * gain;
|
||||
smplR *= env * gain;
|
||||
|
||||
|
|
@ -147,6 +256,12 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
|
|||
if (outR)
|
||||
outR[s] += smplR * panR;
|
||||
|
||||
// Send to effects
|
||||
delaySend.addSample(0, s, smplL * delaySendAmt);
|
||||
delaySend.addSample(1, s, smplR * delaySendAmt);
|
||||
reverbSend.addSample(0, s, smplL * reverbSendAmt);
|
||||
reverbSend.addSample(1, s, smplR * reverbSendAmt);
|
||||
|
||||
v.position += pitchRatio;
|
||||
++v.samplesPlayed;
|
||||
|
||||
|
|
@ -154,6 +269,81 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
|
|||
v.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Read master effect levels
|
||||
const float masterDelayMix = apvts.getRawParameterValue("master_delay")->load();
|
||||
const float masterReverbMix = apvts.getRawParameterValue("master_reverb")->load();
|
||||
|
||||
// Delay sync
|
||||
const int delaySyncDiv = (int)apvts.getRawParameterValue("delay_syncDiv")->load();
|
||||
float delayTimeSec = 0.45f; // default free-running delay time
|
||||
if (delaySyncDiv >= 1 && delaySyncDiv <= 8)
|
||||
{
|
||||
double bpm = 120.0;
|
||||
if (auto* ph = getPlayHead())
|
||||
{
|
||||
auto info = ph->getPosition();
|
||||
if (info.hasValue() && info->getBpm().hasValue())
|
||||
bpm = *info->getBpm();
|
||||
}
|
||||
double beats = delaySyncDiv * 0.25;
|
||||
delayTimeSec = (float)(beats * 60.0 / bpm);
|
||||
}
|
||||
|
||||
// Process delay
|
||||
const int delaySamples = delayBuffer.getNumSamples();
|
||||
auto* delL = delayBuffer.getWritePointer(0);
|
||||
auto* delR = delayBuffer.getNumChannels() > 1
|
||||
? delayBuffer.getWritePointer(1) : nullptr;
|
||||
const float feedback = 0.4f;
|
||||
const float delayMix = 0.3f;
|
||||
const int delayTimeSamples = (int)(delayTimeSec * currentSampleRate);
|
||||
|
||||
for (int s = 0; s < numSamples; ++s)
|
||||
{
|
||||
const int readPos = (delayWritePos - delayTimeSamples + delaySamples)
|
||||
% delaySamples;
|
||||
const float delOutL = delL[readPos];
|
||||
const float delOutR = delR ? delR[readPos] : delOutL;
|
||||
|
||||
delL[delayWritePos] = delaySend.getSample(0, s) + delOutL * feedback;
|
||||
if (delR)
|
||||
delR[delayWritePos] = delaySend.getSample(1, s) + delOutR * feedback;
|
||||
|
||||
outL[s] += delOutL * delayMix * masterDelayMix;
|
||||
if (outR)
|
||||
outR[s] += delOutR * delayMix * masterDelayMix;
|
||||
|
||||
delayWritePos = (delayWritePos + 1) % delaySamples;
|
||||
}
|
||||
|
||||
// Process reverb
|
||||
juce::dsp::AudioBlock<float> reverbBlock(reverbSend);
|
||||
juce::dsp::ProcessContextReplacing<float> reverbCtx(reverbBlock);
|
||||
reverb.process(reverbCtx);
|
||||
|
||||
for (int s = 0; s < numSamples; ++s)
|
||||
{
|
||||
outL[s] += reverbSend.getSample(0, s) * masterReverbMix;
|
||||
if (outR)
|
||||
outR[s] += reverbSend.getSample(1, s) * masterReverbMix;
|
||||
}
|
||||
|
||||
// Master volume & pan
|
||||
const float masterVol = apvts.getRawParameterValue("master_volume")->load();
|
||||
const float masterPan = apvts.getRawParameterValue("master_pan")->load();
|
||||
const float mPanAngle = (masterPan + 1.0f) * 0.25f * MathConstants<float>::pi;
|
||||
const float mPanL = std::cos(mPanAngle) * Decibels::decibelsToGain(-3.0f);
|
||||
const float mPanR = std::sin(mPanAngle) * Decibels::decibelsToGain(-3.0f);
|
||||
|
||||
for (int s = 0; s < numSamples; ++s)
|
||||
{
|
||||
float tmpL = outL[s] * mPanL * masterVol;
|
||||
float tmpR = outR ? outR[s] * mPanR * masterVol : tmpL;
|
||||
outL[s] = tmpL;
|
||||
if (outR)
|
||||
outR[s] = tmpR;
|
||||
}
|
||||
}
|
||||
|
||||
int TrommelkisteProcessor::findFreeVoice(int trackIndex)
|
||||
|
|
@ -206,9 +396,9 @@ void TrommelkisteProcessor::handleNoteOff(int trackIndex)
|
|||
{
|
||||
if (v.active && v.trackIndex == trackIndex)
|
||||
{
|
||||
const int decayType = (int)apvts.getRawParameterValue(
|
||||
"t" + juce::String(trackIndex) + "_decay")->load();
|
||||
if (decayType == 1)
|
||||
const bool decayGate = apvts.getRawParameterValue(
|
||||
"t" + juce::String(trackIndex) + "_decay")->load() > 0.5f;
|
||||
if (decayGate)
|
||||
v.active = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -515,7 +705,6 @@ void TrommelkisteProcessor::setStateInformation(const void* data, int sizeInByte
|
|||
juce::AudioProcessorValueTreeState::ParameterLayout TrommelkisteProcessor::createParameterLayout()
|
||||
{
|
||||
AudioProcessorValueTreeState::ParameterLayout layout;
|
||||
const StringArray decayTypes = {"Saw", "Gate"};
|
||||
|
||||
for (int i = 0; i < NUM_TRACKS; ++i)
|
||||
{
|
||||
|
|
@ -546,11 +735,71 @@ juce::AudioProcessorValueTreeState::ParameterLayout TrommelkisteProcessor::creat
|
|||
p + "pan", tn + "Pan",
|
||||
NormalisableRange<float>(-1.0f, 1.0f), 0.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterChoice>(
|
||||
p + "decay", tn + "Decay",
|
||||
decayTypes, 0));
|
||||
layout.add(std::make_unique<AudioParameterBool>(
|
||||
p + "decay", tn + "Decay", false));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
p + "delaySend", tn + "Delay",
|
||||
NormalisableRange<float>(0.0f, 1.0f), 0.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
p + "reverbSend", tn + "Reverb",
|
||||
NormalisableRange<float>(0.0f, 1.0f), 0.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
p + "ringMod", tn + "RingMod",
|
||||
NormalisableRange<float>(0.0f, 1.0f), 0.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
p + "destruction", tn + "Destr",
|
||||
NormalisableRange<float>(0.0f, 1.0f), 0.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
p + "shuffle", tn + "Shuffle",
|
||||
NormalisableRange<float>(0.0f, 1.0f), 0.0f));
|
||||
}
|
||||
|
||||
// Global controls
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
"master_volume", "Master Volume",
|
||||
NormalisableRange<float>(0.0f, 1.2f, 0.01f), 1.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
"master_pan", "Master Pan",
|
||||
NormalisableRange<float>(-1.0f, 1.0f, 0.01f), 0.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
"master_delay", "Master Delay",
|
||||
NormalisableRange<float>(0.0f, 1.0f, 0.01f), 1.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
"master_reverb", "Master Reverb",
|
||||
NormalisableRange<float>(0.0f, 1.0f, 0.01f), 1.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
"delay_syncDiv", "Delay Sync",
|
||||
NormalisableRange<float>(0.0f, 8.0f, 1.0f), 0.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
"global_shuffleAmount", "Shuffle Amt",
|
||||
NormalisableRange<float>(0.0f, 1.0f, 0.01f), 1.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
"global_groove", "Groove",
|
||||
NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
"global_shuffleDiv", "Shuffle Div",
|
||||
NormalisableRange<float>(0.0f, 4.0f, 1.0f), 0.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
"global_ringFreq", "Ring Freq",
|
||||
NormalisableRange<float>(20.0f, 2000.0f, 1.0f, 0.3f), 80.0f));
|
||||
|
||||
layout.add(std::make_unique<AudioParameterFloat>(
|
||||
"global_destruction", "Destruction",
|
||||
NormalisableRange<float>(0.0f, 1.0f), 0.0f));
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue