Optimize more by avoiding floating point

This commit is contained in:
Dejvino 2026-03-01 14:26:18 +01:00
parent 8cc8898c01
commit 534a7512c4
3 changed files with 32 additions and 27 deletions

View File

@ -12,7 +12,7 @@ const int I2S_LRC_PIN = 10; // Left-Right Clock (GP10)
const int I2S_DOUT_PIN = 11; // Data Out (GP11)
// Audio parameters
const int SAMPLE_RATE = 44100 / 2 / 2 / 2;
const int SAMPLE_RATE = 44100 / 4;
const int16_t AMPLITUDE = 16383 / 2; // Use a lower amplitude to avoid clipping (max is 32767 for 16-bit)
// Create an I2S output object

View File

@ -28,6 +28,7 @@ SynthEngine::SynthEngine(uint32_t sampleRate)
_volume(0.5f),
_waveform(SAWTOOTH),
_isGateOpen(false),
_freqToPhaseInc(0.0f),
_rngState(12345)
{
fill_sine_table();
@ -35,6 +36,7 @@ SynthEngine::SynthEngine(uint32_t sampleRate)
setFrequency(440.0f);
// Initialize SINK
_freqToPhaseInc = 4294967296.0f / (float)_sampleRate;
grid[GRID_W / 2][GRID_H - 1].type = GridCell::SINK;
rebuildProcessingOrder();
}
@ -92,6 +94,7 @@ int SynthEngine::importGrid(const uint8_t* buffer, size_t size) {
c.rotation = 0;
c.value = 0.0f;
c.phase = 0.0f;
c.phase_accumulator = 0;
c.next_value = 0.0f;
}
}
@ -127,6 +130,7 @@ void SynthEngine::clearGrid() {
c.rotation = 0;
c.value = 0.0f;
c.phase = 0.0f;
c.phase_accumulator = 0;
c.next_value = 0.0f;
}
}
@ -446,45 +450,45 @@ float SynthEngine::processGridStep() {
// Gather inputs for modulation
float mod = getInputFromTheBack(x, y, c);
// Freq 10 to 1000 Hz
// Freq 10 to 1000 Hz.
float freq = 10.0f + c.param * 990.0f + (mod * 500.0f); // FM
if (freq < 1.0f) freq = 1.0f;
float inc = freq * (float)SINE_TABLE_SIZE / (float)_sampleRate;
c.phase += inc;
if (c.phase >= SINE_TABLE_SIZE) c.phase -= SINE_TABLE_SIZE;
val = (float)sine_table[(int)c.phase] / 32768.0f;
// Fixed point phase accumulation
uint32_t inc = (uint32_t)(freq * _freqToPhaseInc);
c.phase_accumulator += inc;
// Top 8 bits of 32-bit accumulator form the 256-entry table index
val = (float)sine_table[c.phase_accumulator >> 24] / 32768.0f;
val *= getSideInputGain(x, y, c);
} else if (c.type == GridCell::INPUT_OSCILLATOR) {
float mod = getInputFromTheBack(x, y, c);
// Freq based on current note + octave param (1-5)
float baseFreq = getFrequency();
int octave = 1 + (int)(c.param * 4.99f); // Map 0.0-1.0 to 1-5
float freq = baseFreq * (float)(1 << (octave - 1)); // 2^(octave-1)
freq += (mod * 500.0f); // Apply FM
if (freq < 1.0f) freq = 1.0f; // Protect against negative/zero freq
float inc = freq * (float)SINE_TABLE_SIZE / (float)_sampleRate;
c.phase += inc;
if (c.phase >= SINE_TABLE_SIZE) c.phase -= SINE_TABLE_SIZE;
val = (float)sine_table[(int)c.phase] / 32768.0f;
// Use the engine's global increment directly to avoid float conversion round-trip
uint32_t baseInc = _increment;
uint32_t inc = baseInc << (octave - 1);
// Apply FM (mod is float, convert to fixed point increment)
inc += (int32_t)(mod * 500.0f * _freqToPhaseInc);
c.phase_accumulator += inc;
val = (float)sine_table[c.phase_accumulator >> 24] / 32768.0f;
val *= getSideInputGain(x, y, c);
} else if (c.type == GridCell::WAVETABLE) {
float mod = getInputFromTheBack(x, y, c);
// Track current note frequency + FM
float freq = getFrequency() + (mod * 500.0f);
if (freq < 1.0f) freq = 1.0f;
// Track current note frequency + FM. Use direct increment for speed.
uint32_t inc = _increment + (int32_t)(mod * 500.0f * _freqToPhaseInc);
c.phase_accumulator += inc;
float inc = freq * (float)SINE_TABLE_SIZE / (float)_sampleRate;
c.phase += inc;
if (c.phase >= SINE_TABLE_SIZE) c.phase -= SINE_TABLE_SIZE;
float phase_norm = c.phase / (float)SINE_TABLE_SIZE; // 0.0 to 1.0
// 0.0 to 1.0 representation for math-based waveforms
float phase_norm = (float)c.phase_accumulator / 4294967296.0f;
int wave_select = (int)(c.param * 7.99f);
switch(wave_select) {
case 0: val = (float)sine_table[(int)c.phase] / 32768.0f; break;
case 0: val = (float)sine_table[c.phase_accumulator >> 24] / 32768.0f; break;
case 1: val = (phase_norm * 2.0f) - 1.0f; break; // Saw
case 2: val = (phase_norm < 0.5f) ? 1.0f : -1.0f; break; // Square
case 3: val = (phase_norm < 0.5f) ? (phase_norm * 4.0f - 1.0f) : (3.0f - phase_norm * 4.0f); break; // Triangle
@ -535,11 +539,10 @@ float SynthEngine::processGridStep() {
} else if (c.type == GridCell::LFO) {
// Low Frequency Oscillator (0.1 Hz to 20 Hz)
float freq = 0.1f + c.param * 19.9f;
float inc = freq * (float)SINE_TABLE_SIZE / (float)_sampleRate;
c.phase += inc;
if (c.phase >= SINE_TABLE_SIZE) c.phase -= SINE_TABLE_SIZE;
uint32_t inc = (uint32_t)(freq * _freqToPhaseInc);
c.phase_accumulator += inc;
// Output full range -1.0 to 1.0
val = (float)sine_table[(int)c.phase] / 32768.0f;
val = (float)sine_table[c.phase_accumulator >> 24] / 32768.0f;
} else if (c.type == GridCell::FORK) {
// Sum inputs from "Back" (Input direction)
val = getInputFromTheBack(x, y, c);

View File

@ -107,6 +107,7 @@ public:
float value = 0.0f; // Current output sample
float next_value = 0.0f; // For double-buffering in processGridStep
float phase = 0.0f; // For Oscillator, Noise state
uint32_t phase_accumulator = 0; // For Oscillators (Fixed point optimization)
};
static const int GRID_W = 12;
@ -131,6 +132,7 @@ private:
uint32_t _increment; // Phase increment per sample, determines frequency.
float _volume;
Waveform _waveform;
float _freqToPhaseInc; // Pre-calculated constant for frequency to phase increment conversion
bool _isGateOpen;
uint32_t _rngState;
std::vector<std::pair<int, int>> _processing_order;