mirror of
https://codeberg.org/armin/monoslicer.git
synced 2026-09-01 04:10:45 +02:00
Fix LP24 filter with proper 2-stage cascade, thread-safe SliceManager, improve state save/load
This commit is contained in:
parent
73c4095139
commit
ac6f17d9ae
4 changed files with 109 additions and 58 deletions
|
|
@ -121,25 +121,13 @@ void MonoSlicerProcessor::computeBiquadCoeffs(FilterType type, float cutoff, flo
|
||||||
}
|
}
|
||||||
case FilterType::LP24:
|
case FilterType::LP24:
|
||||||
{
|
{
|
||||||
// Two cascaded LP12
|
// Single LP12 stage; cascaded in processBlock via filterL2/filterR2
|
||||||
float norm = 1.0f / (1.0f + alpha);
|
float norm = 1.0f / (1.0f + alpha);
|
||||||
float tb0 = (1.0f - cosW0) * 0.5f * norm;
|
b0 = (1.0f - cosW0) * 0.5f * norm;
|
||||||
float tb1 = (1.0f - cosW0) * norm;
|
b1 = (1.0f - cosW0) * norm;
|
||||||
float tb2 = (1.0f - cosW0) * 0.5f * norm;
|
b2 = (1.0f - cosW0) * 0.5f * norm;
|
||||||
float ta1 = -2.0f * cosW0 * norm;
|
a1 = -2.0f * cosW0 * norm;
|
||||||
float ta2 = (1.0f - alpha) * norm;
|
a2 = (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;
|
break;
|
||||||
}
|
}
|
||||||
case FilterType::HP:
|
case FilterType::HP:
|
||||||
|
|
@ -384,10 +372,16 @@ void MonoSlicerProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::M
|
||||||
{
|
{
|
||||||
float* outData = buffer.getWritePointer(ch);
|
float* outData = buffer.getWritePointer(ch);
|
||||||
auto& filt = (ch == 0) ? voice.filterL : voice.filterR;
|
auto& filt = (ch == 0) ? voice.filterL : voice.filterR;
|
||||||
|
auto& filt2 = (ch == 0) ? voice.filterL2 : voice.filterR2;
|
||||||
if (filterActive)
|
if (filterActive)
|
||||||
{
|
{
|
||||||
filt.b0 = b0; filt.b1 = b1; filt.b2 = b2;
|
filt.b0 = b0; filt.b1 = b1; filt.b2 = b2;
|
||||||
filt.a1 = a1; filt.a2 = a2;
|
filt.a1 = a1; filt.a2 = a2;
|
||||||
|
if (filterType == FilterType::LP24)
|
||||||
|
{
|
||||||
|
filt2.b0 = b0; filt2.b1 = b1; filt2.b2 = b2;
|
||||||
|
filt2.a1 = a1; filt2.a2 = a2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float chPos = startPos;
|
float chPos = startPos;
|
||||||
|
|
@ -410,7 +404,12 @@ void MonoSlicerProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::M
|
||||||
|
|
||||||
float output;
|
float output;
|
||||||
if (filterActive)
|
if (filterActive)
|
||||||
output = filt.process(dry) * voice.ampEnv;
|
{
|
||||||
|
output = filt.process(dry);
|
||||||
|
if (filterType == FilterType::LP24)
|
||||||
|
output = filt2.process(output);
|
||||||
|
output *= voice.ampEnv;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
output = dry * voice.ampEnv;
|
output = dry * voice.ampEnv;
|
||||||
|
|
||||||
|
|
@ -473,6 +472,8 @@ void MonoSlicerProcessor::handleNoteOn(int noteNumber, float velocity)
|
||||||
|
|
||||||
voice.filterL.reset();
|
voice.filterL.reset();
|
||||||
voice.filterR.reset();
|
voice.filterR.reset();
|
||||||
|
voice.filterL2.reset();
|
||||||
|
voice.filterR2.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -573,24 +574,22 @@ void MonoSlicerProcessor::getStateInformation(juce::MemoryBlock& destData)
|
||||||
state.setProperty("sliceStarts", sliceStarts, nullptr);
|
state.setProperty("sliceStarts", sliceStarts, nullptr);
|
||||||
state.setProperty("sliceEnds", sliceEnds, nullptr);
|
state.setProperty("sliceEnds", sliceEnds, nullptr);
|
||||||
|
|
||||||
|
juce::MemoryOutputStream stream(destData, false);
|
||||||
|
state.writeToStream(stream);
|
||||||
|
|
||||||
|
// Append raw audio data after the ValueTree
|
||||||
if (sampleBuffer.getNumSamples() > 0)
|
if (sampleBuffer.getNumSamples() > 0)
|
||||||
{
|
{
|
||||||
int numChannels = sampleBuffer.getNumChannels();
|
int numChannels = sampleBuffer.getNumChannels();
|
||||||
int numSamples = sampleBuffer.getNumSamples();
|
int numSamples = sampleBuffer.getNumSamples();
|
||||||
|
int totalFloats = numChannels * numSamples;
|
||||||
|
|
||||||
juce::MemoryOutputStream audioStream;
|
stream.writeInt(numChannels);
|
||||||
audioStream.writeInt(numChannels);
|
stream.writeInt(numSamples);
|
||||||
audioStream.writeInt(numSamples);
|
|
||||||
for (int ch = 0; ch < numChannels; ++ch)
|
for (int ch = 0; ch < numChannels; ++ch)
|
||||||
audioStream.write(sampleBuffer.getReadPointer(ch),
|
stream.write(sampleBuffer.getReadPointer(ch),
|
||||||
static_cast<size_t>(numSamples) * sizeof(float));
|
static_cast<size_t>(numSamples) * sizeof(float));
|
||||||
|
|
||||||
audioStream.flush();
|
|
||||||
state.setProperty("audioData", juce::var(juce::MemoryBlock(audioStream.getData(), audioStream.getDataSize())), nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
juce::MemoryOutputStream stream(destData, false);
|
|
||||||
state.writeToStream(stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MonoSlicerProcessor::setStateInformation(const void* data, int sizeInBytes)
|
void MonoSlicerProcessor::setStateInformation(const void* data, int sizeInBytes)
|
||||||
|
|
@ -603,29 +602,32 @@ void MonoSlicerProcessor::setStateInformation(const void* data, int sizeInBytes)
|
||||||
|
|
||||||
loadedSampleRate = static_cast<double>(state.getProperty("sampleRate", 44100.0));
|
loadedSampleRate = static_cast<double>(state.getProperty("sampleRate", 44100.0));
|
||||||
|
|
||||||
auto audioVar = state.getProperty("audioData", {});
|
// Try to read embedded audio data appended after the ValueTree
|
||||||
auto* audioBlock = audioVar.getBinaryData();
|
bool loadedFromEmbedded = false;
|
||||||
if (audioBlock != nullptr && audioBlock->getSize() > 0)
|
if (stream.getTotalLength() - stream.getPosition() > 8)
|
||||||
{
|
{
|
||||||
const auto* buf = static_cast<const uint8_t*>(audioBlock->getData());
|
int numChannels = stream.readInt();
|
||||||
size_t offset = 0;
|
int numSamples = stream.readInt();
|
||||||
|
int totalFloats = numChannels * numSamples;
|
||||||
int numChannels = juce::ByteOrder::bigEndianInt(buf + offset); offset += 4;
|
size_t expectedBytes = static_cast<size_t>(totalFloats) * sizeof(float);
|
||||||
int numSamples = juce::ByteOrder::bigEndianInt(buf + offset); offset += 4;
|
|
||||||
|
|
||||||
|
if (numChannels > 0 && numChannels <= 8 && numSamples > 0
|
||||||
|
&& static_cast<size_t>(stream.getTotalLength() - stream.getPosition()) >= expectedBytes)
|
||||||
|
{
|
||||||
sampleBuffer.setSize(numChannels, numSamples, false, false, false);
|
sampleBuffer.setSize(numChannels, numSamples, false, false, false);
|
||||||
for (int ch = 0; ch < numChannels; ++ch)
|
for (int ch = 0; ch < numChannels; ++ch)
|
||||||
{
|
stream.read(sampleBuffer.getWritePointer(ch),
|
||||||
std::memcpy(sampleBuffer.getWritePointer(ch), buf + offset,
|
|
||||||
static_cast<size_t>(numSamples) * sizeof(float));
|
static_cast<size_t>(numSamples) * sizeof(float));
|
||||||
offset += static_cast<size_t>(numSamples) * sizeof(float);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFile = juce::File(state.getProperty("filePath", "").toString());
|
currentFile = juce::File(state.getProperty("filePath", "").toString());
|
||||||
refreshFolderList();
|
refreshFolderList();
|
||||||
sliceManager.setSampleBuffer(&sampleBuffer, loadedSampleRate);
|
sliceManager.setSampleBuffer(&sampleBuffer, loadedSampleRate);
|
||||||
|
loadedFromEmbedded = true;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
|
// Fallback: load from file path
|
||||||
|
if (!loadedFromEmbedded)
|
||||||
{
|
{
|
||||||
juce::String path = state.getProperty("filePath", "").toString();
|
juce::String path = state.getProperty("filePath", "").toString();
|
||||||
if (path.isNotEmpty())
|
if (path.isNotEmpty())
|
||||||
|
|
@ -637,16 +639,20 @@ void MonoSlicerProcessor::setStateInformation(const void* data, int sizeInBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sliceStarts = state.getProperty("sliceStarts", {});
|
auto sliceStarts = state.getProperty("sliceStarts", {});
|
||||||
if (sliceStarts.isArray())
|
auto sliceEnds = state.getProperty("sliceEnds", {});
|
||||||
|
if (sliceStarts.isArray() && sliceEnds.isArray())
|
||||||
{
|
{
|
||||||
auto* starts = sliceStarts.getArray();
|
auto* starts = sliceStarts.getArray();
|
||||||
int numSavedSlices = starts->size();
|
auto* ends = sliceEnds.getArray();
|
||||||
|
int n = starts->size();
|
||||||
if (numSavedSlices > 0)
|
if (n > 0 && ends->size() == n)
|
||||||
{
|
{
|
||||||
sliceManager.clearSlices();
|
std::vector<SliceManager::Slice> restored;
|
||||||
for (int i = 1; i < numSavedSlices; ++i)
|
restored.reserve(static_cast<size_t>(n));
|
||||||
sliceManager.addSliceManual(static_cast<int>((*starts)[i]));
|
for (int i = 0; i < n; ++i)
|
||||||
|
restored.push_back({ static_cast<int>((*starts)[i]),
|
||||||
|
static_cast<int>((*ends)[i]), 0 });
|
||||||
|
sliceManager.setSlices(restored);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ private:
|
||||||
int filterSamplesToNext = 0;
|
int filterSamplesToNext = 0;
|
||||||
|
|
||||||
BiquadState filterL, filterR;
|
BiquadState filterL, filterR;
|
||||||
|
BiquadState filterL2, filterR2;
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr int maxVoices = 16;
|
static constexpr int maxVoices = 16;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,29 @@
|
||||||
|
|
||||||
SliceManager::SliceManager() = default;
|
SliceManager::SliceManager() = default;
|
||||||
|
|
||||||
|
std::vector<SliceManager::Slice> SliceManager::getSlices() const
|
||||||
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
|
return slices;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SliceManager::getNumSlices() const
|
||||||
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
|
return static_cast<int>(slices.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
SliceManager::Slice SliceManager::getSlice(int index) const
|
||||||
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
|
if (index < 0 || index >= static_cast<int>(slices.size()))
|
||||||
|
return { 0, 0, 0 };
|
||||||
|
return slices[static_cast<size_t>(index)];
|
||||||
|
}
|
||||||
|
|
||||||
void SliceManager::setSampleBuffer(juce::AudioBuffer<float>* buffer, double sampleRate)
|
void SliceManager::setSampleBuffer(juce::AudioBuffer<float>* buffer, double sampleRate)
|
||||||
{
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
sampleBuffer = buffer;
|
sampleBuffer = buffer;
|
||||||
currentSampleRate = sampleRate;
|
currentSampleRate = sampleRate;
|
||||||
slices.clear();
|
slices.clear();
|
||||||
|
|
@ -15,6 +36,7 @@ void SliceManager::setTrebleGain(float g) { trebleGain = juce::jlimit(0.0f, 2.
|
||||||
|
|
||||||
void SliceManager::autoSlice()
|
void SliceManager::autoSlice()
|
||||||
{
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
if (!sampleBuffer || sampleBuffer->getNumSamples() == 0)
|
if (!sampleBuffer || sampleBuffer->getNumSamples() == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -83,12 +105,21 @@ void SliceManager::autoSlice()
|
||||||
|
|
||||||
void SliceManager::clearSlices()
|
void SliceManager::clearSlices()
|
||||||
{
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
pushHistory();
|
pushHistory();
|
||||||
slices.clear();
|
slices.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SliceManager::setSlices(const std::vector<Slice>& newSlices)
|
||||||
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
|
slices = newSlices;
|
||||||
|
assignMidiNotes();
|
||||||
|
}
|
||||||
|
|
||||||
void SliceManager::addSliceManual(int samplePos)
|
void SliceManager::addSliceManual(int samplePos)
|
||||||
{
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
if (!sampleBuffer) return;
|
if (!sampleBuffer) return;
|
||||||
const int numSamples = sampleBuffer->getNumSamples();
|
const int numSamples = sampleBuffer->getNumSamples();
|
||||||
samplePos = juce::jlimit(1, numSamples - 1, samplePos);
|
samplePos = juce::jlimit(1, numSamples - 1, samplePos);
|
||||||
|
|
@ -127,13 +158,18 @@ void SliceManager::addSliceManual(int samplePos)
|
||||||
|
|
||||||
void SliceManager::removeSliceAt(int samplePos, int tolerance)
|
void SliceManager::removeSliceAt(int samplePos, int tolerance)
|
||||||
{
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
for (auto it = slices.begin(); it != slices.end(); ++it)
|
for (auto it = slices.begin(); it != slices.end(); ++it)
|
||||||
{
|
{
|
||||||
int dist = std::abs(it->startSample - samplePos);
|
int dist = std::abs(it->startSample - samplePos);
|
||||||
if (dist <= tolerance)
|
if (dist <= tolerance)
|
||||||
{
|
{
|
||||||
pushHistory();
|
pushHistory();
|
||||||
|
int removedEnd = it->endSample;
|
||||||
|
auto idx = static_cast<int>(std::distance(slices.begin(), it));
|
||||||
slices.erase(it);
|
slices.erase(it);
|
||||||
|
if (idx > 0 && idx - 1 < static_cast<int>(slices.size()))
|
||||||
|
slices[static_cast<size_t>(idx - 1)].endSample = removedEnd;
|
||||||
assignMidiNotes();
|
assignMidiNotes();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -142,6 +178,7 @@ void SliceManager::removeSliceAt(int samplePos, int tolerance)
|
||||||
|
|
||||||
void SliceManager::moveSlice(int fromSample, int toSample)
|
void SliceManager::moveSlice(int fromSample, int toSample)
|
||||||
{
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
for (size_t i = 0; i < slices.size(); ++i)
|
for (size_t i = 0; i < slices.size(); ++i)
|
||||||
{
|
{
|
||||||
if (std::abs(slices[i].startSample - fromSample) < 10)
|
if (std::abs(slices[i].startSample - fromSample) < 10)
|
||||||
|
|
@ -160,6 +197,7 @@ void SliceManager::moveSlice(int fromSample, int toSample)
|
||||||
|
|
||||||
void SliceManager::trimStart(int sample)
|
void SliceManager::trimStart(int sample)
|
||||||
{
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
if (!sampleBuffer || sampleBuffer->getNumSamples() == 0) return;
|
if (!sampleBuffer || sampleBuffer->getNumSamples() == 0) return;
|
||||||
sample = juce::jlimit(0, sampleBuffer->getNumSamples() - 1, sample);
|
sample = juce::jlimit(0, sampleBuffer->getNumSamples() - 1, sample);
|
||||||
if (sample <= 0) return;
|
if (sample <= 0) return;
|
||||||
|
|
@ -199,6 +237,7 @@ void SliceManager::trimStart(int sample)
|
||||||
|
|
||||||
void SliceManager::trimEnd(int sample)
|
void SliceManager::trimEnd(int sample)
|
||||||
{
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
if (!sampleBuffer || sampleBuffer->getNumSamples() == 0) return;
|
if (!sampleBuffer || sampleBuffer->getNumSamples() == 0) return;
|
||||||
const int numSamples = sampleBuffer->getNumSamples();
|
const int numSamples = sampleBuffer->getNumSamples();
|
||||||
sample = juce::jlimit(0, numSamples, sample);
|
sample = juce::jlimit(0, numSamples, sample);
|
||||||
|
|
@ -341,6 +380,7 @@ void SliceManager::truncateHistory()
|
||||||
|
|
||||||
void SliceManager::undo()
|
void SliceManager::undo()
|
||||||
{
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
if (undoStack.empty()) return;
|
if (undoStack.empty()) return;
|
||||||
redoStack.push_back(slices);
|
redoStack.push_back(slices);
|
||||||
slices = undoStack.back();
|
slices = undoStack.back();
|
||||||
|
|
@ -349,6 +389,7 @@ void SliceManager::undo()
|
||||||
|
|
||||||
void SliceManager::redo()
|
void SliceManager::redo()
|
||||||
{
|
{
|
||||||
|
const juce::CriticalSection::ScopedLockType lock(mutex);
|
||||||
if (redoStack.empty()) return;
|
if (redoStack.empty()) return;
|
||||||
undoStack.push_back(slices);
|
undoStack.push_back(slices);
|
||||||
slices = redoStack.back();
|
slices = redoStack.back();
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,11 @@ public:
|
||||||
bool canUndo() const;
|
bool canUndo() const;
|
||||||
bool canRedo() const;
|
bool canRedo() const;
|
||||||
|
|
||||||
const std::vector<Slice>& getSlices() const { return slices; }
|
std::vector<Slice> getSlices() const;
|
||||||
int getNumSlices() const { return static_cast<int>(slices.size()); }
|
int getNumSlices() const;
|
||||||
const Slice& getSlice(int index) const { return slices[static_cast<size_t>(index)]; }
|
Slice getSlice(int index) const;
|
||||||
|
|
||||||
|
void setSlices(const std::vector<Slice>& newSlices);
|
||||||
|
|
||||||
int findSliceIndexForSample(int samplePos) const;
|
int findSliceIndexForSample(int samplePos) const;
|
||||||
|
|
||||||
|
|
@ -50,6 +52,7 @@ private:
|
||||||
void pushHistory();
|
void pushHistory();
|
||||||
void truncateHistory();
|
void truncateHistory();
|
||||||
|
|
||||||
|
mutable juce::CriticalSection mutex;
|
||||||
std::vector<Slice> slices;
|
std::vector<Slice> slices;
|
||||||
std::vector<std::vector<Slice>> undoStack;
|
std::vector<std::vector<Slice>> undoStack;
|
||||||
std::vector<std::vector<Slice>> redoStack;
|
std::vector<std::vector<Slice>> redoStack;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue