mirror of
https://codeberg.org/armin/justasample.git
synced 2026-09-01 04:10:48 +02:00
init
This commit is contained in:
commit
d21bc831e1
178 changed files with 24136 additions and 0 deletions
525
Source/Components/SampleNavigator.cpp
Normal file
525
Source/Components/SampleNavigator.cpp
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
SampleNavigator.cpp
|
||||
Created: 19 Sep 2023 4:41:12pm
|
||||
Author: binya
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include <JuceHeader.h>
|
||||
|
||||
#include "SampleNavigator.h"
|
||||
|
||||
SampleNavigator::SampleNavigator(APVTS& apvts, PluginParameters::State& pluginState, const juce::OwnedArray<CustomSamplerVoice>& synthVoices) :
|
||||
apvts(apvts), state(pluginState), dummyParam(apvts, PluginParameters::State::UI_DUMMY_PARAM),
|
||||
painter(pluginState.primaryChannel, 0.2f),
|
||||
gainAttachment(*apvts.getParameter(PluginParameters::SAMPLE_GAIN), [this](float newValue) { painter.setGain(juce::Decibels::decibelsToGain(newValue)); }, apvts.undoManager),
|
||||
monoAttachment(*apvts.getParameter(PluginParameters::MONO_OUTPUT), [this](bool newValue) { painter.setMono(newValue); }, apvts.undoManager),
|
||||
synthVoices(synthVoices),
|
||||
isWavetableModeDisabled(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::DISABLE_WAVETABLE_MODE))),
|
||||
isLooping(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::IS_LOOPING))),
|
||||
loopHasStart(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::LOOPING_HAS_START))),
|
||||
loopHasEnd(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::LOOPING_HAS_END))),
|
||||
isWavetableModeDisabledAttachment(*this->isWavetableModeDisabled, [this](bool) { repaint(); }, apvts.undoManager),
|
||||
loopAttachment(*isLooping, [this](bool newValue) { loopHasStartUpdate(newValue && loopHasStart->get()); loopHasEndUpdate(newValue && loopHasEnd->get()); }, apvts.undoManager),
|
||||
loopStartAttachment(*loopHasStart, [this](bool newValue) { loopHasStartUpdate(newValue); }, apvts.undoManager),
|
||||
loopEndAttachment(*loopHasEnd, [this](bool newValue) { loopHasEndUpdate(newValue); }, apvts.undoManager)
|
||||
{
|
||||
state.viewStart.addListener(this);
|
||||
state.viewEnd.addListener(this);
|
||||
state.sampleStart.addListener(this);
|
||||
state.sampleEnd.addListener(this);
|
||||
state.loopStart.addListener(this);
|
||||
state.loopEnd.addListener(this);
|
||||
state.pinView.addListener(this);
|
||||
|
||||
updatePinnedPositions();
|
||||
|
||||
painter.setGain(juce::Decibels::decibelsToGain(float(apvts.getParameterAsValue(PluginParameters::SAMPLE_GAIN).getValue())));
|
||||
addAndMakeVisible(&painter);
|
||||
|
||||
painter.setInterceptsMouseClicks(false, false);
|
||||
}
|
||||
|
||||
SampleNavigator::~SampleNavigator()
|
||||
{
|
||||
state.viewStart.removeListener(this);
|
||||
state.viewEnd.removeListener(this);
|
||||
state.sampleStart.removeListener(this);
|
||||
state.sampleEnd.removeListener(this);
|
||||
state.loopStart.removeListener(this);
|
||||
state.loopEnd.removeListener(this);
|
||||
state.pinView.removeListener(this);
|
||||
}
|
||||
|
||||
void SampleNavigator::valueChanged(ListenableValue<int>& source, int /*newValue*/)
|
||||
{
|
||||
auto& sourceA = dynamic_cast<ListenableAtomic<int>&>(source);
|
||||
if (!navigatorUpdate && (sourceA == state.loopStart || sourceA == state.loopEnd || sourceA == state.sampleStart || sourceA == state.sampleEnd))
|
||||
updatePinnedPositions();
|
||||
|
||||
safeRepaint();
|
||||
}
|
||||
|
||||
void SampleNavigator::valueChanged(ListenableValue<bool>& source, bool newValue)
|
||||
{
|
||||
// When the pin is enabled, we move the bounds to within the viewport
|
||||
updatePinnedPositions();
|
||||
|
||||
bool loopingHasStart = isLooping->get() && loopHasStart->get() && !isWaveformMode();
|
||||
bool loopingHasEnd = isLooping->get() && loopHasEnd->get() && !isWaveformMode();
|
||||
|
||||
if (state.pinView && dynamic_cast<ListenableAtomic<bool>&>(source) == state.pinView && newValue &&
|
||||
((loopingHasStart && state.loopStart < state.viewStart) || (loopingHasEnd && state.loopEnd > state.viewEnd) ||
|
||||
state.sampleStart < state.viewStart || state.sampleEnd > state.viewEnd))
|
||||
{
|
||||
int currentViewStart = state.viewStart;
|
||||
int currentViewEnd = state.viewEnd;
|
||||
|
||||
int newViewStart = loopingHasStart ? state.loopStart.load() : state.sampleStart.load();
|
||||
int newViewEnd = loopingHasEnd ? state.loopEnd.load() : state.sampleEnd.load();
|
||||
|
||||
state.viewStart = newViewStart;
|
||||
state.viewEnd = newViewEnd;
|
||||
updatePinnedPositions();
|
||||
state.viewStart = currentViewStart;
|
||||
state.viewEnd = currentViewEnd;
|
||||
moveBoundsToPinnedPositions(newViewStart > currentViewStart);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SampleNavigator::setSample(const juce::AudioBuffer<float>& sampleBuffer, float bufferSampleRate, bool resetView)
|
||||
{
|
||||
painter.setSample(sampleBuffer);
|
||||
sample = &sampleBuffer;
|
||||
sampleRate = bufferSampleRate;
|
||||
|
||||
if (resetView || recordingMode)
|
||||
{
|
||||
state.viewStart = 0;
|
||||
state.viewEnd = sampleBuffer.getNumSamples() - 1;
|
||||
updatePinnedPositions();
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
void SampleNavigator::sampleUpdated(int oldSize, int newSize)
|
||||
{
|
||||
painter.appendToPath(oldSize, newSize - 1);
|
||||
}
|
||||
|
||||
void SampleNavigator::setRecordingMode(bool recording)
|
||||
{
|
||||
recordingMode = recording;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SampleNavigator::paintOverChildren(juce::Graphics& g)
|
||||
{
|
||||
if (!sample || !sample->getNumSamples())
|
||||
return;
|
||||
|
||||
using namespace juce;
|
||||
|
||||
auto colors = getTheme();
|
||||
|
||||
// Paints the voice positions
|
||||
if (!recordingMode && !isWaveformMode())
|
||||
{
|
||||
for (auto& voice : synthVoices)
|
||||
{
|
||||
if (voice->isPlaying())
|
||||
{
|
||||
int location = int(std::ceil(voice->getPosition()));
|
||||
auto pos = sampleToPosition(location);
|
||||
|
||||
Path voicePath{};
|
||||
voicePath.addLineSegment(Line<float>(pos, 0.f, pos, float(getHeight())), 1.f);
|
||||
g.setColour(colors.light.withAlpha(voice->getEnvelopeGain()));
|
||||
g.strokePath(voicePath, PathStrokeType(Layout::playheadWidth * getWidth()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float boundsThickness = getWidth() * Layout::navigatorBoundsWidth * 0.66f;
|
||||
float sampleStartPos = sampleToPosition(state.sampleStart);
|
||||
float sampleEndPos = sampleToPosition(state.sampleEnd);
|
||||
float loopStartPos = sampleToPosition(state.loopStart);
|
||||
float loopEndPos = sampleToPosition(state.loopEnd);
|
||||
|
||||
auto boundColor = isWaveformMode() ? colors.highlight : isLooping->get() ? colors.loop : colors.slate;
|
||||
|
||||
g.setColour(disabled(boundColor));
|
||||
g.fillRect(sampleStartPos - boundsThickness, getHeight() * 0.1f, boundsThickness, getHeight() * 0.8f);
|
||||
g.setColour(disabled(boundColor));
|
||||
g.fillRect(sampleEndPos, getHeight() * 0.1f, boundsThickness, getHeight() * 0.8f);
|
||||
|
||||
g.setColour(disabled(colors.slate));
|
||||
if (isLooping->get() && loopHasStart->get() && !isWaveformMode())
|
||||
g.fillRect(loopStartPos - boundsThickness, getHeight() * 0.1f, boundsThickness, getHeight() * 0.8f);
|
||||
if (isLooping->get() && loopHasEnd->get() && !isWaveformMode())
|
||||
g.fillRect(loopEndPos, getHeight() * 0.1f, boundsThickness, getHeight() * 0.8f);
|
||||
|
||||
// Paints the start and stop
|
||||
float startPos = sampleToPosition(recordingMode ? 0 : int(state.viewStart));
|
||||
float stopPos = sampleToPosition(recordingMode ? sample->getNumSamples() - 1 : int(state.viewEnd));
|
||||
g.setColour(disabled(colors.slate.withAlpha(0.15f)));
|
||||
g.fillRect(startPos, 0.f, stopPos - startPos + 1.f, float(getHeight()));
|
||||
|
||||
float lineThickness = getWidth() * Layout::navigatorBoundsWidth;
|
||||
g.setColour(disabled(colors.slate));
|
||||
g.fillRect(startPos - lineThickness, 0.f, lineThickness, float(getHeight()));
|
||||
g.fillRect(stopPos, 0.f, lineThickness, float(getHeight()));
|
||||
}
|
||||
|
||||
void SampleNavigator::resized()
|
||||
{
|
||||
auto bounds = getLocalBounds().toFloat();
|
||||
float lineThickness = bounds.getWidth() * Layout::navigatorBoundsWidth;
|
||||
|
||||
bounds.reduce(lineThickness, 0.f);
|
||||
painter.setBounds(bounds.toNearestInt());
|
||||
}
|
||||
|
||||
void SampleNavigator::lookAndFeelChanged()
|
||||
{
|
||||
auto colors = getTheme();
|
||||
|
||||
painter.setColour(Colors::painterColorId, colors.darkerSlate);
|
||||
}
|
||||
|
||||
void SampleNavigator::enablementChanged()
|
||||
{
|
||||
painter.setEnabled(isEnabled());
|
||||
painter.repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SampleNavigator::mouseDown(const juce::MouseEvent& event)
|
||||
{
|
||||
if (!sample || !isEnabled() || recordingMode)
|
||||
return;
|
||||
|
||||
draggingTarget = getDraggingTarget(event.getMouseDownX(), event.getMouseDownY());
|
||||
if (draggingTarget == Drag::SAMPLE_FULL)
|
||||
dragSelectOffset = event.getMouseDownX() - sampleToPosition(state.viewStart);
|
||||
dragging = draggingTarget != Drag::NONE;
|
||||
lastDragOffset = 0;
|
||||
|
||||
if (draggingTarget == Drag::SAMPLE_START || draggingTarget == Drag::SAMPLE_END || draggingTarget == Drag::SAMPLE_FULL)
|
||||
juce::Desktop::getInstance().getMainMouseSource().enableUnboundedMouseMovement(true, false);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void SampleNavigator::mouseUp(const juce::MouseEvent&)
|
||||
{
|
||||
if (!sample || recordingMode || !dragging)
|
||||
return;
|
||||
|
||||
if (dragging)
|
||||
dummyParam.sendUIUpdate();
|
||||
|
||||
dragging = false;
|
||||
|
||||
juce::Desktop::getInstance().getMainMouseSource().enableUnboundedMouseMovement(false);
|
||||
auto screenPos = getScreenBounds().toFloat();
|
||||
juce::Point<float> newMousePos;
|
||||
if (draggingTarget == Drag::SAMPLE_START)
|
||||
newMousePos = juce::Point(screenPos.getX() + sampleToPosition(state.viewStart) * screenPos.getWidth() / getWidth(), screenPos.getCentreY());
|
||||
else if (draggingTarget == Drag::SAMPLE_END)
|
||||
newMousePos = juce::Point(screenPos.getX() + sampleToPosition(state.viewEnd) * screenPos.getWidth() / getWidth(), screenPos.getCentreY());
|
||||
else if (draggingTarget == Drag::SAMPLE_FULL)
|
||||
newMousePos = juce::Point(screenPos.getX() + (sampleToPosition(state.viewStart) + dragSelectOffset) * screenPos.getWidth() / getWidth(), screenPos.getCentreY());
|
||||
juce::Desktop::getInstance().getMainMouseSource().setScreenPosition(newMousePos);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void SampleNavigator::mouseMove(const juce::MouseEvent& event)
|
||||
{
|
||||
if (!sample || !isEnabled() || recordingMode)
|
||||
{
|
||||
setMouseCursor(juce::MouseCursor::NormalCursor);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dragging) // Don't change while dragging
|
||||
return;
|
||||
|
||||
Drag currentTarget = getDraggingTarget(event.getMouseDownX(), event.getMouseDownY());
|
||||
switch (currentTarget)
|
||||
{
|
||||
case Drag::SAMPLE_START:
|
||||
case Drag::SAMPLE_END:
|
||||
setMouseCursor(juce::MouseCursor::LeftRightResizeCursor);
|
||||
break;
|
||||
case Drag::SAMPLE_FULL:
|
||||
setMouseCursor(juce::MouseCursor::DraggingHandCursor);
|
||||
break;
|
||||
case Drag::NONE:
|
||||
setMouseCursor(juce::MouseCursor::NormalCursor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SampleNavigator::mouseDrag(const juce::MouseEvent& event)
|
||||
{
|
||||
if (!sample || !dragging || !isEnabled() || recordingMode)
|
||||
return;
|
||||
|
||||
// The goal is to keep the positions within their normal constraints
|
||||
bool secondary = juce::ModifierKeys::currentModifiers.isAnyModifierKeyDown();
|
||||
float edgeSensitivity = getDragSensitivity(secondary);
|
||||
float fullSensitivity = getDragSensitivity(!secondary);
|
||||
switch (draggingTarget)
|
||||
{
|
||||
case Drag::SAMPLE_START:
|
||||
{
|
||||
int difference = event.getOffsetFromDragStart().getX() - lastDragOffset;
|
||||
moveStart(difference / 2.f, edgeSensitivity);
|
||||
moveEnd(-difference / 2.f, edgeSensitivity);
|
||||
|
||||
break;
|
||||
}
|
||||
case Drag::SAMPLE_END:
|
||||
{
|
||||
int difference = event.getOffsetFromDragStart().getX() - lastDragOffset;
|
||||
moveEnd(difference / 2.f, edgeSensitivity);
|
||||
moveStart(-difference / 2.f, edgeSensitivity);
|
||||
|
||||
break;
|
||||
}
|
||||
case Drag::SAMPLE_FULL:
|
||||
{
|
||||
int change = event.getOffsetFromDragStart().getX() - lastDragOffset;
|
||||
moveBoth(float(change), fullSensitivity);
|
||||
|
||||
break;
|
||||
}
|
||||
case Drag::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
lastDragOffset = event.getOffsetFromDragStart().getX();
|
||||
}
|
||||
|
||||
void SampleNavigator::mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel)
|
||||
{
|
||||
if (!sample || dragging || !isEnabled() || recordingMode)
|
||||
return;
|
||||
|
||||
int sampleCenter = positionToSample(event.position.getX());
|
||||
scrollView(wheel, sampleCenter);
|
||||
}
|
||||
|
||||
void SampleNavigator::mouseDoubleClick(const juce::MouseEvent&)
|
||||
{
|
||||
if (!sample || recordingMode)
|
||||
return;
|
||||
|
||||
state.viewStart = 0;
|
||||
state.viewEnd = sample->getNumSamples() - 1;
|
||||
}
|
||||
|
||||
void SampleNavigator::scrollView(const juce::MouseWheelDetails& wheel, int sampleCenter, bool centerZoomOut)
|
||||
{
|
||||
if (!sample)
|
||||
return;
|
||||
|
||||
float changeY = wheel.deltaY * Feel::MOUSE_SENSITIVITY;
|
||||
float changeX = wheel.deltaX * Feel::MOUSE_SENSITIVITY;
|
||||
|
||||
// MacOS sensitivity is a little different
|
||||
if (wheel.isSmooth && ((juce::SystemStats::getOperatingSystemType() & juce::SystemStats::MacOSX) == 0))
|
||||
changeY *= 0.75f;
|
||||
|
||||
bool trackHorizontal = std::abs(wheel.deltaX) > std::abs(wheel.deltaY);
|
||||
bool modifier = juce::ModifierKeys::currentModifiers.isAnyModifierKeyDown();
|
||||
|
||||
if (modifier || trackHorizontal)
|
||||
{
|
||||
float sensitivity = getDragSensitivity(false);
|
||||
moveBoth(trackHorizontal ? changeX : changeY, sensitivity);
|
||||
}
|
||||
else if (!modifier)
|
||||
{
|
||||
float sensitivity = getDragSensitivity(false);
|
||||
float startRatio = 1.f;
|
||||
float endRatio = 1.f;
|
||||
if (sampleCenter >= state.viewStart && sampleCenter <= state.viewEnd &&
|
||||
((centerZoomOut && state.viewStart > 0 && state.viewEnd < sample->getNumSamples() - 1) || changeY > 0))
|
||||
{
|
||||
endRatio = float(state.viewEnd - sampleCenter) / (state.viewEnd - state.viewStart);
|
||||
startRatio = 1.f - endRatio;
|
||||
}
|
||||
|
||||
// The order must change depending on the direction of the wheel, since both maintain constraints and there's a minimum view size
|
||||
if (changeY > 0)
|
||||
{
|
||||
moveEnd(-changeY * endRatio, sensitivity);
|
||||
moveStart(changeY * startRatio, sensitivity);
|
||||
}
|
||||
else
|
||||
{
|
||||
moveStart(changeY * startRatio, sensitivity);
|
||||
moveEnd(-changeY * endRatio, sensitivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SampleNavigator::fitView()
|
||||
{
|
||||
if (!sample)
|
||||
return;
|
||||
|
||||
int viewStart = isLooping->get() && loopHasStart->get() && !isWaveformMode() ? state.loopStart.load() : state.sampleStart.load();
|
||||
int viewEnd = isLooping->get() && loopHasEnd->get() && !isWaveformMode() ? state.loopEnd.load() : state.sampleEnd.load();
|
||||
|
||||
// Keep the view size larger than the minimum
|
||||
if (viewEnd - viewStart + 1 < Feel::MINIMUM_VIEW)
|
||||
{
|
||||
viewEnd = juce::jmin<int>(sample->getNumSamples() - 1, viewStart + Feel::MINIMUM_VIEW - 1);
|
||||
viewStart = juce::jmax<int>(0, viewEnd - Feel::MINIMUM_VIEW + 1);
|
||||
}
|
||||
|
||||
state.viewStart = viewStart;
|
||||
state.viewEnd = viewEnd;
|
||||
|
||||
updatePinnedPositions();
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void SampleNavigator::moveStart(float change, float sensitivity)
|
||||
{
|
||||
int oldViewStart = state.viewStart;
|
||||
|
||||
auto viewStart = juce::jmax<int>(juce::jmin<int>(int(std::round(state.viewStart + change * sensitivity)), state.viewEnd - Feel::MINIMUM_VIEW), 0);
|
||||
state.viewStart = viewStart;
|
||||
|
||||
if (state.pinView)
|
||||
moveBoundsToPinnedPositions(viewStart < oldViewStart);
|
||||
}
|
||||
|
||||
void SampleNavigator::moveEnd(float change, float sensitivity)
|
||||
{
|
||||
int oldViewEnd = state.viewEnd;
|
||||
|
||||
auto viewEnd = juce::jmin<int>(juce::jmax<int>(int(std::round(state.viewEnd + change * sensitivity)), state.viewStart + Feel::MINIMUM_VIEW), sample->getNumSamples() - 1);
|
||||
state.viewEnd = viewEnd;
|
||||
|
||||
if (state.pinView)
|
||||
moveBoundsToPinnedPositions(viewEnd < oldViewEnd);
|
||||
}
|
||||
|
||||
void SampleNavigator::moveBoth(float change, float sensitivity)
|
||||
{
|
||||
int difference = juce::jlimit<int>(-state.viewStart, sample->getNumSamples() - 1 - state.viewEnd, int(change * sensitivity));
|
||||
|
||||
state.viewStart = state.viewStart + difference;
|
||||
state.viewEnd = state.viewEnd + difference;
|
||||
|
||||
if (state.pinView)
|
||||
moveBoundsToPinnedPositions(difference < 0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
NavigatorParts SampleNavigator::getDraggingTarget(int x, int y) const
|
||||
{
|
||||
auto bounds = getLocalBounds().toFloat();
|
||||
auto startPos = sampleToPosition(state.viewStart);
|
||||
auto stopPos = sampleToPosition(state.viewEnd);
|
||||
|
||||
// A little trick to make the full sample always possible to drag
|
||||
auto offset = juce::jmax<float>(30 - (stopPos - startPos), 0) / 2;
|
||||
|
||||
juce::Array targets = {
|
||||
CompPart {Drag::SAMPLE_START, juce::Rectangle<float>{startPos - offset, bounds.getY(), 0, bounds.getHeight()}, 2},
|
||||
CompPart {Drag::SAMPLE_END, juce::Rectangle<float>{stopPos + offset, bounds.getY(), 0, bounds.getHeight()}, 2},
|
||||
CompPart {Drag::SAMPLE_FULL, juce::Rectangle<float>{startPos, bounds.getY(), stopPos - startPos, bounds.getHeight()}, 1}
|
||||
};
|
||||
return CompPart<Drag>::getClosestInRange(targets, x, y, Feel::DRAGGABLE_SNAP);
|
||||
}
|
||||
|
||||
float SampleNavigator::sampleToPosition(int sampleIndex) const
|
||||
{
|
||||
float boundsWidth = getWidth() * Layout::navigatorBoundsWidth;
|
||||
return juce::jmap<float>(float(sampleIndex), 0.f, float(sample->getNumSamples() - 1), 0.f, float(painter.getWidth())) + boundsWidth;
|
||||
}
|
||||
|
||||
int SampleNavigator::positionToSample(float position) const
|
||||
{
|
||||
return int(juce::jmap<float>(position - getWidth() * Layout::navigatorBoundsWidth, 0.f, float(painter.getWidth()), 0.f, float(sample->getNumSamples())));
|
||||
}
|
||||
|
||||
float SampleNavigator::getDragSensitivity(bool constant) const
|
||||
{
|
||||
int viewSize = state.viewEnd - state.viewStart + 1;
|
||||
if (constant)
|
||||
return float(sample->getNumSamples()) / getWidth();
|
||||
else
|
||||
return -std::log(float(viewSize) / sample->getNumSamples() / juce::MathConstants<float>::euler) * viewSize / getWidth();
|
||||
}
|
||||
|
||||
void SampleNavigator::loopHasStartUpdate(bool newValue)
|
||||
{
|
||||
repaint();
|
||||
|
||||
if (!bool(newValue))
|
||||
return;
|
||||
|
||||
if (state.viewStart > state.loopStart && (state.loopStart == 0 || state.pinView))
|
||||
state.loopStart = state.viewStart.load();
|
||||
}
|
||||
|
||||
void SampleNavigator::loopHasEndUpdate(bool newValue)
|
||||
{
|
||||
repaint();
|
||||
|
||||
if (!bool(newValue))
|
||||
return;
|
||||
|
||||
if (state.viewEnd < state.loopEnd && (state.loopEnd == sample->getNumSamples() - 1 || state.pinView))
|
||||
state.loopEnd = state.viewEnd.load();
|
||||
}
|
||||
|
||||
void SampleNavigator::updatePinnedPositions()
|
||||
{
|
||||
int viewStart = state.viewStart;
|
||||
int viewEnd = state.viewEnd;
|
||||
|
||||
pinnedSampleStart = juce::jlimit<float>(0.f, 1.f, float(state.sampleStart - viewStart) / (viewEnd - viewStart));
|
||||
pinnedSampleEnd = juce::jlimit<float>(0.f, 1.f, float(state.sampleEnd - viewStart) / (viewEnd - viewStart));
|
||||
pinnedLoopStart = juce::jlimit<float>(0.f, 1.f, float(state.loopStart - viewStart) / (viewEnd - viewStart));
|
||||
pinnedLoopEnd = juce::jlimit<float>(0.f, 1.f, float(state.loopEnd - viewStart) / (viewEnd - viewStart));
|
||||
}
|
||||
|
||||
void SampleNavigator::moveBoundsToPinnedPositions(bool startToEnd)
|
||||
{
|
||||
// To maintain constraints, we change the bounds in order depending on the direction of the change
|
||||
navigatorUpdate = true;
|
||||
if (startToEnd)
|
||||
{
|
||||
state.loopStart = juce::jmax(int(std::round(pinnedLoopStart * (state.viewEnd - state.viewStart))) + state.viewStart, 0);
|
||||
state.sampleStart = int(std::round(pinnedSampleStart * (state.viewEnd - state.viewStart))) + state.viewStart;
|
||||
state.sampleEnd = int(std::round(pinnedSampleEnd * (state.viewEnd - state.viewStart))) + state.viewStart;
|
||||
state.loopEnd = juce::jmin(int(std::round(pinnedLoopEnd * (state.viewEnd - state.viewStart))) + state.viewStart, sample->getNumSamples() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.loopEnd = juce::jmin(int(std::round(pinnedLoopEnd * (state.viewEnd - state.viewStart))) + state.viewStart, sample->getNumSamples() - 1);
|
||||
state.sampleEnd = int(std::round(pinnedSampleEnd * (state.viewEnd - state.viewStart))) + state.viewStart;
|
||||
state.sampleStart = int(std::round(pinnedSampleStart * (state.viewEnd - state.viewStart))) + state.viewStart;
|
||||
state.loopStart = juce::jmax(int(std::round(pinnedLoopStart * (state.viewEnd - state.viewStart))) + state.viewStart, 0);
|
||||
}
|
||||
navigatorUpdate = false;
|
||||
}
|
||||
|
||||
bool SampleNavigator::isWaveformMode() const
|
||||
{
|
||||
return CustomSamplerVoice::isWavetableModeAvailable(sampleRate, state.sampleStart, state.sampleEnd) && !isWavetableModeDisabled->get();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue