Low-Level API

The ruopus.lowlevel submodule exposes the SILK, LPC, and CELT codec layers directly, below the Opus packet layer. These are advanced building blocks for research, educational, or custom codec work. For ordinary encode/decode, prefer OpusEncoder and OpusDecoder.

import ruopus.lowlevel as ll

# Inspect what's available
print(dir(ll))

CELT Encoder and Decoder

CeltEncoder and CeltDecoder operate on raw CELT frame bodies, with no Opus TOC byte and no packet framing. They are useful for research into the MDCT layer independently.

import numpy as np
import ruopus.lowlevel as ll

enc = ll.CeltEncoder(channels=1, complexity=10, bitrate=64_000)
dec = ll.CeltDecoder(channels=1)

frame = np.zeros(960, dtype=np.float32)
body  = enc.encode(frame)           # bytes, raw CELT frame body
print(f"Encoded: {len(body)} bytes")

# CELT decoder also provides packet-loss concealment:
concealed = dec.decode_lost(frame_size=960)   # (960, 1) float32

Warning

The CeltDecoder does not expose a full decode method because CELT decoding requires the range-coder state from the encoder, which is not exported. Decoding real CELT packets should go through OpusDecoder.

SILK Encoder and Decoder

SilkEncoder operates at SILK’s internal sample rates (8, 12, or 16 kHz), not the 48 kHz Opus rate. Input PCM is int16. SilkDecoder decodes SILK bitstream bytes back to int16 PCM.

import numpy as np
import ruopus.lowlevel as ll

# 16 kHz, 20 ms frames (4 subframes)
enc = ll.SilkEncoder(fs_khz=16, nb_subfr=4, bitrate=25_000, complexity=10)

# One 20 ms frame at 16 kHz = 16000 * 0.02 = 320 samples
frame_i16 = np.zeros(320, dtype=np.int16)
payload = enc.encode(frame_i16)       # bytes, SILK bitstream

print(f"SILK payload: {len(payload)} bytes")

SILK Stereo Encoding

SilkStereoEncoder extends the mono encoder with mid-side (M/S) stereo coding:

enc_ms = ll.SilkStereoEncoder(fs_khz=16, nb_subfr=4, bitrate=40_000)
stereo_frame = np.zeros(320 * 2, dtype=np.int16)   # interleaved L/R
payload = enc_ms.encode(stereo_frame)

LPC Arithmetic

The ruopus.lowlevel module exposes the complete LPC analysis pipeline used inside SILK. These functions are useful for DSP research, feature extraction, and understanding speech codec internals.

import numpy as np
import ruopus.lowlevel as ll

signal = np.random.randn(320).astype(np.float32)

Autocorrelation

# Biased autocorrelation up to lag `order`
ac = ll.compute_autocorrelation(signal, order=16)
print(f"r[0] = {ac[0]:.4f}")   # signal energy

Levinson-Durbin

Solve the Yule-Walker equations to get LPC coefficients:

# From a pre-computed autocorrelation vector
coeffs = ll.levinson_durbin(ac)
print(coeffs)   # LpcCoefficients(order=16)
print(coeffs.coeffs)   # list of float32

Full LPC Analysis

lpc_analysis() combines autocorrelation and Levinson-Durbin in one call:

coeffs = ll.lpc_analysis(signal, order=16)
print(f"LPC order: {coeffs.order}")

Residual and Synthesis

Compute the LPC prediction residual (analysis filter) and reconstruct PCM (synthesis filter):

# Stateless: history is passed explicitly
history = np.zeros(coeffs.order, dtype=np.float32)
residual = ll.lpc_residual(signal, coeffs, history)

reconstructed = ll.lpc_synthesis(residual, coeffs, history)

# Stateful wrappers that maintain a rolling history buffer:
residual2 = ll.lpc_residual_stateful(signal, coeffs)
recon2    = ll.lpc_synthesis_stateful(residual2, coeffs)

Long-Term Prediction (LTP)

LTP models the pitch periodicity on top of the LPC short-term model:

pitch_lag = ll.estimate_pitch(signal, fs_khz=16)
print(f"Estimated pitch lag: {pitch_lag} samples")

ltp_res = ll.ltp_residual(signal, pitch_lag)
ltp_syn = ll.ltp_synthesis(ltp_res, pitch_lag)

DecControl

DecControl carries the SILK decoder’s control parameters (sample rate, frame length, and similar). It is passed to / returned from SilkDecoder:

ctrl = ll.DecControl(fs_khz=16, nb_subfr=4)
dec  = ll.SilkDecoder()
pcm  = dec.decode(silk_payload, ctrl)   # (frame_samples,) int16