This is a revival of a post I made last week.
I thought I had fixed my troubles of Aliasing, but it turns out that I had not. It's definitely some kind of mistake because it's audible, and very visible.
Ew:
I think it's maybe a misunderstanding of the Polyphonic interpolation scripts.
This sampler has certainly been giving me a headache. but I'm thankful for everyone who has helped me get this far.
Just posting in case the mistake is obvious to anyone.
Chat gpt has not been helpful with this problem.
// The aliasing is apparent when playing loud sine waves. It's not from clipping, I think it would be apparent if that was the issue
#pragma once
#include <JuceHeader.h>
#include <array>
namespace project
{
using namespace juce;
using namespace hise;
using namespace scriptnode;
template <int NV>
struct Griffin_Sampler : public data::base
{
SNEX_NODE(Griffin_Sampler);
struct MetadataClass
{
SN_NODE_ID("Griffin_Sampler");
};
// ==========================| Node Properties |==========================
static constexpr bool isModNode() { return true; }
static constexpr bool isPolyphonic() { return NV > 1; }
static constexpr bool hasTail() { return false; }
static constexpr bool isSuspendedOnSilence() { return false; }
static constexpr int getFixChannelAmount() { return 2; }
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 IndexType = index::clamped<0, false>;
using FloatIndex = index::unscaled<double, IndexType>;
using InterpolatorType = index::hermite<index::unscaled<double, index::clamped<0, false>>>;
static const int NUM_CHANNELS = 2;
ExternalData data;
span<dyn<float>, NUM_CHANNELS> sample;
double sr = 0.0;
double file_sr = 0.0;
int64_t loopStart = 0;
int64_t loopEnd = 1LL << 32;
bool loopEnabled = false;
double pitchModulation = 0.0; // Pitch modulation in semitones
double speedMultiplier = 1.0; // Speed multiplier (1 = normal, -1 = half, 2 = double)
struct VoiceData
{
int64_t uptime = 0;
int64_t delta = 1LL << 28;
int midiNote = 60;
bool isLooping = false;
void reset() { uptime = 0; }
int64_t bump(int64_t loopStart, int64_t loopEnd, int64_t numSamples)
{
int64_t rv = uptime;
uptime += delta;
if (isLooping && uptime >= loopEnd && loopEnd > loopStart)
{
int64_t loopLength = loopEnd - loopStart;
uptime = loopStart + (uptime - loopStart) % loopLength;
}
uptime = std::min(uptime, numSamples);
return rv;
}
};
PolyData<VoiceData, NV> voiceData;
ModValue gate;
// Pitch ratio lookup table
std::array<float, 128> pitchRatios;
// ==========================| Prepare |==========================
void prepare(PrepareSpecs specs)
{
voiceData.prepare(specs);
sr = specs.sampleRate;
initPitchRatios();
// Recalculate delta for all voices
if (data.numSamples > 0 && sr != 0.0)
{
updateAllVoiceDeltas();
}
validateLoopPoints();
}
void initPitchRatios()
{
constexpr int rootNote = 60; // C4
for (int i = 0; i < 128; ++i)
pitchRatios[i] = std::pow(2.0f, (i - rootNote) / 12.0f);
}
void validateLoopPoints()
{
int64_t numSamples = static_cast<int64_t>(data.numSamples) << 32;
if (numSamples > 0)
{
loopStart = juce::jlimit(0LL, numSamples - 1, loopStart);
loopEnd = juce::jlimit(loopStart + 1, numSamples, loopEnd);
}
else
{
// If there are no samples, reset loop points to default values
loopStart = 0;
loopEnd = 1LL << 32;
}
}
// ==========================| Reset |==========================
void reset()
{
for (auto& v : voiceData)
v.reset();
}
// ==========================| Process |==========================
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(static_cast<double>(v.uptime) / (1LL << 32));
auto isPlaying = v.uptime < (static_cast<int64_t>(this->data.numSamples) << 32) || v.isLooping;
gate.setModValueIfChanged(static_cast<double>(isPlaying));
}
void processBlock(juce::dsp::AudioBlock<float>& audioBlock, VoiceData& v, int numSamples)
{
auto* leftChannelData = audioBlock.getChannelPointer(0);
auto* rightChannelData = audioBlock.getChannelPointer(1);
for (int i = 0; i < numSamples; ++i)
{
int64_t thisUptime = v.bump(loopStart, loopEnd, static_cast<int64_t>(data.numSamples) << 32);
double fractionalSample = static_cast<double>(thisUptime & 0xFFFFFFFF) / (1LL << 32);
int wholeSample = static_cast<int>(thisUptime >> 32);
InterpolatorType idx(fractionalSample);
FloatIndex floatIndex(wholeSample + fractionalSample);
// Use the interpolator through the FloatIndex for both channels
leftChannelData[i] = sample[0][floatIndex]
rightChannelData[i] = sample[1][floatIndex]
}
}
// Frame-based processing (for potential single-frame calls)
template <typename FrameDataType>
void processFrame(FrameDataType& fd)
{
// Implement if needed
}
// ==========================| Set Parameter |==========================
template <int P>
void setParameter(double v)
{
int64_t numSamples = static_cast<int64_t>(data.numSamples) << 32;
bool needsValidation = false;
if (P == 0 && numSamples > 0)
{
loopStart = static_cast<int64_t>(v * numSamples);
needsValidation = true;
}
else if (P == 1 && numSamples > 0)
{
loopEnd = static_cast<int64_t>(v * numSamples);
needsValidation = true;
}
else if (P == 2)
{
loopEnabled = v > 0.5;
}
else if (P == 3)
{
pitchModulation = v; // Directly use the value as semitones
updateAllVoiceDeltas();
}
else if (P == 4)
{
speedMultiplier = v;
updateAllVoiceDeltas();
}
if (needsValidation)
{
validateLoopPoints();
}
}
// ==========================| Create Parameters |==========================
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 }); // Range: -12 to +12 semitones
registerCallback<3>(p);
p.setDefaultValue(0.0); // Default to no pitch change
data.add(std::move(p));
}
{
parameter::data p("Speed", { -1.0, 2.0, 0.01 }); // Range: -1 to 2
registerCallback<4>(p);
p.setDefaultValue(1.0); // Default to normal speed
data.add(std::move(p));
}
}
// ==========================| External Data |==========================
void setExternalData(const ExternalData& ed, int index)
{
int64_t oldNumSamples = static_cast<int64_t>(data.numSamples) << 32;
data = ed;
ed.referBlockTo(sample[0], 0);
ed.referBlockTo(sample[1], 1);
if (data.numSamples > 0 && sr != 0.0)
{
updateAllVoiceDeltas();
int64_t newNumSamples = static_cast<int64_t>(data.numSamples) << 32;
if (oldNumSamples != newNumSamples)
adjustLoopPoints(oldNumSamples, newNumSamples);
}
}
void adjustLoopPoints(int64_t oldNumSamples, int64_t newNumSamples)
{
if (oldNumSamples > 0)
{
double scaleFactor = static_cast<double>(newNumSamples) / oldNumSamples;
loopStart = static_cast<int64_t>(loopStart * scaleFactor);
loopEnd = static_cast<int64_t>(loopEnd * scaleFactor);
validateLoopPoints();
}
}
// ==========================| Handle HISE Event |==========================
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;
}
}
// ==========================| Modulation Slot |==========================
bool handleModulation(double& value)
{
return gate.getChangedValue(value);
}
// ==========================| Helper Functions |==========================
void updateAllVoiceDeltas()
{
for (auto& v : voiceData)
{
v.delta = calculateDelta(v.midiNote);
}
}
int64_t calculateDelta(int midiNote)
{
double pitchRatio = std::pow(2.0, pitchModulation / 12.0);
double speedFactor = (speedMultiplier >= 1.0) ? speedMultiplier : (1.0 / (2.0 - speedMultiplier));
return static_cast<int64_t>((data.sampleRate / sr) * pitchRatios[midiNote] * pitchRatio * speedFactor * (1LL << 32));
}
};
}