Lind of you to say so but I've not done anywhere near as much as I would have liked to. There's still a lot to do.
SampleGrid3 BETA 1
- magmavander
- Posts: 722
- Joined: Tue Nov 22, 2011 5:22 pm
- Location: France
- Contact:
Re: SampleGrid3 BETA 1
This is a killer machine and you expanded it to be a Masterpiece.Lind of you to say so but I've not done anywhere near as much as I would have liked to. There's still a lot to do.
Re: SampleGrid3 BETA 1
Is there a 64 bit version of Samplegrid by any chance?
I can't remember if it is only 32 bit.
I can't remember if it is only 32 bit.
Re: SampleGrid3 BETA 1
Cheeky question to tack onto this thread, probably for IXix really....
I use Peerctl a *lot* and when I'm performing I have a Behringer controller so with everything is mapped to knobs that's the focus but when I write on the laptop I tend to need the 'Mixer GUI' function of Peerctl a lot. It pains me that opening it always requires a right-click on the mouse, fiddle down the options and select. Do you think it's possible (easy) to create a machine which would only open that window on double-click? Without delving fully into how Peerctl is written I wonder if this would be a simple way to achieve what I need...My other bane is that the 'Mixer GUI' window doesn't resize at all properly on modern size desktops but that's for another time or perhaps a proper rewrite
I use Peerctl a *lot* and when I'm performing I have a Behringer controller so with everything is mapped to knobs that's the focus but when I write on the laptop I tend to need the 'Mixer GUI' function of Peerctl a lot. It pains me that opening it always requires a right-click on the mouse, fiddle down the options and select. Do you think it's possible (easy) to create a machine which would only open that window on double-click? Without delving fully into how Peerctl is written I wonder if this would be a simple way to achieve what I need...My other bane is that the 'Mixer GUI' window doesn't resize at all properly on modern size desktops but that's for another time or perhaps a proper rewrite
Re: SampleGrid3 BETA 1
I haven't really gone into the PeerCtrl code, it's mostly a mystery to me.
Not likely to look anytime soon either I'm afraid. Probably quicker to learn how to code it yourself than wait for me to get around to it.
Not likely to look anytime soon either I'm afraid. Probably quicker to learn how to code it yourself than wait for me to get around to it.
Re: SampleGrid3 BETA 1
No worries, that's entirely fair enough. I may just do that! 
Re: SampleGrid3 BETA 1
Are the changes George applied to Matilde tracker relevant to SampleGrid and possible there too? Perhaps it was written with better sample handling from the start?
viewtopic.php?p=17783
viewtopic.php?p=17783
Re: SampleGrid3 BETA 1
Did SampleGrid sources never make it up to the Sourceforge page? Is there a reason for that?
Re: SampleGrid3 BETA 1
I don't know tbh. The sample handling code in SGrid is a mystery to me. All I ever did was minor tinkering.thepedal wrote: ↑Tue Oct 21, 2025 8:02 pm Are the changes George applied to Matilde tracker relevant to SampleGrid and possible there too? Perhaps it was written with better sample handling from the start?
viewtopic.php?p=17783
SGrid 2 is on there. SampleGrid 3 isn't but there's no real reason for that. I could make it available.Did SampleGrid sources never make it up to the Sourceforge page? Is there a reason for that?
Really at some point I should move the BTD stuff over to GitHub, just haven't got around to it yet.
Re: SampleGrid3 BETA 1
Would be cool if you could one day get the BTD stuff over to GitHub. I tried for a while to use Claude AI to either create a modern replica of PeerCtrl or at least tinker with the bits of it that annoy me but it was a nightmare. With a mind to get it working in ReBuzz. I'm no programmer, especially where Microsoft libs are concerned and it just spat out tons of uncompilable guff. By the time I'd worked through all the really dumb errors all I ended up with in both cases were some binaries that did nothing. Proper waste of time and after a few weeks on it I realised I'm much better off just making tunes and leaving this stuff to those who really know what they are doing
This ai coding stuff is improving all the time though and I'll probably try it again sometime when I'm out of musical inspiration again. PeerCtrl is just such an incredible tool when you have a large midi controller and the tweaks I'm after for now are not that major really.
Re: SampleGrid3 BETA 1
claude code etc can actually do very nice buzz machines if you tell them so. give source code of existing buzz machines for reference.
for example, give it this: https://jeskola.net/buzz/beta/files/dev ... /Limiter.h and MachineInterface.h and ask it to make another machine based on it. works quite well in my experience.
for example, give it this: https://jeskola.net/buzz/beta/files/dev ... /Limiter.h and MachineInterface.h and ask it to make another machine based on it. works quite well in my experience.
Re: SampleGrid3 BETA 1
oskari wrote: ↑Thu Oct 30, 2025 3:12 pm claude code etc can actually do very nice buzz machines if you tell them so. give source code of existing buzz machines for reference.
for example, give it this: https://jeskola.net/buzz/beta/files/dev ... /Limiter.h and MachineInterface.h and ask it to make another machine based on it. works quite well in my experience.
Re: SampleGrid3 BETA 1
did you try it yourself?
here's what i got when said the above
here's what i got when said the above
Code: Select all
// FDN Reverb - Modern Feedback Delay Network Reverb for Buzz
// Implements Hadamard mixing, modulated delays, and multi-stage diffusion
#include <windef.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define _USE_MATH_DEFINES
#include <math.h>
#include "MachineInterface.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
// Modern FDN Reverb with:
// - Hadamard feedback matrix for diffuse reflections
// - Modulated delay lines to reduce metallic artifacts
// - Multi-stage diffusers for early reflections
// - Tone control via damping filters
#define FDN_DELAYS 8
#define MAX_DELAY_SAMPLES 96000 // 2 seconds at 48kHz
#define DIFFUSER_STAGES 4
#define MAX_PREDELAY_SAMPLES 9600 // 100ms at 96kHz
// Parameters
#define PARAM_SIZE 0
#define PARAM_DAMPING 1
#define PARAM_MIX 2
#define PARAM_PREDELAY 3
// Simple one-pole filter for tone shaping
class OnePole
{
public:
OnePole() : z1(0.0f), coeff(0.0f) {}
void SetCutoff(float frequency, float sampleRate)
{
coeff = expf(-2.0f * M_PI * frequency / sampleRate);
}
float Process(float input)
{
z1 = input + coeff * (z1 - input);
return z1;
}
void Reset() { z1 = 0.0f; }
private:
float z1;
float coeff;
};
// Allpass diffuser for building dense early reflections
class Allpass
{
public:
Allpass() : writePos(0), delayLength(0), gain(0.7f)
{
for (int i = 0; i < 8192; i++)
buffer[i] = 0.0f;
}
void SetDelay(int samples, float g = 0.7f)
{
delayLength = std::min(samples, 8191);
gain = g;
}
float Process(float input)
{
int readPos = (writePos - delayLength) & 8191;
float delayed = buffer[readPos];
float output = -gain * input + delayed;
buffer[writePos] = input + gain * output;
writePos = (writePos + 1) & 8191;
return output;
}
void Reset()
{
for (int i = 0; i < 8192; i++)
buffer[i] = 0.0f;
writePos = 0;
}
private:
float buffer[8192];
int writePos;
int delayLength;
float gain;
};
// Modulated delay line with interpolation
class ModulatedDelay
{
public:
ModulatedDelay() : writePos(0), delayLength(0), modPhase(0.0f), modDepth(0), modRate(0.0f)
{
buffer = new float[MAX_DELAY_SAMPLES];
for (int i = 0; i < MAX_DELAY_SAMPLES; i++)
buffer[i] = 0.0f;
}
~ModulatedDelay()
{
delete[] buffer;
}
void SetDelay(int samples)
{
delayLength = std::min(samples, MAX_DELAY_SAMPLES - 1);
}
void SetModulation(float rate, int depth)
{
modRate = rate;
modDepth = std::min(depth, delayLength / 4);
}
void Write(float input)
{
buffer[writePos] = input;
writePos++;
if (writePos >= MAX_DELAY_SAMPLES)
writePos = 0;
}
float Read()
{
// Add LFO modulation
float modOffset = modDepth * sinf(modPhase);
modPhase += modRate;
if (modPhase > 2.0f * M_PI)
modPhase -= 2.0f * M_PI;
float readPosFloat = writePos - delayLength - modOffset;
if (readPosFloat < 0)
readPosFloat += MAX_DELAY_SAMPLES;
// Linear interpolation
int readPos0 = (int)readPosFloat;
int readPos1 = (readPos0 + 1) % MAX_DELAY_SAMPLES;
float frac = readPosFloat - readPos0;
return buffer[readPos0] * (1.0f - frac) + buffer[readPos1] * frac;
}
void Reset()
{
for (int i = 0; i < MAX_DELAY_SAMPLES; i++)
buffer[i] = 0.0f;
writePos = 0;
modPhase = 0.0f;
}
private:
float *buffer;
int writePos;
int delayLength;
float modPhase;
int modDepth;
float modRate;
};
// Parameter structure
#pragma pack(1)
struct Params
{
byte size; // 0-255 (decay time)
byte damping; // 0-255 (high frequency absorption)
byte mix; // 0-255 (dry/wet mix)
byte predelay; // 0-255 (predelay in ms)
};
#pragma pack()
// Parameter definitions
CMachineParameter const paraSize =
{
pt_byte,
"Size",
"Decay Time (0=short, FF=long)",
0,
255,
0xFF,
MPF_STATE,
127
};
CMachineParameter const paraDamping =
{
pt_byte,
"Damping",
"High Frequency Absorption (0=bright, FF=dark)",
0,
255,
0xFF,
MPF_STATE,
127
};
CMachineParameter const paraMix =
{
pt_byte,
"Mix",
"Dry/Wet Mix (0=dry, FF=wet)",
0,
255,
0xFF,
MPF_STATE,
64
};
CMachineParameter const paraPredelay =
{
pt_byte,
"Predelay",
"Initial Delay (0-FF ms)",
0,
255,
0xFF,
MPF_STATE,
0
};
CMachineParameter const *pParameters[] =
{
¶Size,
¶Damping,
¶Mix,
¶Predelay
};
#pragma pack(1)
CMachineInfo const MacInfo =
{
MT_EFFECT,
MI_VERSION,
MIF_STEREO_EFFECT,
0,
0,
4,
0,
pParameters,
0,
NULL,
"FDN Reverb",
"FDNReverb",
"Modern FDN Reverb",
NULL,
NULL
};
#pragma pack()
// Machine implementation
class mi : public CMachineInterface
{
public:
mi();
virtual ~mi();
virtual void Init(CMachineDataInput * const pi);
virtual void Tick();
virtual bool Work(float *psamples, int numsamples, int const mode);
virtual void Stop();
virtual void SetNumTracks(int const n) {}
virtual void Command(int const i);
virtual char const *DescribeValue(int const param, int const value);
private:
void HadamardMix(float *input, float *output);
void UpdateParameters();
Params params;
ModulatedDelay delays[FDN_DELAYS];
OnePole dampingFilters[FDN_DELAYS];
Allpass inputDiffuserL[DIFFUSER_STAGES];
Allpass inputDiffuserR[DIFFUSER_STAGES];
float feedback[FDN_DELAYS];
float mix;
float size;
float damping;
float *predelayBufferL;
float *predelayBufferR;
int predelayPos;
int predelayLength;
float sampleRate;
bool paramChanged;
};
// DLL exports
DLL_EXPORTS
mi::mi()
{
GlobalVals = ¶ms;
TrackVals = NULL;
AttrVals = NULL;
predelayBufferL = new float[MAX_PREDELAY_SAMPLES];
predelayBufferR = new float[MAX_PREDELAY_SAMPLES];
for (int i = 0; i < MAX_PREDELAY_SAMPLES; i++)
{
predelayBufferL[i] = 0.0f;
predelayBufferR[i] = 0.0f;
}
predelayPos = 0;
predelayLength = 0;
paramChanged = false;
}
mi::~mi()
{
delete[] predelayBufferL;
delete[] predelayBufferR;
}
void mi::Init(CMachineDataInput * const pi)
{
sampleRate = (float)pMasterInfo->SamplesPerSec;
// Prime number delays for FDN (in samples at 48kHz)
int baseDelays[FDN_DELAYS] = { 3001, 3203, 3407, 3617, 3823, 4027, 4229, 4441 };
for (int i = 0; i < FDN_DELAYS; i++)
{
delays[i].SetDelay((int)(baseDelays[i] * sampleRate / 48000.0f));
delays[i].SetModulation(0.0001f + i * 0.00005f, 8);
dampingFilters[i].SetCutoff(4000.0f, sampleRate);
}
// Early reflection diffusers
int diffuserDelays[DIFFUSER_STAGES] = { 142, 107, 379, 277 };
for (int i = 0; i < DIFFUSER_STAGES; i++)
{
inputDiffuserL[i].SetDelay((int)(diffuserDelays[i] * sampleRate / 48000.0f), 0.65f);
inputDiffuserR[i].SetDelay((int)((diffuserDelays[i] + 11) * sampleRate / 48000.0f), 0.65f);
}
// Set default parameters
params.size = 127;
params.damping = 127;
params.mix = 64;
params.predelay = 0;
UpdateParameters();
}
void mi::Tick()
{
// Check for parameter changes
if (params.size != 0xFF)
paramChanged = true;
if (params.damping != 0xFF)
paramChanged = true;
if (params.mix != 0xFF)
paramChanged = true;
if (params.predelay != 0xFF)
paramChanged = true;
}
void mi::UpdateParameters()
{
// Size (decay time)
if (params.size != 0xFF)
{
size = params.size / 255.0f;
// Map size to feedback gain
for (int i = 0; i < FDN_DELAYS; i++)
{
float variance = 0.98f + (i * 0.005f);
feedback[i] = size * 0.95f * variance;
}
}
// Damping (high frequency absorption)
if (params.damping != 0xFF)
{
damping = params.damping / 255.0f;
float cutoff = 1000.0f + (1.0f - damping) * 14000.0f;
for (int i = 0; i < FDN_DELAYS; i++)
dampingFilters[i].SetCutoff(cutoff, sampleRate);
}
// Mix (dry/wet)
if (params.mix != 0xFF)
{
mix = params.mix / 255.0f;
}
// Predelay
if (params.predelay != 0xFF)
{
float milliseconds = params.predelay * 100.0f / 255.0f; // 0-100ms
predelayLength = std::min((int)(milliseconds * sampleRate / 1000.0f), MAX_PREDELAY_SAMPLES - 1);
}
paramChanged = false;
}
bool mi::Work(float *psamples, int numsamples, int const mode)
{
if (paramChanged)
UpdateParameters();
if (mode == WM_NOIO)
return false;
if (mode == WM_READ)
return false;
float *pin = psamples;
float *pout = psamples;
for (int i = 0; i < numsamples; i++)
{
float dryL = pin[i * 2];
float dryR = pin[i * 2 + 1];
// Predelay
int predelayReadPos = predelayPos - predelayLength;
if (predelayReadPos < 0)
predelayReadPos += MAX_PREDELAY_SAMPLES;
float inputL = predelayBufferL[predelayReadPos];
float inputR = predelayBufferR[predelayReadPos];
predelayBufferL[predelayPos] = dryL;
predelayBufferR[predelayPos] = dryR;
predelayPos = (predelayPos + 1) % MAX_PREDELAY_SAMPLES;
// Input diffusion
float diffusedL = inputL;
float diffusedR = inputR;
for (int d = 0; d < DIFFUSER_STAGES; d++)
{
diffusedL = inputDiffuserL[d].Process(diffusedL);
diffusedR = inputDiffuserR[d].Process(diffusedR);
}
// Read from delay lines
float delayOutputs[FDN_DELAYS];
for (int d = 0; d < FDN_DELAYS; d++)
delayOutputs[d] = delays[d].Read();
// Hadamard feedback matrix
float mixed[FDN_DELAYS];
HadamardMix(delayOutputs, mixed);
// Apply feedback, damping, and write back
for (int d = 0; d < FDN_DELAYS; d++)
{
float feedbackSignal = mixed[d] * feedback[d];
feedbackSignal = dampingFilters[d].Process(feedbackSignal);
// Inject input into first 4 lines (L) and last 4 (R)
if (d < 4)
feedbackSignal += diffusedL * 0.25f;
else
feedbackSignal += diffusedR * 0.25f;
delays[d].Write(feedbackSignal);
}
// Output: sum delay lines to stereo
float outL = 0.0f, outR = 0.0f;
for (int d = 0; d < FDN_DELAYS / 2; d++)
{
outL += delayOutputs[d] + delayOutputs[d + 4];
outR += delayOutputs[d] - delayOutputs[d + 4];
}
outL *= 0.25f;
outR *= 0.25f;
// Mix dry/wet
pout[i * 2] = dryL * (1.0f - mix) + outL * mix;
pout[i * 2 + 1] = dryR * (1.0f - mix) + outR * mix;
}
return true;
}
void mi::HadamardMix(float *input, float *output)
{
float temp[8];
// First stage (4x 2-point)
temp[0] = input[0] + input[1];
temp[1] = input[0] - input[1];
temp[2] = input[2] + input[3];
temp[3] = input[2] - input[3];
temp[4] = input[4] + input[5];
temp[5] = input[4] - input[5];
temp[6] = input[6] + input[7];
temp[7] = input[6] - input[7];
// Second stage (2x 4-point)
float temp2[8];
temp2[0] = temp[0] + temp[2];
temp2[1] = temp[1] + temp[3];
temp2[2] = temp[0] - temp[2];
temp2[3] = temp[1] - temp[3];
temp2[4] = temp[4] + temp[6];
temp2[5] = temp[5] + temp[7];
temp2[6] = temp[4] - temp[6];
temp2[7] = temp[5] - temp[7];
// Third stage (1x 8-point)
output[0] = temp2[0] + temp2[4];
output[1] = temp2[1] + temp2[5];
output[2] = temp2[2] + temp2[6];
output[3] = temp2[3] + temp2[7];
output[4] = temp2[0] - temp2[4];
output[5] = temp2[1] - temp2[5];
output[6] = temp2[2] - temp2[6];
output[7] = temp2[3] - temp2[7];
// Normalize (1/sqrt(8))
for (int i = 0; i < 8; i++)
output[i] *= 0.353553f;
}
void mi::Stop()
{
// Reset all delay lines and filters
for (int i = 0; i < FDN_DELAYS; i++)
{
delays[i].Reset();
dampingFilters[i].Reset();
}
for (int i = 0; i < DIFFUSER_STAGES; i++)
{
inputDiffuserL[i].Reset();
inputDiffuserR[i].Reset();
}
for (int i = 0; i < MAX_PREDELAY_SAMPLES; i++)
{
predelayBufferL[i] = 0.0f;
predelayBufferR[i] = 0.0f;
}
predelayPos = 0;
}
void mi::Command(int const i)
{
// No commands implemented
}
char const *mi::DescribeValue(int const param, int const value)
{
static char txt[16];
switch(param)
{
case PARAM_SIZE:
sprintf(txt, "%.1f%%", value * 100.0f / 255.0f);
break;
case PARAM_DAMPING:
sprintf(txt, "%.1f%%", value * 100.0f / 255.0f);
break;
case PARAM_MIX:
sprintf(txt, "%.1f%%", value * 100.0f / 255.0f);
break;
case PARAM_PREDELAY:
sprintf(txt, "%.1fms", value * 100.0f / 255.0f);
break;
default:
return NULL;
}
return txt;
}
Re: SampleGrid3 BETA 1
Curious how this bit works (if it does ?!). To me this reads as: 'when any of the sliders aren't at maximum value, then they've changed'
Code: Select all
// Check for parameter changes
if (params.size != 0xFF)
paramChanged = true;
if (params.damping != 0xFF)
paramChanged = true;
if (params.mix != 0xFF)
paramChanged = true;
if (params.predelay != 0xFF)
paramChanged = true;Re: SampleGrid3 BETA 1
Yeah, that's normal. Parameters have a "no value", usually 0xFF but it can be something else if you want. The params storage is reset to the null values every tick, so if it's not the null then something has changed. I do it like this though, as I find it easier to read...
Code: Select all
if(gVals.myParam != MyParam.NoValue)
{
// Do stuff
}
Re: SampleGrid3 BETA 1
Gotcha - thanks ! So is the normal parameter range: 0x00 -> 0xFE, and 0xFF is an unreachable/un-initialised value ?