SolarOS

SolarOS 4.17.7 manual · api

Digital signal processing

Use solaros.dsp to scale, mix, filter, or analyze signed 16-bit samples from Python or Lua. Create a FIR, decimator, or FFT processor for repeated work, and close it when finished. Native C APIs are listed below.

SolarOS selects acceleration automatically where available on ESP32-S3. The same API works with the portable backend on other boards.

Numeric contract

FIR coefficient zero multiplies the newest sample. A decimator filters every input sample and emits the filtered value at phase zero. Its history and phase continue across calls until reset.

Native API

Include solar_os_dsp.h. Stateless operations are:

solar_os_dsp_dot_s16(a, b, count, &dot);
solar_os_dsp_gain_q15(output, input, count, gain_q15);
solar_os_dsp_mix_q15(output, a, b, count, gain_a_q15, gain_b_q15);
solar_os_dsp_clip_s16(output, input, count, minimum, maximum);
solar_os_dsp_level_s16(input, count, &level);
solar_os_dsp_window_q15(output, input, window_q15, count);

Streaming processors use caller-owned opaque contexts:

solar_os_dsp_fir_create(coefficients, taps, &fir);
solar_os_dsp_fir_process(fir, output, input, count);
solar_os_dsp_fir_reset(fir);
solar_os_dsp_fir_destroy(fir);

solar_os_dsp_decimator_create(coefficients, taps, factor, &decimator);
solar_os_dsp_decimator_process(decimator, output, capacity,
                               input, count, &produced);
solar_os_dsp_decimator_destroy(decimator);

solar_os_dsp_fft_create(size, &fft);
solar_os_dsp_fft_execute(fft, spectrum, input, &scale_exponent);
solar_os_dsp_fft_destroy(fft);

FIR filters support 1 through 1024 taps. FFT sizes must be powers of two from 2 through 4096. One context must not be used concurrently by multiple callers.

Use solar_os_dsp_backend(), solar_os_dsp_capabilities(), and solar_os_dsp_accelerated_capabilities() for diagnostics. Capability bits are:

BitOperation
0signed 16-bit dot product
1Q15 gain
2Q15 mix
3signed 16-bit clip
4signed 16-bit level
5Q15 window
6Q15 FIR
7Q15 decimator
8signed 16-bit FFT, PIE accelerated on eligible ESP32-S3 blocks

Python

Python accepts contiguous buffer objects containing native little-endian signed 16-bit samples. array('h') and bytearray objects are suitable. Stateless output functions return a new bytearray:

from array import array
import solaros

dsp = solaros.dsp
samples = array('h', [1000, -1000, 500, -500])

scaled = dsp.gain(samples, 0.5)
mixed = dsp.mix(samples, samples, 0.7, 0.3)
clipped = dsp.clip(samples, -800, 800)
peak, rms = dsp.level(samples)
energy = dsp.dot(samples, samples)

window(samples, coefficients) returns a new sample buffer. Both inputs must contain the same number of samples. Gains use floating-point values from -1.0 through 1.0 and are converted to Q15.

Stateful processors own their native context and release it during close() or garbage collection:

coefficients = array('h', [16384, 16384])
filt = dsp.fir(coefficients)
filtered = filt.process(samples)

downsample = dsp.fir(coefficients, decimation=4)
low_rate = downsample.process(samples)
downsample.reset()

fft = dsp.fft(1024)
spectrum, exponent = fft.execute(array('h', [0] * 1024))
fft.close()

The script bridge accepts at most 32768 samples per call.

Lua

Lua uses binary strings containing native little-endian signed 16-bit values. Stateless functions return a new binary string. FIR and FFT constructors return userdata that own the native context:

local dsp = solaros.dsp
local scaled = dsp.gain(samples, 0.5)
local peak, rms = dsp.level(samples)

local filt = dsp.fir(coefficients, 4)
local low_rate = filt:process(samples)
filt:reset()
filt:close()

local fft = dsp.fft(1024)
local spectrum, exponent = fft:execute(samples_1024)

Lua strings are immutable, so each output operation allocates a new string. The script bridge accepts at most 32768 samples per call.

Quick reference

Use service.dsp for synchronous fixed-point block processing. The service selects portable or ESP32-S3 PIE code without application-side board checks. Callers own streaming contexts and must destroy or close them. Python uses signed 16-bit buffer objects; Lua uses binary strings. The native Synth service uses level for its captured PCM scope blocks and publishes the resulting peak and RMS values through Synth status.

service.signal-widgets builds reusable oscilloscope and spectrum views on top of solar_os_gfx and this DSP API. Its submit calls copy recent mono or interleaved signed-16-bit PCM into thread-safe snapshots. Rendering the spectrum applies a Hann window and a 512-point FFT; the DSP service selects PIE SIMD on ESP32-S3 without widget-side board checks.