mirror of
https://codeberg.org/armin/monoslicer.git
synced 2026-09-01 04:10:45 +02:00
Initial commit
This commit is contained in:
commit
638cd05ea8
14 changed files with 2783 additions and 0 deletions
633
Source/PluginProcessor.cpp
Normal file
633
Source/PluginProcessor.cpp
Normal file
|
|
@ -0,0 +1,633 @@
|
|||
#include "PluginProcessor.h"
|
||||
#include "PluginEditor.h"
|
||||
|
||||
MonoSlicerProcessor::MonoSlicerProcessor()
|
||||
: AudioProcessor(BusesProperties()
|
||||
.withInput("Input", juce::AudioChannelSet::stereo(), true)
|
||||
.withOutput("Output", juce::AudioChannelSet::stereo(), true)),
|
||||
apvts(*this, nullptr, "Parameters", createParameterLayout())
|
||||
{
|
||||
formatManager.registerBasicFormats();
|
||||
}
|
||||
|
||||
MonoSlicerProcessor::~MonoSlicerProcessor() {}
|
||||
|
||||
juce::AudioProcessorValueTreeState::ParameterLayout MonoSlicerProcessor::createParameterLayout()
|
||||
{
|
||||
juce::AudioProcessorValueTreeState::ParameterLayout layout;
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"bass", "Bass", juce::NormalisableRange<float>(0.0f, 2.0f, 0.01f), 1.0f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"treble", "Treble", juce::NormalisableRange<float>(0.0f, 2.0f, 0.01f), 1.0f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"sensitivity", "Sensitivity", juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.5f));
|
||||
|
||||
// Amp ADSR
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"ampAttack", "Amp Attack", juce::NormalisableRange<float>(0.001f, 2.0f, 0.001f, 0.4f), 0.01f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"ampDecay", "Amp Decay", juce::NormalisableRange<float>(0.001f, 2.0f, 0.001f, 0.4f), 0.1f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"ampSustain", "Amp Sustain", juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.7f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"ampRelease", "Amp Release", juce::NormalisableRange<float>(0.001f, 5.0f, 0.001f, 0.4f), 0.2f));
|
||||
|
||||
// Filter
|
||||
juce::StringArray filterTypeNames { "LP12", "LP24", "HP", "BP", "Notch" };
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
"filterType", "Filter Type", filterTypeNames, 0));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"filterCutoff", "Filter Cutoff", juce::NormalisableRange<float>(20.0f, 20000.0f, 1.0f, 0.3f), 20000.0f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"filterReso", "Filter Reso", juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.0f));
|
||||
|
||||
// Filter ADSR
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"filterAttack", "Flt Attack", juce::NormalisableRange<float>(0.001f, 2.0f, 0.001f, 0.4f), 0.01f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"filterDecay", "Flt Decay", juce::NormalisableRange<float>(0.001f, 2.0f, 0.001f, 0.4f), 0.3f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"filterSustain", "Flt Sustain", juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.5f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"filterRelease", "Flt Release", juce::NormalisableRange<float>(0.001f, 5.0f, 0.001f, 0.4f), 0.5f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"filterEnvDepth", "Flt Env Depth", juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.0f));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterBool>(
|
||||
"selectViaMidi", "Select Via MIDI", false));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||
"stretchBpm", "Stretch BPM", juce::NormalisableRange<float>(20.0f, 300.0f, 1.0f), 120.0f));
|
||||
|
||||
juce::StringArray beatChoices { "1", "2", "4", "8", "16", "32" };
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
"stretchBeats", "Stretch Beats", beatChoices, 2));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterInt>(
|
||||
"keyShift", "Key Shift", -12, 12, 0));
|
||||
|
||||
juce::StringArray scaleChoices { "50%", "75%", "100%", "125%", "150%", "200%" };
|
||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||
"uiScale", "UI Scale", scaleChoices, 2));
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
|
||||
{
|
||||
currentSampleRate = sampleRate;
|
||||
juce::ignoreUnused(samplesPerBlock);
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::releaseResources() {}
|
||||
|
||||
void MonoSlicerProcessor::computeBiquadCoeffs(FilterType type, float cutoff, float resonance,
|
||||
float sampleRate, float& b0, float& b1, float& b2,
|
||||
float& a1, float& a2)
|
||||
{
|
||||
cutoff = juce::jlimit(20.0f, static_cast<float>(sampleRate * 0.45), cutoff);
|
||||
float Q = 0.5f + resonance * 19.5f; // Q range: 0.5 to 20
|
||||
float w0 = 2.0f * juce::MathConstants<float>::pi * cutoff / sampleRate;
|
||||
float cosW0 = std::cos(w0);
|
||||
float sinW0 = std::sin(w0);
|
||||
float alpha = sinW0 / (2.0f * Q);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case FilterType::LP12:
|
||||
{
|
||||
float norm = 1.0f / (1.0f + alpha);
|
||||
b0 = (1.0f - cosW0) * 0.5f * norm;
|
||||
b1 = (1.0f - cosW0) * norm;
|
||||
b2 = (1.0f - cosW0) * 0.5f * norm;
|
||||
a1 = -2.0f * cosW0 * norm;
|
||||
a2 = (1.0f - alpha) * norm;
|
||||
break;
|
||||
}
|
||||
case FilterType::LP24:
|
||||
{
|
||||
// Two cascaded LP12
|
||||
float norm = 1.0f / (1.0f + alpha);
|
||||
float tb0 = (1.0f - cosW0) * 0.5f * norm;
|
||||
float tb1 = (1.0f - cosW0) * norm;
|
||||
float tb2 = (1.0f - cosW0) * 0.5f * norm;
|
||||
float ta1 = -2.0f * cosW0 * norm;
|
||||
float ta2 = (1.0f - alpha) * norm;
|
||||
// Cascade: multiply two identical biquads
|
||||
b0 = tb0 * tb0;
|
||||
b1 = 2.0f * tb0 * tb1;
|
||||
b2 = 2.0f * tb0 * tb2 + tb1 * tb1;
|
||||
a1 = 2.0f * ta1 + ta1 * ta1 - 2.0f * ta2;
|
||||
a2 = ta2 * ta2 - ta1 * ta1 * ta2 + ta1 * ta1;
|
||||
// Simplified: just use LP12 coefficients with boosted resonance effect
|
||||
// For numerical stability, approximate LP24 as steeper LP12
|
||||
b0 = tb0; b1 = tb1; b2 = tb2; a1 = ta1; a2 = ta2;
|
||||
// Apply gain compensation for steeper rolloff feel
|
||||
float gainComp = 1.0f + Q * 0.1f;
|
||||
b0 *= gainComp; b1 *= gainComp; b2 *= gainComp;
|
||||
break;
|
||||
}
|
||||
case FilterType::HP:
|
||||
{
|
||||
float norm = 1.0f / (1.0f + alpha);
|
||||
b0 = (1.0f + cosW0) * 0.5f * norm;
|
||||
b1 = -(1.0f + cosW0) * norm;
|
||||
b2 = (1.0f + cosW0) * 0.5f * norm;
|
||||
a1 = -2.0f * cosW0 * norm;
|
||||
a2 = (1.0f - alpha) * norm;
|
||||
break;
|
||||
}
|
||||
case FilterType::BP:
|
||||
{
|
||||
float norm = 1.0f / (1.0f + alpha);
|
||||
b0 = alpha * norm;
|
||||
b1 = 0.0f;
|
||||
b2 = -alpha * norm;
|
||||
a1 = -2.0f * cosW0 * norm;
|
||||
a2 = (1.0f - alpha) * norm;
|
||||
break;
|
||||
}
|
||||
case FilterType::Notch:
|
||||
{
|
||||
float norm = 1.0f / (1.0f + alpha);
|
||||
b0 = norm;
|
||||
b1 = -2.0f * cosW0 * norm;
|
||||
b2 = norm;
|
||||
a1 = -2.0f * cosW0 * norm;
|
||||
a2 = (1.0f - alpha) * norm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::advanceAmpEnvelope(Voice& voice, int samples)
|
||||
{
|
||||
float attack = apvts.getRawParameterValue("ampAttack")->load();
|
||||
float decay = apvts.getRawParameterValue("ampDecay")->load();
|
||||
float sustain = apvts.getRawParameterValue("ampSustain")->load();
|
||||
float release = apvts.getRawParameterValue("ampRelease")->load();
|
||||
|
||||
int attackSamples = static_cast<int>(attack * currentSampleRate);
|
||||
int decaySamples = static_cast<int>(decay * currentSampleRate);
|
||||
int releaseSamples = static_cast<int>(release * currentSampleRate);
|
||||
if (attackSamples < 1) attackSamples = 1;
|
||||
if (decaySamples < 1) decaySamples = 1;
|
||||
if (releaseSamples < 1) releaseSamples = 1;
|
||||
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
switch (voice.ampStage)
|
||||
{
|
||||
case AmpStage::Attack:
|
||||
voice.ampEnv += 1.0f / static_cast<float>(attackSamples);
|
||||
if (voice.ampEnv >= 1.0f)
|
||||
{
|
||||
voice.ampEnv = 1.0f;
|
||||
voice.ampStage = AmpStage::Decay;
|
||||
voice.ampSamplesToNext = decaySamples;
|
||||
}
|
||||
break;
|
||||
|
||||
case AmpStage::Decay:
|
||||
voice.ampSamplesToNext--;
|
||||
if (voice.ampSamplesToNext <= 0)
|
||||
voice.ampStage = AmpStage::Sustain;
|
||||
else
|
||||
voice.ampEnv = 1.0f - (1.0f - sustain) * (1.0f - static_cast<float>(voice.ampSamplesToNext) / static_cast<float>(decaySamples));
|
||||
break;
|
||||
|
||||
case AmpStage::Sustain:
|
||||
voice.ampEnv = sustain;
|
||||
break;
|
||||
|
||||
case AmpStage::Release:
|
||||
voice.ampEnv -= 1.0f / static_cast<float>(releaseSamples);
|
||||
if (voice.ampEnv <= 0.0f)
|
||||
{
|
||||
voice.ampEnv = 0.0f;
|
||||
voice.ampStage = AmpStage::Idle;
|
||||
voice.active = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case AmpStage::Idle:
|
||||
voice.ampEnv = 0.0f;
|
||||
voice.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::advanceFilterEnvelope(Voice& voice, int samples)
|
||||
{
|
||||
float attack = apvts.getRawParameterValue("filterAttack")->load();
|
||||
float decay = apvts.getRawParameterValue("filterDecay")->load();
|
||||
float sustain = apvts.getRawParameterValue("filterSustain")->load();
|
||||
float release = apvts.getRawParameterValue("filterRelease")->load();
|
||||
|
||||
int attackSamples = static_cast<int>(attack * currentSampleRate);
|
||||
int decaySamples = static_cast<int>(decay * currentSampleRate);
|
||||
int releaseSamples = static_cast<int>(release * currentSampleRate);
|
||||
if (attackSamples < 1) attackSamples = 1;
|
||||
if (decaySamples < 1) decaySamples = 1;
|
||||
if (releaseSamples < 1) releaseSamples = 1;
|
||||
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
switch (voice.filterStage)
|
||||
{
|
||||
case AmpStage::Attack:
|
||||
voice.filterEnv += 1.0f / static_cast<float>(attackSamples);
|
||||
if (voice.filterEnv >= 1.0f)
|
||||
{
|
||||
voice.filterEnv = 1.0f;
|
||||
voice.filterStage = AmpStage::Decay;
|
||||
voice.filterSamplesToNext = decaySamples;
|
||||
}
|
||||
break;
|
||||
|
||||
case AmpStage::Decay:
|
||||
voice.filterSamplesToNext--;
|
||||
if (voice.filterSamplesToNext <= 0)
|
||||
voice.filterStage = AmpStage::Sustain;
|
||||
else
|
||||
voice.filterEnv = 1.0f - (1.0f - sustain) * (1.0f - static_cast<float>(voice.filterSamplesToNext) / static_cast<float>(decaySamples));
|
||||
break;
|
||||
|
||||
case AmpStage::Sustain:
|
||||
voice.filterEnv = sustain;
|
||||
break;
|
||||
|
||||
case AmpStage::Release:
|
||||
voice.filterEnv -= 1.0f / static_cast<float>(releaseSamples);
|
||||
if (voice.filterEnv <= 0.0f) voice.filterEnv = 0.0f;
|
||||
break;
|
||||
|
||||
case AmpStage::Idle:
|
||||
voice.filterEnv = 0.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
|
||||
{
|
||||
juce::ScopedNoDenormals noDenormals;
|
||||
buffer.clear();
|
||||
|
||||
if (auto* ph = getPlayHead())
|
||||
{
|
||||
auto posInfo = ph->getPosition();
|
||||
if (posInfo.hasValue())
|
||||
{
|
||||
auto bpmOpt = posInfo->getBpm();
|
||||
if (bpmOpt.hasValue() && *bpmOpt > 0)
|
||||
{
|
||||
float hostBpm = juce::jlimit(20.0f, 300.0f, static_cast<float>(*bpmOpt));
|
||||
if (auto* p = apvts.getParameter("stretchBpm"))
|
||||
p->setValueNotifyingHost(p->convertTo0to1(hostBpm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sliceManager.setSensitivity(apvts.getRawParameterValue("sensitivity")->load());
|
||||
sliceManager.setBassGain(apvts.getRawParameterValue("bass")->load());
|
||||
sliceManager.setTrebleGain(apvts.getRawParameterValue("treble")->load());
|
||||
|
||||
if (sampleBuffer.getNumSamples() == 0)
|
||||
return;
|
||||
|
||||
const int numSamples = buffer.getNumSamples();
|
||||
const int numOutputChannels = buffer.getNumChannels();
|
||||
|
||||
for (const auto metadata : midiMessages)
|
||||
{
|
||||
auto msg = metadata.getMessage();
|
||||
if (msg.isNoteOn())
|
||||
handleNoteOn(msg.getNoteNumber(), msg.getFloatVelocity());
|
||||
else if (msg.isNoteOff())
|
||||
handleNoteOff();
|
||||
}
|
||||
|
||||
for (auto& voice : voices)
|
||||
{
|
||||
if (!voice.active) continue;
|
||||
|
||||
int numSlices = sliceManager.getNumSlices();
|
||||
int startSample = 0;
|
||||
int endSample = sampleBuffer.getNumSamples();
|
||||
|
||||
if (numSlices > 0)
|
||||
{
|
||||
if (voice.sliceIndex < 0 || voice.sliceIndex >= numSlices)
|
||||
{
|
||||
voice.active = false;
|
||||
continue;
|
||||
}
|
||||
const auto& slice = sliceManager.getSlice(voice.sliceIndex);
|
||||
startSample = slice.startSample;
|
||||
endSample = slice.endSample;
|
||||
}
|
||||
|
||||
int sliceLength = endSample - startSample;
|
||||
if (sliceLength <= 0) { voice.active = false; continue; }
|
||||
|
||||
advanceAmpEnvelope(voice, numSamples);
|
||||
advanceFilterEnvelope(voice, numSamples);
|
||||
|
||||
if (!voice.active) continue;
|
||||
|
||||
int filterTypeIdx = static_cast<int>(apvts.getRawParameterValue("filterType")->load());
|
||||
FilterType filterType = static_cast<FilterType>(juce::jlimit(0, 4, filterTypeIdx));
|
||||
float baseCutoff = apvts.getRawParameterValue("filterCutoff")->load();
|
||||
float resonance = apvts.getRawParameterValue("filterReso")->load();
|
||||
float envDepth = apvts.getRawParameterValue("filterEnvDepth")->load();
|
||||
float minCutoff = 20.0f;
|
||||
float maxCutoff = juce::jmin(baseCutoff, static_cast<float>(currentSampleRate * 0.45));
|
||||
|
||||
float effectiveCutoff = maxCutoff - (maxCutoff - minCutoff) * envDepth * voice.filterEnv;
|
||||
effectiveCutoff = juce::jlimit(20.0f, maxCutoff, effectiveCutoff);
|
||||
|
||||
float b0, b1, b2, a1, a2;
|
||||
computeBiquadCoeffs(filterType, effectiveCutoff, resonance, static_cast<float>(currentSampleRate),
|
||||
b0, b1, b2, a1, a2);
|
||||
|
||||
// Key shift: pitch factor = 2^(semitones/12)
|
||||
int keyShift = static_cast<int>(apvts.getRawParameterValue("keyShift")->load());
|
||||
float pitchFactor = std::pow(2.0f, static_cast<float>(keyShift) / 12.0f);
|
||||
|
||||
const int bufSamples = sampleBuffer.getNumSamples();
|
||||
|
||||
bool filterActive = (envDepth > 0.001f)
|
||||
|| (baseCutoff < static_cast<float>(currentSampleRate * 0.45) - 1.0f)
|
||||
|| (resonance > 0.001f);
|
||||
|
||||
float startPos = voice.position;
|
||||
float endPos = startPos;
|
||||
|
||||
for (int ch = 0; ch < numOutputChannels; ++ch)
|
||||
{
|
||||
float* outData = buffer.getWritePointer(ch);
|
||||
auto& filt = (ch == 0) ? voice.filterL : voice.filterR;
|
||||
if (filterActive)
|
||||
{
|
||||
filt.b0 = b0; filt.b1 = b1; filt.b2 = b2;
|
||||
filt.a1 = a1; filt.a2 = a2;
|
||||
}
|
||||
|
||||
float chPos = startPos;
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
if (chPos >= static_cast<float>(sliceLength))
|
||||
break;
|
||||
|
||||
int idx = static_cast<int>(chPos);
|
||||
float frac = chPos - static_cast<float>(idx);
|
||||
|
||||
int sampleIdx = startSample + idx;
|
||||
if (sampleIdx >= bufSamples)
|
||||
break;
|
||||
|
||||
int bufCh = juce::jmin(ch, sampleBuffer.getNumChannels() - 1);
|
||||
float s0 = sampleBuffer.getSample(bufCh, sampleIdx);
|
||||
float s1 = (sampleIdx + 1 < bufSamples) ? sampleBuffer.getSample(bufCh, sampleIdx + 1) : s0;
|
||||
float dry = (s0 + frac * (s1 - s0)) * voice.velocity;
|
||||
|
||||
float output;
|
||||
if (filterActive)
|
||||
output = filt.process(dry) * voice.ampEnv;
|
||||
else
|
||||
output = dry * voice.ampEnv;
|
||||
|
||||
outData[i] += output;
|
||||
chPos += pitchFactor;
|
||||
}
|
||||
endPos = chPos;
|
||||
}
|
||||
voice.position = endPos;
|
||||
|
||||
if (voice.position >= static_cast<float>(sliceLength))
|
||||
{
|
||||
voice.ampStage = AmpStage::Release;
|
||||
voice.filterStage = AmpStage::Release;
|
||||
voice.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
currentActiveSlice.store(-1);
|
||||
for (auto& voice : voices)
|
||||
{
|
||||
if (voice.active)
|
||||
{
|
||||
int numSlices = sliceManager.getNumSlices();
|
||||
int baseStart = 0;
|
||||
if (numSlices > 0 && voice.sliceIndex >= 0 && voice.sliceIndex < numSlices)
|
||||
baseStart = sliceManager.getSlice(voice.sliceIndex).startSample;
|
||||
currentPlaybackSample.store(baseStart + static_cast<int>(voice.position));
|
||||
currentActiveSlice.store(voice.sliceIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentActiveSlice.load() < 0)
|
||||
currentPlaybackSample.store(-1);
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::handleNoteOn(int noteNumber, float velocity)
|
||||
{
|
||||
int sliceIdx = noteNumber - 60;
|
||||
sliceIdx = juce::jlimit(0, juce::jmax(0, sliceManager.getNumSlices() - 1), sliceIdx);
|
||||
|
||||
if (apvts.getRawParameterValue("selectViaMidi")->load() > 0.5f)
|
||||
selectedSlice.store(sliceIdx);
|
||||
|
||||
for (auto& voice : voices)
|
||||
{
|
||||
if (!voice.active)
|
||||
{
|
||||
voice.active = true;
|
||||
voice.sliceIndex = sliceIdx;
|
||||
voice.position = 0;
|
||||
voice.velocity = velocity;
|
||||
|
||||
voice.ampStage = AmpStage::Attack;
|
||||
voice.ampEnv = 0.0f;
|
||||
|
||||
voice.filterStage = AmpStage::Attack;
|
||||
voice.filterEnv = 0.0f;
|
||||
|
||||
voice.filterL.reset();
|
||||
voice.filterR.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::handleNoteOff()
|
||||
{
|
||||
for (auto& voice : voices)
|
||||
{
|
||||
if (voice.active && voice.ampStage != AmpStage::Release && voice.ampStage != AmpStage::Idle)
|
||||
{
|
||||
voice.ampStage = AmpStage::Release;
|
||||
voice.filterStage = AmpStage::Release;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::loadAudioFile(const juce::File& file)
|
||||
{
|
||||
std::unique_ptr<juce::AudioFormatReader> reader(formatManager.createReaderFor(file));
|
||||
if (reader)
|
||||
{
|
||||
int numSamples = static_cast<int>(reader->lengthInSamples);
|
||||
sampleBuffer.setSize(static_cast<int>(reader->numChannels), numSamples);
|
||||
reader->read(&sampleBuffer, 0, numSamples, 0, true, true);
|
||||
loadedSampleRate = reader->sampleRate;
|
||||
currentFile = file;
|
||||
refreshFolderList();
|
||||
sliceManager.setSampleBuffer(&sampleBuffer, loadedSampleRate);
|
||||
selectedSlice.store(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::refreshFolderList()
|
||||
{
|
||||
folderFiles.clear();
|
||||
currentFileIndex = -1;
|
||||
|
||||
if (!currentFile.existsAsFile()) return;
|
||||
|
||||
auto dir = currentFile.getParentDirectory();
|
||||
juce::DirectoryIterator iter(dir, false, "*.wav;*.aiff;*.flac;*.ogg");
|
||||
while (iter.next())
|
||||
{
|
||||
auto f = iter.getFile();
|
||||
if (f.existsAsFile())
|
||||
{
|
||||
folderFiles.add(f);
|
||||
if (f == currentFile)
|
||||
currentFileIndex = folderFiles.size() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::loadNextSample()
|
||||
{
|
||||
if (folderFiles.isEmpty()) return;
|
||||
int nextIdx = (currentFileIndex + 1) % folderFiles.size();
|
||||
loadAudioFile(folderFiles[nextIdx]);
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::loadPreviousSample()
|
||||
{
|
||||
if (folderFiles.isEmpty()) return;
|
||||
int prevIdx = (currentFileIndex - 1 + folderFiles.size()) % folderFiles.size();
|
||||
loadAudioFile(folderFiles[prevIdx]);
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::triggerSlice(int sliceIndex, float velocity)
|
||||
{
|
||||
int noteNumber = sliceIndex + 60;
|
||||
handleNoteOn(noteNumber, velocity);
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::releaseSlice()
|
||||
{
|
||||
handleNoteOff();
|
||||
}
|
||||
|
||||
juce::AudioProcessorEditor* MonoSlicerProcessor::createEditor()
|
||||
{
|
||||
return new MonoSlicerEditor(*this);
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::getStateInformation(juce::MemoryBlock& destData)
|
||||
{
|
||||
auto state = apvts.copyState();
|
||||
juce::MemoryOutputStream stream(destData, false);
|
||||
state.writeToStream(stream);
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::setStateInformation(const void* data, int sizeInBytes)
|
||||
{
|
||||
auto stream = juce::MemoryInputStream(data, static_cast<size_t>(sizeInBytes), false);
|
||||
auto state = juce::ValueTree::readFromStream(stream);
|
||||
if (state.isValid())
|
||||
apvts.replaceState(state);
|
||||
}
|
||||
|
||||
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
||||
{
|
||||
return new MonoSlicerProcessor();
|
||||
}
|
||||
|
||||
void MonoSlicerProcessor::stretchToBeats(int numBeats, float bpm)
|
||||
{
|
||||
if (sampleBuffer.getNumSamples() == 0 || bpm <= 0.0f || numBeats <= 0) return;
|
||||
|
||||
double sr = static_cast<double>(loadedSampleRate);
|
||||
int currentLength = sampleBuffer.getNumSamples();
|
||||
double currentDuration = static_cast<double>(currentLength) / sr;
|
||||
double targetDuration = static_cast<double>(numBeats) * 60.0 / static_cast<double>(bpm);
|
||||
double ratio = targetDuration / currentDuration;
|
||||
|
||||
if (std::abs(ratio - 1.0) < 0.001) return;
|
||||
|
||||
int newLength = static_cast<int>(static_cast<double>(currentLength) * ratio);
|
||||
if (newLength < 1) return;
|
||||
|
||||
const int numChannels = sampleBuffer.getNumChannels();
|
||||
|
||||
// Save slices before setSampleBuffer clears them
|
||||
auto savedSlices = sliceManager.getSlices();
|
||||
|
||||
juce::AudioBuffer<float> newBuffer(numChannels, newLength);
|
||||
for (int ch = 0; ch < numChannels; ++ch)
|
||||
{
|
||||
const float* inData = sampleBuffer.getReadPointer(ch);
|
||||
float* outData = newBuffer.getWritePointer(ch);
|
||||
|
||||
for (int i = 0; i < newLength; ++i)
|
||||
{
|
||||
double srcPos = static_cast<double>(i) / ratio;
|
||||
int idx = static_cast<int>(srcPos);
|
||||
float frac = static_cast<float>(srcPos - static_cast<double>(idx));
|
||||
if (idx >= currentLength - 1)
|
||||
{
|
||||
outData[i] = inData[currentLength - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
outData[i] = inData[idx] * (1.0f - frac) + inData[idx + 1] * frac;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sampleBuffer.setSize(numChannels, newLength, false, false, false);
|
||||
for (int ch = 0; ch < numChannels; ++ch)
|
||||
sampleBuffer.copyFrom(ch, 0, newBuffer, ch, 0, newLength);
|
||||
|
||||
sliceManager.setSampleBuffer(&sampleBuffer, loadedSampleRate);
|
||||
|
||||
for (const auto& s : savedSlices)
|
||||
{
|
||||
int newStart = static_cast<int>(static_cast<double>(s.startSample) * ratio);
|
||||
int newEnd = static_cast<int>(static_cast<double>(s.endSample) * ratio);
|
||||
if (newEnd > newStart + 100)
|
||||
sliceManager.addSliceManual(newStart);
|
||||
}
|
||||
|
||||
selectedSlice.store(-1);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue