mirror of
https://codeberg.org/armin/chromaflock.git
synced 2026-09-01 04:10:47 +02:00
feat: beat-sync LFOs and delay to host BPM
This commit is contained in:
parent
21cccaf281
commit
5cc704c1d1
5 changed files with 250 additions and 110 deletions
|
|
@ -1,6 +1,33 @@
|
|||
#include "PluginProcessor.h"
|
||||
#include "PluginEditor.h"
|
||||
|
||||
// Beat-sync multipliers and their display labels (period = multiplier * beat).
|
||||
static const float beatValues[] = {0.25f, 0.3333333f, 0.5f, 0.75f, 1.0f, 1.5f, 2.0f};
|
||||
static const char* beatLabels[] = {"1/4", "1/3", "1/2", "3/4", "1.0", "1.5", "2.0"};
|
||||
static const int numBeats = 7;
|
||||
|
||||
// Halftone transpose menu: "0" plus -12..-1 and +1..+12.
|
||||
const juce::StringArray& getSemitoneChoices() {
|
||||
static const juce::StringArray s = [] {
|
||||
juce::StringArray a;
|
||||
a.add("0");
|
||||
for (int i = 12; i >= 1; --i) a.add("-" + juce::String(i));
|
||||
for (int i = 1; i <= 12; ++i) a.add("+" + juce::String(i));
|
||||
return a;
|
||||
}();
|
||||
return s;
|
||||
}
|
||||
static int semitoneFromIndex(int idx) {
|
||||
if (idx <= 0) return 0;
|
||||
if (idx <= 12) return -(13 - idx); // idx 1..12 -> -12..-1
|
||||
return idx - 12; // idx 13..24 -> +1..+12
|
||||
}
|
||||
|
||||
static juce::String beatValueToText(float v, int) {
|
||||
int i = juce::jlimit(0, numBeats - 1, juce::roundToInt(v));
|
||||
return juce::String(beatLabels[i]);
|
||||
}
|
||||
|
||||
ChromaFlockProcessor::ChromaFlockProcessor()
|
||||
: AudioProcessor(BusesProperties()
|
||||
.withOutput("Output", juce::AudioChannelSet::stereo(), true)),
|
||||
|
|
@ -153,6 +180,14 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create
|
|||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
juce::ParameterID{"lfo1Dest", 1}, "LFO1 Dest",
|
||||
juce::StringArray{"Filter", "OSC1", "OSC2", "Both OSC"}, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
juce::ParameterID{"lfo1Sync", 1}, "LFO1 Sync",
|
||||
juce::StringArray{"Sync Off", "Sync On"}, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
juce::ParameterID{"lfo1Beat", 1}, "LFO1 Beat",
|
||||
juce::NormalisableRange<float>(0.0f, static_cast<float>(numBeats - 1), 1.0f), 4.0f,
|
||||
juce::AudioParameterFloatAttributes()
|
||||
.withStringFromValueFunction(beatValueToText)));
|
||||
|
||||
// LFO 2
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
|
|
@ -166,6 +201,14 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create
|
|||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
juce::ParameterID{"lfo2Dest", 1}, "LFO2 Dest",
|
||||
juce::StringArray{"Filter", "OSC1", "OSC2", "Both OSC"}, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
juce::ParameterID{"lfo2Sync", 1}, "LFO2 Sync",
|
||||
juce::StringArray{"Sync Off", "Sync On"}, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
juce::ParameterID{"lfo2Beat", 1}, "LFO2 Beat",
|
||||
juce::NormalisableRange<float>(0.0f, static_cast<float>(numBeats - 1), 1.0f), 4.0f,
|
||||
juce::AudioParameterFloatAttributes()
|
||||
.withStringFromValueFunction(beatValueToText)));
|
||||
|
||||
// DELAY
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
|
|
@ -175,6 +218,14 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create
|
|||
juce::ParameterID{"delayFeedback", 1}, "Delay Feedback", zeroOne, 0.3f));
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
juce::ParameterID{"delayMix", 1}, "Delay Mix", zeroOne, 0.25f));
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
juce::ParameterID{"delaySync", 1}, "Delay Sync",
|
||||
juce::StringArray{"Sync Off", "Sync On"}, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
juce::ParameterID{"delayBeat", 1}, "Delay Beat",
|
||||
juce::NormalisableRange<float>(0.0f, static_cast<float>(numBeats - 1), 1.0f), 4.0f,
|
||||
juce::AudioParameterFloatAttributes()
|
||||
.withStringFromValueFunction(beatValueToText)));
|
||||
|
||||
// REVERB
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
|
|
@ -210,6 +261,18 @@ void ChromaFlockProcessor::updateVoiceParameters() {
|
|||
if (auto* voice = dynamic_cast<SubtractiveVoice*>(synth.getVoice(i))) {
|
||||
float pbRange = getParam("pitchBendRange");
|
||||
voice->setPitchBend(getPitchBend() * pbRange);
|
||||
|
||||
float l1Rate = getParam("lfo1Rate");
|
||||
if (getParam("lfo1Sync") > 0.5f) {
|
||||
int bi = juce::jlimit(0, numBeats - 1, juce::roundToInt(getParam("lfo1Beat")));
|
||||
l1Rate = static_cast<float>(currentBpm) / (60.0f * beatValues[bi]);
|
||||
}
|
||||
float l2Rate = getParam("lfo2Rate");
|
||||
if (getParam("lfo2Sync") > 0.5f) {
|
||||
int bi = juce::jlimit(0, numBeats - 1, juce::roundToInt(getParam("lfo2Beat")));
|
||||
l2Rate = static_cast<float>(currentBpm) / (60.0f * beatValues[bi]);
|
||||
}
|
||||
|
||||
voice->setParameters(
|
||||
static_cast<Waveform>(static_cast<int>(getParam("osc1Wave"))),
|
||||
static_cast<Waveform>(static_cast<int>(getParam("osc2Wave"))),
|
||||
|
|
@ -238,22 +301,36 @@ void ChromaFlockProcessor::updateVoiceParameters() {
|
|||
getParam("pan"),
|
||||
getParam("drive"),
|
||||
getParam("masterLevel"),
|
||||
getParam("lfo1Rate"),
|
||||
getParam("lfo1Depth"),
|
||||
static_cast<int>(getParam("lfo1Shape")),
|
||||
static_cast<int>(getParam("lfo1Dest")),
|
||||
getParam("lfo2Rate"),
|
||||
getParam("lfo2Depth"),
|
||||
static_cast<int>(getParam("lfo2Shape")),
|
||||
static_cast<int>(getParam("lfo2Dest"))
|
||||
l1Rate,
|
||||
getParam("lfo1Depth"),
|
||||
static_cast<int>(getParam("lfo1Shape")),
|
||||
static_cast<int>(getParam("lfo1Dest")),
|
||||
l2Rate,
|
||||
getParam("lfo2Depth"),
|
||||
static_cast<int>(getParam("lfo2Shape")),
|
||||
static_cast<int>(getParam("lfo2Dest"))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ChromaFlockProcessor::getTransposeSemitones() const {
|
||||
auto* op = apvts.getRawParameterValue("octaveTranspose");
|
||||
auto* sp = apvts.getRawParameterValue("semitoneTranspose");
|
||||
int octVal = (op != nullptr ? juce::roundToInt(op->load()) : 3) - 3; // index 0..6 -> -3..+3
|
||||
int semiVal = (sp != nullptr ? semitoneFromIndex(juce::roundToInt(sp->load())) : 0);
|
||||
return octVal * 12 + semiVal;
|
||||
}
|
||||
|
||||
void ChromaFlockProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) {
|
||||
juce::ScopedNoDenormals noDenormals;
|
||||
|
||||
if (auto* playHead = getPlayHead()) {
|
||||
juce::AudioPlayHead::CurrentPositionInfo pos;
|
||||
if (playHead->getCurrentPosition(pos) && pos.bpm > 0.0)
|
||||
currentBpm = pos.bpm;
|
||||
}
|
||||
|
||||
buffer.clear();
|
||||
updateVoiceParameters();
|
||||
|
||||
|
|
@ -263,7 +340,16 @@ void ChromaFlockProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::
|
|||
else if (metadata.getMessage().isNoteOff())
|
||||
activeNotes[metadata.getMessage().getNoteNumber()].store(false, std::memory_order_relaxed);
|
||||
|
||||
synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
|
||||
int transpose = getTransposeSemitones();
|
||||
juce::MidiBuffer transposed;
|
||||
for (const auto metadata : midiMessages) {
|
||||
auto msg = metadata.getMessage();
|
||||
if (msg.isNoteOn() || msg.isNoteOff())
|
||||
msg.setNoteNumber(juce::jlimit(0, 127, msg.getNoteNumber() + transpose));
|
||||
transposed.addEvent(msg, metadata.samplePosition);
|
||||
}
|
||||
|
||||
synth.renderNextBlock(buffer, transposed, 0, buffer.getNumSamples());
|
||||
|
||||
// Read FX params
|
||||
auto getParam = [&](const juce::String& id) -> float {
|
||||
|
|
@ -287,7 +373,12 @@ void ChromaFlockProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::
|
|||
float panRate = getParam("autoPanRate");
|
||||
float panDepth = getParam("autoPanDepth");
|
||||
|
||||
delay.setParameters(getParam("delayTime"), getParam("delayFeedback"), getParam("delayMix"));
|
||||
float delayTimeMs = getParam("delayTime");
|
||||
if (getParam("delaySync") > 0.5f) {
|
||||
int bi = juce::jlimit(0, numBeats - 1, juce::roundToInt(getParam("delayBeat")));
|
||||
delayTimeMs = beatValues[bi] * 60000.0f / static_cast<float>(currentBpm);
|
||||
}
|
||||
delay.setParameters(delayTimeMs, getParam("delayFeedback"), getParam("delayMix"));
|
||||
reverbFX.setParameters(getParam("reverbSize"), getParam("reverbDamping"), 0.8f, getParam("reverbMix"));
|
||||
|
||||
// FX processing: distortion → compression → auto-pan
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue