@Christoph-Hart
Yep, took it down immediately realising the sillyness!
This is a good solution now, it seems to work fine.
I've not done resampling before, I was tying myself in knots. Best to make something functional and robust before looking at optimisations.
This seems to work so I'll leave it here in case anyone needed to see the interpolation method. I need to do more learning myself
// ==========================| Sampler v0.58 : by Griffinboy |==========================
#pragma once
#include <JuceHeader.h>
#include <array>
namespace project
{
using namespace juce;
using namespace hise;
using namespace scriptnode;
// Template class for a sampler with a specified number of voices (NV)
template <int NV>
struct Griffin_Sampler : public data::base
{
SNEX_NODE(Griffin_Sampler);
struct MetadataClass
{
SN_NODE_ID("Griffin_Sampler");
};
// ==========================| Node Properties |==========================
// Define properties of the node
static constexpr bool isModNode() { return true; } // Indicates this is a modulation node
static constexpr bool isPolyphonic() { return NV > 1; } // Determines if the sampler is polyphonic
static constexpr bool hasTail() { return false; } // Indicates if the node has a tail (sustain)
static constexpr bool isSuspendedOnSilence() { return false; } // Determines if processing is suspended on silence
static constexpr int getFixChannelAmount() { return 2; } // Fixed number of audio channels
// Define the number of various components
static constexpr int NumTables = 0;
static constexpr int NumSliderPacks = 0;
static constexpr int NumAudioFiles = 1;
static constexpr int NumFilters = 0;
static constexpr int NumDisplayBuffers = 0;
// ==========================| Global Variables |==========================
using InterpolatorType = index::hermite<index::unscaled<double, index::clamped<0, false>>>;
static const int NUM_CHANNELS = 2; // Number of audio channels
ExternalData data; // External data for the sampler
span<dyn<float>, NUM_CHANNELS> sample; // Sample data for each channel
double sr = 0.0; // Sample rate
double file_sr = 0.0; // File sample rate
// Looping and playback parameters
double loopStart = 0.0;
double loopEnd = 1.0;
bool loopEnabled = false;
double pitchModulation = 0.0;
double speedMultiplier = 1.0;
// Structure to hold voice-specific data
struct VoiceData
{
double uptime = 0.0; // Time since the voice started playing
double delta = 1.0 / (1 << 28); // Increment per sample
int midiNote = 60; // MIDI note number
bool isLooping = false; // Looping state
bool isPlaying = false; // Playback state
// Resets the voice data
void reset() { uptime = 0.0; isPlaying = true; }
// Advances the playback position and handles looping
double bump(double loopStart, double loopEnd, double numSamples)
{
if (!isPlaying) return numSamples;
double rv = uptime;
uptime += delta;
if (isLooping && uptime >= loopEnd && loopEnd > loopStart)
{
double loopLength = loopEnd - loopStart;
uptime = loopStart + std::fmod(uptime - loopStart, loopLength);
}
else if (uptime >= numSamples)
{
uptime = numSamples;
isPlaying = false;
}
return rv;
}
};
PolyData<VoiceData, NV> voiceData; // Container for voice data
ModValue gate; // Modulation value for gate
std::array<float, 128> pitchRatios; // Precomputed pitch ratios for MIDI notes
// ==========================| Prepare |==========================
// Prepares the sampler for playback
void prepare(PrepareSpecs specs)
{
voiceData.prepare(specs);
sr = specs.sampleRate;
initPitchRatios();
if (data.numSamples > 0 && sr != 0.0)
{
updateAllVoiceDeltas();
}
validateLoopPoints();
}
// Initializes pitch ratios for MIDI notes
void initPitchRatios()
{
constexpr int rootNote = 60; // Middle C
for (int i = 0; i < 128; ++i)
pitchRatios[i] = std::pow(2.0f, (i - rootNote) / 12.0f);
}
// Validates and adjusts loop points
void validateLoopPoints()
{
if (data.numSamples > 0)
{
loopStart = juce::jlimit(0.0, 1.0, loopStart);
loopEnd = juce::jlimit(loopStart, 1.0, loopEnd);
}
else
{
loopStart = 0.0;
loopEnd = 1.0;
}
}
// ==========================| Reset |==========================
// Resets all voices
void reset()
{
for (auto& v : voiceData)
v.reset();
}
// ==========================| Process |==========================
// Processes audio data
template <typename ProcessDataType>
void process(ProcessDataType& data)
{
int numSamples = data.getNumSamples();
if (numSamples == 0 || this->data.numSamples == 0)
return;
DataReadLock sl(this->data);
auto& v = voiceData.get();
auto& fixData = data.template as<ProcessData<NUM_CHANNELS>>();
auto audioBlock = fixData.toAudioBlock();
processBlock(audioBlock, v, numSamples);
this->data.setDisplayedValue(v.uptime);
gate.setModValueIfChanged(static_cast<double>(v.isPlaying));
}
// Processes a block of audio samples
void processBlock(juce::dsp::AudioBlock<float>& audioBlock, VoiceData& v, int numSamples)
{
auto* leftChannelData = audioBlock.getChannelPointer(0);
auto* rightChannelData = audioBlock.getChannelPointer(1);
double numSamplesDouble = static_cast<double>(data.numSamples);
double loopStartSamples = loopStart * numSamplesDouble;
double loopEndSamples = loopEnd * numSamplesDouble;
for (int i = 0; i < numSamples; ++i)
{
double thisUptime = v.bump(loopStartSamples, loopEndSamples, numSamplesDouble);
if (v.isPlaying)
{
InterpolatorType idx(thisUptime);
leftChannelData[i] = sample[0][idx];
rightChannelData[i] = sample[1][idx];
}
else
{
leftChannelData[i] = 0.0f;
rightChannelData[i] = 0.0f;
}
}
}
template <typename FrameDataType>
void processFrame(FrameDataType& fd)
{
// Implement if needed
}
// ==========================| Set Parameter |==========================
// Sets a parameter value and validates if necessary
template <int P>
void setParameter(double v)
{
bool needsValidation = false;
if (P == 0)
{
loopStart = v;
needsValidation = true;
}
else if (P == 1)
{
loopEnd = v;
needsValidation = true;
}
else if (P == 2)
{
loopEnabled = v > 0.5;
}
else if (P == 3)
{
pitchModulation = v;
updateAllVoiceDeltas();
}
else if (P == 4)
{
speedMultiplier = v;
updateAllVoiceDeltas();
}
if (needsValidation)
{
validateLoopPoints();
}
}
// ==========================| Create Parameters |==========================
// Creates and registers parameters for the sampler
void createParameters(ParameterDataList& data)
{
{
parameter::data p("Loop Start", { 0.0, 1.0, 0.01 });
registerCallback<0>(p);
p.setDefaultValue(0.0);
data.add(std::move(p));
}
{
parameter::data p("Loop End", { 0.0, 1.0, 0.01 });
registerCallback<1>(p);
p.setDefaultValue(1.0);
data.add(std::move(p));
}
{
parameter::data p("Loop Enable", { 0.0, 1.0, 1.0 });
registerCallback<2>(p);
p.setDefaultValue(0.0);
data.add(std::move(p));
}
{
parameter::data p("Pitch", { -12.0, 12.0, 0.001 });
registerCallback<3>(p);
p.setDefaultValue(0.0);
data.add(std::move(p));
}
{
parameter::data p("Speed", { 0.5, 2.0, 0.01 });
registerCallback<4>(p);
p.setDefaultValue(1.0);
data.add(std::move(p));
}
}
// ==========================| External Data |==========================
// Sets external data for the sampler
void setExternalData(const ExternalData& ed, int index)
{
data = ed;
ed.referBlockTo(sample[0], 0);
ed.referBlockTo(sample[1], 1);
if (data.numSamples > 0 && sr != 0.0)
{
updateAllVoiceDeltas();
validateLoopPoints();
}
}
// ==========================| Handle HISE Event |==========================
// Handles MIDI events for note on and note off
void handleHiseEvent(HiseEvent& e)
{
if (e.isNoteOn())
{
auto& v = voiceData.get();
v.midiNote = e.getNoteNumber();
v.delta = calculateDelta(v.midiNote);
v.reset();
v.isLooping = loopEnabled;
}
else if (e.isNoteOff())
{
auto& v = voiceData.get();
if (v.midiNote == e.getNoteNumber())
{
v.isPlaying = false;
}
}
}
// ==========================| Modulation Slot |==========================
// Handles modulation changes
bool handleModulation(double& value)
{
return gate.getChangedValue(value);
}
// ==========================| Helper Functions |==========================
// Updates the delta for all voices based on current parameters
void updateAllVoiceDeltas()
{
for (auto& v : voiceData)
{
v.delta = calculateDelta(v.midiNote);
}
}
// Calculates the delta for a given MIDI note
double calculateDelta(int midiNote)
{
double pitchRatio = std::pow(2.0, pitchModulation / 12.0);
return (data.sampleRate / sr) * pitchRatios[midiNote] * pitchRatio * speedMultiplier;
}
};
}