justasample/Source/Components/SampleEditor.cpp

556 lines
23 KiB
C++
Raw Normal View History

2026-07-23 23:40:48 +02:00
/*
==============================================================================
SampleEditorOverlay.cpp
Created: 19 Sep 2023 2:03:29pm
Author: binya
==============================================================================
*/
#include <JuceHeader.h>
#include "SampleEditor.h"
SampleEditorOverlay::SampleEditorOverlay(const APVTS& apvts, PluginParameters::State& pluginState, const juce::OwnedArray<CustomSamplerVoice>& synthVoices, UIDummyParam& dummy, CustomComponent* forwardEventsTo) :
synthVoices(synthVoices), dummyParam(dummy),
viewStart(pluginState.viewStart),
viewEnd(pluginState.viewEnd),
sampleStart(pluginState.sampleStart),
sampleEnd(pluginState.sampleEnd),
loopStart(pluginState.loopStart),
loopEnd(pluginState.loopEnd),
pinnedBounds(pluginState.pinView),
isWavetableModeDisabled(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::DISABLE_WAVETABLE_MODE))),
isLooping(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::IS_LOOPING))),
loopingHasStart(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::LOOPING_HAS_START))),
loopingHasEnd(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::LOOPING_HAS_END))),
isWavetableModeDisabledAttachment(*isWavetableModeDisabled, [this](bool) { repaint(); }, apvts.undoManager),
isLoopingAttachment(*isLooping, [this](bool) { repaint(); }, apvts.undoManager),
loopingHasStartAttachment(*loopingHasStart, [this](bool) { repaint(); }, apvts.undoManager),
loopingHasEndAttachment(*loopingHasEnd, [this](bool) { repaint(); }, apvts.undoManager),
forwardMouseEvents(forwardEventsTo)
{
viewStart.addListener(this);
viewEnd.addListener(this);
sampleStart.addListener(this);
sampleEnd.addListener(this);
loopStart.addListener(this);
loopEnd.addListener(this);
}
SampleEditorOverlay::~SampleEditorOverlay()
{
viewStart.removeListener(this);
viewEnd.removeListener(this);
sampleStart.removeListener(this);
sampleEnd.removeListener(this);
loopStart.removeListener(this);
loopEnd.removeListener(this);
}
//==============================================================================
void SampleEditorOverlay::valueChanged(ListenableValue<int>&, int)
{
safeRepaint();
}
void SampleEditorOverlay::paint(juce::Graphics& g)
{
if (viewStart == 0 && viewEnd == 0)
return;
using namespace juce;
auto colors = getTheme();
// Draw voice positions
auto waveformMode = isWaveformMode();
auto gain = 0.f;
for (auto& voice : synthVoices)
{
if (voice->isPlaying())
{
int location = int(std::ceil(voice->getPosition()));
auto pos = sampleToPosition(location) + getWidth() * Layout::boundsWidth;
Path voicePosition;
voicePosition.addLineSegment(Line<float>(pos, 0.f, pos, float(getHeight())), 1);
if (!waveformMode)
{
g.setColour(colors.light.withAlpha(voice->getEnvelopeGain()));
g.strokePath(voicePosition, PathStrokeType(Layout::playheadWidth * getWidth()));
}
gain += voice->getEnvelopeGain() / synthVoices.size();
}
}
float boundsWidth = getBoundsWidth();
float boundsSeparation = jmax(5 * boundsWidth, sampleToPosition(viewStart + Feel::MINIMUM_BOUNDS_DISTANCE) + 2 * boundsWidth);
float handleStrokeWidth = Layout::handleWidth * getWidth();
float startPos = sampleToPosition(sampleStart);
float endPos = sampleToPosition(sampleEnd) + boundsWidth;
float loopStartPos = sampleToPosition(loopStart);
float loopEndPos = sampleToPosition(loopEnd) + boundsWidth;
auto looping = isLooping->get() && !waveformMode;
auto loopingWithStart = isLooping->get() && loopingHasStart->get() && !waveformMode;
auto loopingWithEnd = isLooping->get() && loopingHasEnd->get() && !waveformMode;
// Paint the backgrounds
if (looping || waveformMode)
{
auto color = looping ? colors.loop.withAlpha(0.07f) : colors.highlight.withAlpha(jmin(gain * 0.5f, 0.2f));
g.setColour(disabled(color));
g.fillRect(Rectangle(startPos, 0.f, endPos - startPos, float(getHeight())));
}
g.setColour(disabled(colors.slate.withAlpha(0.15f)));
g.fillRect(Rectangle(0.f, 0.f, loopingWithStart ? loopStartPos : startPos, float(getHeight())));
g.fillRect(Rectangle(loopingWithEnd ? loopEndPos : endPos, 0.f, float(getWidth()) - endPos, float(getHeight())));
// Paint the start bound
Path startPosPath;
startPosPath.addRectangle(startPos, 0.f, boundsWidth, float(getHeight()));
(looping ? loopBoundsShadow : boundsShadow).render(g, startPosPath);
auto color = looping ? colors.loop : waveformMode ? colors.highlight : colors.slate;
g.setColour(dragging && draggingTarget == EditorParts::SAMPLE_START ? color.withAlpha(0.5f) : disabled(color));
g.fillPath(startPosPath);
if (isMouseOverOrDragging() && (!loopingWithStart || startPos - loopStartPos > boundsSeparation))
g.strokePath(handleLeft, PathStrokeType(handleStrokeWidth), AffineTransform::translation(startPos, 0.f));
if (isMouseOverOrDragging() && endPos - startPos > boundsSeparation)
g.strokePath(handleRight, PathStrokeType(handleStrokeWidth), AffineTransform::translation(startPos, 0.f));
// Paint the end bound
Path endPosPath;
endPosPath.addRectangle(endPos, 0.f, boundsWidth, float(getHeight()));
(looping ? loopBoundsShadow : boundsShadow).render(g, endPosPath);
g.setColour(dragging && draggingTarget == EditorParts::SAMPLE_END ? color.withAlpha(0.5f) : disabled(color));
g.fillPath(endPosPath);
if (isMouseOverOrDragging() && endPos - startPos > boundsSeparation)
g.strokePath(handleLeft, PathStrokeType(handleStrokeWidth), AffineTransform::translation(endPos, 0.f));
if (isMouseOverOrDragging() && (!loopingWithEnd || loopEndPos - endPos > boundsSeparation))
g.strokePath(handleRight, PathStrokeType(handleStrokeWidth), AffineTransform::translation(endPos, 0.f));
// Paint the loop bounds
if (looping)
{
if (loopingWithStart)
{
Path loopStartPath;
loopStartPath.addRectangle(loopStartPos, 0.f, boundsWidth, float(getHeight()));
boundsShadow.render(g, loopStartPath);
g.setColour(dragging && draggingTarget == EditorParts::LOOP_START ? colors.slate.withAlpha(0.5f) : disabled(colors.slate));
g.fillPath(loopStartPath);
if (isMouseOverOrDragging())
g.strokePath(handleLeft, PathStrokeType(handleStrokeWidth), AffineTransform::translation(loopStartPos, 0.f));
if (isMouseOverOrDragging() && startPos - loopStartPos > boundsSeparation)
g.strokePath(handleRight, PathStrokeType(handleStrokeWidth), AffineTransform::translation(loopStartPos, 0.f));
}
if (loopingWithEnd)
{
Path loopEndPath;
loopEndPath.addRectangle(loopEndPos, 0.f, boundsWidth, float(getHeight()));
boundsShadow.render(g, loopEndPath);
g.setColour(dragging && draggingTarget == EditorParts::LOOP_END ? colors.slate.withAlpha(0.5f) : disabled(colors.slate));
g.fillPath(loopEndPath);
if (isMouseOverOrDragging() && loopEndPos - endPos > boundsSeparation)
g.strokePath(handleLeft, PathStrokeType(handleStrokeWidth, PathStrokeType::curved, PathStrokeType::rounded), AffineTransform::translation(loopEndPos, 0.f));
if (isMouseOverOrDragging())
g.strokePath(handleRight, PathStrokeType(handleStrokeWidth, PathStrokeType::curved, PathStrokeType::rounded), AffineTransform::translation(loopEndPos, 0.f));
}
}
Path innerShadowPath;
innerShadowPath.addRectangle(getLocalBounds());
innerShadow.render(g, innerShadowPath);
}
void SampleEditorOverlay::resized()
{
int offset = int(0.0005f * getWidth());
boundsShadow.setOffset({ offset, 0 }, 0);
boundsShadow.setOffset({ -offset, 0 }, 1);
loopBoundsShadow.setOffset({ offset, 0 }, 0);
loopBoundsShadow.setOffset({ -offset, 0 }, 1);
int innerShadowOffset = int(0.001f * getWidth());
innerShadow.setOffset({ 0, innerShadowOffset }, 0);
innerShadow.setOffset({ 0, -innerShadowOffset }, 1);
float bounds = getBoundsWidth();
float handleHeight = 0.04f * getWidth();
handleLeft.clear();
handleLeft.startNewSubPath(-bounds / 2.f, (getHeight() + handleHeight) / 2.f);
handleLeft.lineTo(-bounds * 4.f / 3.f, getHeight() / 2.f);
handleLeft.lineTo(-bounds / 2.f, (getHeight() - handleHeight) / 2.f);
handleRight.clear();
handleRight.startNewSubPath(bounds + bounds / 2.f, (getHeight() + handleHeight) / 2.f);
handleRight.lineTo(bounds + bounds * 4.f / 3.f, getHeight() / 2.f);
handleRight.lineTo(bounds + bounds / 2.f, (getHeight() - handleHeight) / 2.f);
}
void SampleEditorOverlay::enablementChanged()
{
setInterceptsMouseClicks(isEnabled(), isEnabled());
repaint();
}
void SampleEditorOverlay::lookAndFeelChanged()
{
auto colors = getTheme();
boundsShadow.setColor(colors.slate.withAlpha(0.25f), 0);
boundsShadow.setColor(colors.slate.withAlpha(0.25f), 1);
loopBoundsShadow.setColor(colors.loop.withAlpha(0.25f), 0);
loopBoundsShadow.setColor(colors.loop.withAlpha(0.25f), 1);
innerShadow.setColor(colors.slate.withAlpha(0.25f), 0);
innerShadow.setColor(colors.slate.withAlpha(0.25f), 1);
}
//==============================================================================
void SampleEditorOverlay::mouseMove(const juce::MouseEvent& event)
{
EditorParts editorPart = getClosestPartInRange(event.x, event.y);
switch (editorPart)
{
case EditorParts::SAMPLE_START:
case EditorParts::SAMPLE_END:
case EditorParts::LOOP_START:
case EditorParts::LOOP_END:
setMouseCursor(juce::MouseCursor::LeftRightResizeCursor);
break;
case EditorParts::NONE:
setMouseCursor(juce::MouseCursor::NormalCursor);
}
}
void SampleEditorOverlay::mouseDown(const juce::MouseEvent& event)
{
EditorParts closest = getClosestPartInRange(event.getMouseDownX(), event.getMouseDownY());
switch (closest)
{
case EditorParts::NONE:
if (forwardMouseEvents)
forwardMouseEvents->mouseDown(event.getEventRelativeTo(forwardMouseEvents));
break;
default:
dragging = true;
draggingTarget = closest;
repaint();
break;
}
}
void SampleEditorOverlay::mouseUp(const juce::MouseEvent& event)
{
if (dragging)
dummyParam.sendUIUpdate();
dragging = false;
EditorParts closest = getClosestPartInRange(event.getMouseDownX(), event.getMouseDownY());
if (closest == EditorParts::NONE && forwardMouseEvents)
forwardMouseEvents->mouseUp(event.getEventRelativeTo(forwardMouseEvents));
repaint();
}
void SampleEditorOverlay::mouseDrag(const juce::MouseEvent& event)
{
using namespace juce;
if (!dragging)
return;
auto newSample = positionToSample(event.getMouseDownX() + event.getOffsetFromDragStart().getX() - getBoundsWidth());
auto loopHasStart = isLooping->get() && loopingHasStart->get() && !isWaveformMode();
auto loopHasEnd = isLooping->get() && loopingHasEnd->get() && !isWaveformMode();
auto pin = pinnedBounds.load();
auto minBufferSize = (1 + int(loopHasStart) + int(loopHasEnd)) * Feel::MINIMUM_BOUNDS_DISTANCE;
if (sampleBuffer->getNumSamples() < minBufferSize)
return;
// An intricate way to handle the dragging of the bounds, hopefully for a user-friendly experience
// Note that jmin(jmax(value, lower), upper) is equivalent to jlimit but uses the upper bound if min > max, while jmax(jmin(value, upper), lower) does the opposite
switch (draggingTarget)
{
case EditorParts::LOOP_START:
viewStart = jmax<int>(jmin<int>(viewStart, newSample), 0);
viewEnd = jmin<int>(jmax<int>(viewEnd, newSample + (2 + int(loopHasEnd)) * Feel::MINIMUM_BOUNDS_DISTANCE), sampleBuffer->getNumSamples() - 1);
if (newSample < loopStart)
{
loopStart = jmax(0, newSample);
}
else
{
loopEnd = jmin<int>(jmax<int>(loopEnd, newSample + 3 * Feel::MINIMUM_BOUNDS_DISTANCE), loopHasEnd && pin && loopEnd <= viewEnd ? viewEnd.load() : sampleBuffer->getNumSamples() - 1);
sampleEnd = jmin<int>(jmax<int>(sampleEnd, newSample + 2 * Feel::MINIMUM_BOUNDS_DISTANCE), jmin<int>(loopHasEnd ? loopEnd - Feel::MINIMUM_BOUNDS_DISTANCE : loopEnd.load(), pin ? viewEnd.load() : sampleBuffer->getNumSamples() - 1));
sampleStart = jmin<int>(jmax<int>(sampleStart, newSample + Feel::MINIMUM_BOUNDS_DISTANCE), sampleEnd - Feel::MINIMUM_BOUNDS_DISTANCE);
loopStart = jmin<int>(newSample, sampleStart - Feel::MINIMUM_BOUNDS_DISTANCE);
}
break;
case EditorParts::SAMPLE_START:
viewStart = jmax<int>(jmin<int>(viewStart, newSample - int(loopHasStart) * Feel::MINIMUM_BOUNDS_DISTANCE), 0);
viewEnd = jmin<int>(jmax<int>(viewEnd, newSample + (1 + int(loopHasEnd)) * Feel::MINIMUM_BOUNDS_DISTANCE), sampleBuffer->getNumSamples() - 1);
if (newSample < sampleStart)
{
loopStart = jmax<int>(jmin<int>(loopStart, newSample - Feel::MINIMUM_BOUNDS_DISTANCE), loopHasStart && pin && loopStart >= viewStart ? viewStart.load() : 0);
sampleStart = jmax<int>(newSample, loopStart + int(loopHasStart) * Feel::MINIMUM_BOUNDS_DISTANCE);
}
else
{
loopEnd = jmin<int>(jmax<int>(loopEnd, newSample + 2 * Feel::MINIMUM_BOUNDS_DISTANCE), loopHasEnd && pin && loopEnd <= viewEnd ? viewEnd.load() : sampleBuffer->getNumSamples() - 1);
sampleEnd = jmin<int>(jmax<int>(sampleEnd, newSample + Feel::MINIMUM_BOUNDS_DISTANCE), jmin<int>(loopHasEnd ? loopEnd - Feel::MINIMUM_BOUNDS_DISTANCE : loopEnd.load(), pin ? viewEnd.load() : sampleBuffer->getNumSamples() - 1));
sampleStart = jmin<int>(newSample, sampleEnd - Feel::MINIMUM_BOUNDS_DISTANCE);
}
break;
case EditorParts::SAMPLE_END:
viewStart = jmax<int>(jmin<int>(viewStart, newSample - (1 + int(loopHasStart)) * Feel::MINIMUM_BOUNDS_DISTANCE), 0);
viewEnd = jmin<int>(jmax<int>(viewEnd, newSample + int(loopHasEnd) * Feel::MINIMUM_BOUNDS_DISTANCE), sampleBuffer->getNumSamples() - 1);
if (newSample > sampleEnd)
{
loopEnd = jmin<int>(jmax<int>(loopEnd, newSample + Feel::MINIMUM_BOUNDS_DISTANCE), loopHasEnd && pin && loopEnd <= viewEnd ? viewEnd.load() : sampleBuffer->getNumSamples() - 1);
sampleEnd = jmin<int>(newSample, loopEnd - int(loopHasEnd) * Feel::MINIMUM_BOUNDS_DISTANCE);
}
else
{
loopStart = jmax<int>(jmin<int>(loopStart, newSample - 2 * Feel::MINIMUM_BOUNDS_DISTANCE), loopHasStart && pin && loopStart >= viewStart ? viewStart.load() : 0);
sampleStart = jmax<int>(jmin<int>(sampleStart, newSample - Feel::MINIMUM_BOUNDS_DISTANCE), jmax<int>(loopHasStart ? loopStart + Feel::MINIMUM_BOUNDS_DISTANCE : loopStart.load(), pin ? viewStart.load() : 0));
sampleEnd = jmax<int>(newSample, sampleStart + Feel::MINIMUM_BOUNDS_DISTANCE);
}
break;
case EditorParts::LOOP_END:
viewStart = jmax<int>(jmin<int>(viewStart, newSample - (2 + int(loopHasStart)) * Feel::MINIMUM_BOUNDS_DISTANCE), 0);
viewEnd = jmin<int>(jmax<int>(viewEnd, newSample), sampleBuffer->getNumSamples() - 1);
if (newSample > loopEnd)
{
loopEnd = jmin(sampleBuffer->getNumSamples() - 1, newSample);
}
else
{
loopStart = jmax<int>(jmin<int>(loopStart, newSample - 3 * Feel::MINIMUM_BOUNDS_DISTANCE), loopHasStart && pin && loopStart >= viewStart ? viewStart.load() : 0);
sampleStart = jmax<int>(jmin<int>(sampleStart, newSample - 2 * Feel::MINIMUM_BOUNDS_DISTANCE), jmax<int>(loopHasStart ? loopStart + Feel::MINIMUM_BOUNDS_DISTANCE : loopStart.load(), pin ? viewStart.load() : 0));
sampleEnd = jmax<int>(jmin<int>(sampleEnd, newSample - Feel::MINIMUM_BOUNDS_DISTANCE), sampleStart + Feel::MINIMUM_BOUNDS_DISTANCE);
loopEnd = jmax<int>(newSample, sampleEnd + Feel::MINIMUM_BOUNDS_DISTANCE);
}
break;
}
}
//==============================================================================
void SampleEditorOverlay::setSample(const juce::AudioBuffer<float>& sample, float bufferSampleRate)
{
sampleBuffer = &sample;
sampleRate = bufferSampleRate;
}
juce::String SampleEditorOverlay::getCustomHelpText()
{
auto closest = dragging ? draggingTarget : getClosestPartInRange(getMouseXYRelative().getX(), getMouseXYRelative().getY());
switch (closest)
{
case EditorParts::SAMPLE_START: return "Adjust sample start";
case EditorParts::SAMPLE_END: return "Adjust sample end";
case EditorParts::LOOP_START: return "Adjust loop start portion";
case EditorParts::LOOP_END: return "Adjust loop release portion";
default:
if (forwardMouseEvents)
{
const auto& helpText = forwardMouseEvents->getCustomHelpText();
if (helpText.isNotEmpty())
return helpText;
}
return "Scroll to navigate";
}
}
//==============================================================================
EditorParts SampleEditorOverlay::getClosestPartInRange(int x, int y) const
{
auto startPos = sampleToPosition(sampleStart);
auto endPos = sampleToPosition(sampleEnd);
juce::Array targets = {
CompPart {EditorParts::SAMPLE_START, juce::Rectangle(startPos + getBoundsWidth() / 2.f, 0.f, 1.f, float(getHeight())), 1},
CompPart {EditorParts::SAMPLE_END, juce::Rectangle(endPos + 3 * getBoundsWidth() / 2.f, 0.f, 1.f, float(getHeight())), 1},
};
if (isLooping->get() && !isWaveformMode())
{
if (loopingHasStart->get())
targets.add(CompPart{ EditorParts::LOOP_START, juce::Rectangle(sampleToPosition(loopStart) + getBoundsWidth() / 2.f, 0.f, 1.f, float(getHeight())), 1 });
if (loopingHasEnd->get())
targets.add(CompPart{ EditorParts::LOOP_END, juce::Rectangle(sampleToPosition(loopEnd) + 3 * getBoundsWidth() / 2.f, 0.f, 1.f, float(getHeight())), 1 });
}
return CompPart<EditorParts>::getClosestInRange(targets, x, y, Feel::DRAGGABLE_SNAP);
}
float SampleEditorOverlay::getBoundsWidth() const
{
return Layout::boundsWidth * getWidth();
}
float SampleEditorOverlay::sampleToPosition(int sampleIndex) const
{
return juce::jmap<float>(float(sampleIndex - viewStart), 0.f, float(viewEnd - viewStart), 0.f, float(getWidth() - 2 * getBoundsWidth()));
}
int SampleEditorOverlay::positionToSample(float position) const
{
return viewStart + int(std::round(juce::jmap<float>(position, 0.f, float(getWidth() - 2 * getBoundsWidth()), 0.f, float(viewEnd - viewStart))));
}
bool SampleEditorOverlay::isWaveformMode() const
{
return CustomSamplerVoice::isWavetableModeAvailable(sampleRate, sampleStart, sampleEnd) && !isWavetableModeDisabled->get();
}
/*
==============================================================================
SampleEditor.cpp
Created: 19 Sep 2023 2:03:29pm
Author: binya
==============================================================================
*/
SampleEditor::SampleEditor(APVTS& apvts, PluginParameters::State& pluginState, const juce::OwnedArray<CustomSamplerVoice>& synthVoices, const std::function<void(const juce::MouseWheelDetails& details, int centerSample)>& navScrollFunc) :
apvts(apvts), pluginState(pluginState), dummyParam(apvts, PluginParameters::State::UI_DUMMY_PARAM),
painter(pluginState.primaryChannel, 0.25f, &dummyParam),
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),
overlay(apvts, pluginState, synthVoices, dummyParam, &painter), scrollFunc(navScrollFunc)
{
pluginState.viewStart.addListener(this);
pluginState.viewEnd.addListener(this);
painter.setGain(juce::Decibels::decibelsToGain(float(apvts.getParameterAsValue(PluginParameters::SAMPLE_GAIN).getValue())));
painter.setMono(bool(apvts.getParameterAsValue(PluginParameters::MONO_OUTPUT).getValue()));
addAndMakeVisible(&painter);
overlay.toFront(true);
addAndMakeVisible(&overlay);
boundsSelector.toFront(true);
addAndMakeVisible(&boundsSelector);
}
SampleEditor::~SampleEditor()
{
pluginState.viewStart.removeListener(this);
pluginState.viewEnd.removeListener(this);
}
//==============================================================================
void SampleEditor::valueChanged(ListenableValue<int>& source, int /*newValue*/)
{
if ((&source == &pluginState.viewStart || &source == &pluginState.viewEnd) && pluginState.viewStart < pluginState.viewEnd)
{
painter.setSampleView(pluginState.viewStart, pluginState.viewEnd);
}
}
//==============================================================================
void SampleEditor::resized()
{
auto bounds = getLocalBounds();
overlay.setBounds(bounds);
bounds.reduce(int(Layout::boundsWidth * bounds.getWidth()), 0);
painter.setBounds(bounds.reduced(0, int(getWidth() * 0.01f)));
boundsSelector.setBounds(bounds);
}
void SampleEditor::enablementChanged()
{
overlay.setEnabled(isEnabled());
painter.setEnabled(isEnabled());
}
void SampleEditor::mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel)
{
if (!sampleBuffer || recordingMode)
return;
int sample = positionToSample(event.position.getX());
scrollFunc(wheel, sample);
}
int SampleEditor::positionToSample(float position) const
{
if (!sampleBuffer)
return 0;
return juce::jlimit<int>(0, sampleBuffer->getNumSamples() - 1, overlay.positionToSample(position - Layout::boundsWidth * getWidth()));
}
//==============================================================================
void SampleEditor::setSample(const juce::AudioBuffer<float>& sample, float bufferSampleRate, bool resetView)
{
sampleBuffer = &sample;
sampleRate = bufferSampleRate;
if (resetView || recordingMode)
painter.setSample(sample);
else
painter.setSample(sample, pluginState.viewStart, pluginState.viewEnd);
overlay.setSample(sample, bufferSampleRate);
}
void SampleEditor::setRecordingMode(bool recording)
{
recordingMode = recording;
overlay.setVisible(!recording);
}
bool SampleEditor::isRecordingMode() const
{
return recordingMode;
}
void SampleEditor::sampleUpdated(int oldSize, int newSize)
{
painter.appendToPath(oldSize, newSize - 1);
}
//==============================================================================
void SampleEditor::promptBoundsSelection(const std::function<void(int, int)>& callback)
{
overlay.setEnabled(false);
boundsSelector.promptRangeSelect([this, callback](int startPos, int endPos) -> void {
return callback(positionToSample(float(startPos)), positionToSample(float(endPos)));
});
}
void SampleEditor::cancelBoundsSelection()
{
overlay.setEnabled(true);
boundsSelector.cancelRangeSelect();
}
bool SampleEditor::isInBoundsSelection() const
{
return boundsSelector.isSelectingRange();
}