mirror of
https://codeberg.org/armin/chromaflock.git
synced 2026-09-01 04:10:47 +02:00
add arpeggiator, rename presets, move spectrum analyzer into waveform
This commit is contained in:
parent
a806e1a8b5
commit
3331b4cc16
9 changed files with 1126 additions and 395 deletions
|
|
@ -274,6 +274,23 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create
|
|||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
juce::ParameterID{"reverbMix", 1}, "Reverb Mix", zeroOne, 0.2f));
|
||||
|
||||
// ARPEGGIATOR
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
juce::ParameterID{"arpEnabled", 1}, "Arp On",
|
||||
juce::StringArray{"Off", "On"}, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
juce::ParameterID{"arpPattern", 1}, "Arp Pattern",
|
||||
juce::StringArray{"Up", "Down", "UpDown", "DownUp", "Random", "As Played", "Chord"}, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
juce::ParameterID{"arpOctaves", 1}, "Arp Octaves",
|
||||
juce::StringArray{"1", "2", "3"}, 1));
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
juce::ParameterID{"arpDirection", 1}, "Arp Direction",
|
||||
juce::StringArray{"Up", "Down"}, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
juce::ParameterID{"arpRate", 1}, "Arp Rate",
|
||||
juce::StringArray{"1/16", "1/8", "1/4", "1/2", "1", "2"}, 1));
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
|
|
@ -286,6 +303,9 @@ void ChromaFlockProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
|
|||
delay.prepare(sampleRate);
|
||||
reverbFX.prepare(sampleRate);
|
||||
autoPanPhase = 0.0f;
|
||||
arpSampleCount = 0.0;
|
||||
std::vector<int> arpStop;
|
||||
arp.reset(arpStop);
|
||||
updateVoiceParameters();
|
||||
}
|
||||
|
||||
|
|
@ -383,19 +403,85 @@ void ChromaFlockProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::
|
|||
buffer.clear();
|
||||
updateVoiceParameters();
|
||||
|
||||
for (const auto metadata : midiMessages)
|
||||
if (metadata.getMessage().isNoteOn())
|
||||
activeNotes[metadata.getMessage().getNoteNumber()].store(true, std::memory_order_relaxed);
|
||||
else if (metadata.getMessage().isNoteOff())
|
||||
activeNotes[metadata.getMessage().getNoteNumber()].store(false, std::memory_order_relaxed);
|
||||
auto getRaw = [&](const juce::String& id) -> float {
|
||||
auto* p = apvts.getRawParameterValue(id);
|
||||
return p != nullptr ? p->load() : 0.0f;
|
||||
};
|
||||
|
||||
bool arpOn = getRaw("arpEnabled") > 0.5f;
|
||||
if (arpOn) {
|
||||
static const double arpRateBeats[] = {0.25, 0.5, 1.0, 2.0, 4.0, 8.0};
|
||||
int rateIdx = juce::jlimit(0, 5, juce::roundToInt(getRaw("arpRate")));
|
||||
arp.setParameters(true,
|
||||
juce::roundToInt(getRaw("arpPattern")),
|
||||
juce::roundToInt(getRaw("arpOctaves")) + 1,
|
||||
juce::roundToInt(getRaw("arpDirection")),
|
||||
arpRateBeats[rateIdx],
|
||||
currentBpm);
|
||||
} else {
|
||||
arp.setParameters(false, 0, 1, 0, 0.5, currentBpm);
|
||||
}
|
||||
|
||||
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);
|
||||
if (msg.isNoteOn()) {
|
||||
int note = msg.getNoteNumber();
|
||||
activeNotes[note].store(true, std::memory_order_relaxed);
|
||||
if (arpOn)
|
||||
arp.noteOn(note, msg.getFloatVelocity());
|
||||
else {
|
||||
msg.setNoteNumber(juce::jlimit(0, 127, note + transpose));
|
||||
transposed.addEvent(msg, metadata.samplePosition);
|
||||
}
|
||||
} else if (msg.isNoteOff()) {
|
||||
int note = msg.getNoteNumber();
|
||||
activeNotes[note].store(false, std::memory_order_relaxed);
|
||||
if (arpOn)
|
||||
arp.noteOff(note);
|
||||
else {
|
||||
msg.setNoteNumber(juce::jlimit(0, 127, note + transpose));
|
||||
transposed.addEvent(msg, metadata.samplePosition);
|
||||
}
|
||||
} else {
|
||||
transposed.addEvent(msg, metadata.samplePosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (arpOn) {
|
||||
// A freshly pressed key triggers the first note immediately; the
|
||||
// clock then starts from zero so the subsequent ticks stay synced.
|
||||
if (arp.shouldTriggerNow()) {
|
||||
std::vector<int> stopNotes;
|
||||
std::vector<Arpeggiator::NotePitch> playNotes;
|
||||
arp.step(stopNotes, playNotes);
|
||||
for (int n : stopNotes)
|
||||
synth.noteOff(1, juce::jlimit(0, 127, n + transpose), 0.0f, true);
|
||||
for (const auto& np : playNotes)
|
||||
synth.noteOn(1, juce::jlimit(0, 127, np.note + transpose), np.velocity);
|
||||
arpSampleCount = 0.0;
|
||||
}
|
||||
|
||||
double tickSamples = arp.getTickSeconds() * currentSampleRate;
|
||||
if (tickSamples < 1.0) tickSamples = 1.0;
|
||||
arpSampleCount += buffer.getNumSamples();
|
||||
while (arpSampleCount >= tickSamples) {
|
||||
arpSampleCount -= tickSamples;
|
||||
std::vector<int> stopNotes;
|
||||
std::vector<Arpeggiator::NotePitch> playNotes;
|
||||
arp.step(stopNotes, playNotes);
|
||||
for (int n : stopNotes)
|
||||
synth.noteOff(1, juce::jlimit(0, 127, n + transpose), 0.0f, true);
|
||||
for (const auto& np : playNotes)
|
||||
synth.noteOn(1, juce::jlimit(0, 127, np.note + transpose), np.velocity);
|
||||
}
|
||||
} else {
|
||||
arpSampleCount = 0.0;
|
||||
std::vector<int> stopNotes;
|
||||
arp.reset(stopNotes);
|
||||
for (int n : stopNotes)
|
||||
synth.noteOff(1, juce::jlimit(0, 127, n + transpose), 0.0f, true);
|
||||
}
|
||||
|
||||
synth.renderNextBlock(buffer, transposed, 0, buffer.getNumSamples());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue