MoonSpectrum

MoonBit-native scientific signal processing toolkit with FFT, spectra, windows, convolution, FIR filters, and CLI analysis.

signal-processing
fft
spectrum
dsp
science
moon add chgttyyr/MoonSpectrum@0.2.2
Download zip
Author
Version
0.2.2
License
Apache-2.0
Last updated
3 days ago
Downloads
20

Dependencies

README

#MoonSpectrum

CI

MoonSpectrum 是一个面向 MoonBit 生态的科学信号处理基础库。它提供复数运算、DFT/FFT、频谱分析、窗函数、卷积、基础 FIR 滤波和命令行分析工具,目标是补齐 MoonBit 在传感器数据、实验数据、振动信号、周期信号和教学验证中的基础算法能力。

本项目不做音频播放、采集或设备后端,也不依赖 native FFI。当前版本已覆盖 STFT、periodogram/Welch PSD、IIR 滤波、重采样、跨通道相关分析和频谱特征;后续再扩展流式输入、时间戳管线和 WebAssembly 可视化。

#Why

MoonBit 已经有一些音频、图形、矩阵和工程工具包,但通用科学信号处理方向还缺少一个小而稳的基础库。MoonSpectrum 的定位是:

  • 给 MoonBit 项目提供可复用的 FFT、频谱、窗函数、卷积和基础滤波能力。
  • 给 OSC2026 评审提供可直接运行的测试、示例数据和 CLI。
  • 给后续生态扩展留下清楚边界,而不是把音频引擎、数据框、可视化和控制系统混在一起。

#Installation

MoonSpectrum is a MoonBit module. The repository is the source distribution; the published module metadata and dependency version are recorded in moon.mod.

#From the repository

Install the current MoonBit toolchain from the official installer, then clone the repository and refresh its pinned dependency:

# Windows PowerShell Set-ExecutionPolicy RemoteSigned -Scope CurrentUser irm https://cli.moonbitlang.com/install/powershell.ps1 | iex git clone https://github.com/chgttyyr/MoonSpectrum.git cd MoonSpectrum moon update moon check --target all # For Windows native validation, install MSYS2 UCRT64 GCC and add # C:\\msys64\\ucrt64\\bin to PATH before running the all-backend commands.

# Linux or macOS curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash git clone https://github.com/chgttyyr/MoonSpectrum.git cd MoonSpectrum moon update moon check --target all

The competition notice recommended MoonBit 0.10.3. The CLI package keeps the backward-compatible options("is-main": true) declaration, so it works with the committee toolchain and newer official releases. Because newer formatters try to migrate that legacy declaration, CI uses scripts/check_format.py: it runs the official formatter against every .mbt source and structurally checks the intentional legacy package file. CI prints moon version --all so the exact compiler is visible. If starting a new copy instead of using the committed moon.mod, the equivalent dependency command is moon add moonbitlang/x@0.4.45.

#Features

  • Complex 复数类型:加减乘、缩放、幅值、近似比较。
  • DFT / radix-2 FFT / inverse FFT。
  • 幅度谱、功率谱、频率 bin、主频检测。
  • STFT、periodogram PSD、单边 PSD、线性重采样。
  • 信号生成:正弦波、方波、脉冲、线性 chirp、确定性白噪声。
  • 窗函数:Rectangular、Hann、Hamming、Blackman。
  • 卷积:线性卷积、循环卷积。
  • 基础滤波:移动平均、FIR taps、IIR biquad 低通/高通/带通。
  • CLI:demofftanalyzewindowconvolvestftpsdresamplebiquadstatscorrelatewelchpeaks

#Quick Start

moon test moon run cmd/main -- demo moon run cmd/main -- fft examples/sine.csv --sample-rate 8 moon run cmd/main -- analyze examples/sine.csv --sample-rate 8 moon run cmd/main -- window hann 4 moon run cmd/main -- convolve examples/sine.csv examples/kernel.csv

Typical output:

{"samples":8,"sample_rate":8,"dominant_bin":2,"dominant_frequency":2,"magnitude":4}

#API Sketch

test {
let signal = sine_wave(length=8, sample_rate=8.0, frequency=2.0)
let spectrum = fft(real_signal(signal))
let peak = dominant_peak(spectrum, 8.0)
inspect(peak.index, content="2")
}

The root package exports the public API. CLI code lives in cmd/main and uses the same library functions as users would.

#Project Layout

  • complex.mbt - complex arithmetic and approximate comparisons.
  • fft.mbt - DFT, FFT, inverse FFT, real signal conversion.
  • analysis.mbt - spectra, frequency bins, peak detection.
  • signals.mbt - test/demo signal generation.
  • windows.mbt - window functions and window application.
  • convolution.mbt - linear and circular convolution.
  • filters.mbt - moving average, FIR filtering, low/high-pass taps.
  • stft.mbt, psd.mbt, resampling.mbt, iir_filters.mbt - extended scientific signal-processing primitives.
  • cmd/main - CLI entry point.
  • examples - small CSV fixtures for smoke tests.
  • docs/fixtures.md - fixture provenance, construction rules, and expected numeric invariants.
  • docs/competition - OSC2026 proposal and acceptance material.

#Verification

moon info python scripts/check_format.py moon check --target all --warn-list +73 moon build --target all moon test --target all powershell -ExecutionPolicy Bypass -File scripts\verify_acceptance.ps1 -SkipPublishDryRun

The extended analysis commands are also covered by deterministic smoke tests:

moon run cmd/main -- stats examples/offset.csv moon run cmd/main -- correlate examples/dual-tone.csv --max-lag 4 moon run cmd/main -- welch examples/sine.csv --sample-rate 8 --segment-length 8 --overlap 0.5 --window hann moon run cmd/main -- peaks examples/dual-tone.csv --threshold 0.4 --min-distance 2

MoonSpectrum is published as the Mooncakes module chgttyyr/MoonSpectrum@0.2.2. The moon.mod file is the source of truth for the package metadata shown on Mooncakes.

#Current Boundaries

  • FFT currently requires power-of-two input length.
  • CSV parser intentionally handles simple single-column numeric fixtures.
  • Time zones, timestamps, streaming IO, and visualization are future work.
  • No native FFI is used in v0.2.2.

#Roadmap

  • Cross-channel correlation and streaming window processing.
  • Robust CSV/JSON data ingestion and timestamp-aware analysis.
  • Browser/WebAssembly demo for frequency-domain visualization.
  • Additional statistical estimators and calibration helpers.

#License

Apache-2.0.

#
SpectrumError

pub(all) suberror SpectrumError {
EmptySignal
LengthMismatch
NonPowerOfTwo(length~ : Int)
InvalidArgument(message~ : String)
} derive(Eq, ToJson,
Debug
)

#
Biquad

pub(all) struct Biquad {
b0 : Double
b1 : Double
b2 : Double
a1 : Double
a2 : Double
x1 : Double
x2 : Double
y1 : Double
y2 : Double
} derive(Eq, ToJson,
Debug
)

#
Biquad::allpass

fn Biquad::allpass(cutoff_hz : Double, sample_rate : Double, q? : Double) -> Biquad raise SpectrumError

#
Biquad::bandpass

fn Biquad::bandpass(cutoff_hz : Double, sample_rate : Double, q? : Double) -> Biquad raise SpectrumError

#
Biquad::filter

fn Biquad::filter(self : Biquad, signal : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
Biquad::highpass

fn Biquad::highpass(cutoff_hz : Double, sample_rate : Double, q? : Double) -> Biquad raise SpectrumError

#
Biquad::lowpass

fn Biquad::lowpass(cutoff_hz : Double, sample_rate : Double, q? : Double) -> Biquad raise SpectrumError

#
Biquad::new

fn Biquad::new(b0 : Double, b1 : Double, b2 : Double, a1 : Double, a2 : Double) -> Biquad

#
Biquad::notch

fn Biquad::notch(cutoff_hz : Double, sample_rate : Double, q? : Double) -> Biquad raise SpectrumError

#
Biquad::step

fn Biquad::step(self : Biquad, x : Double) -> Double

#
Complex

pub(all) struct Complex {
re : Double
im : Double
} derive(Eq, ToJson,
Debug
)

#
Complex::add

fn Complex::add(self : Complex, other : Complex) -> Complex

#
Complex::approx_equal

fn Complex::approx_equal(self : Complex, other : Complex, eps? : Double) -> Bool

#
Complex::magnitude

fn Complex::magnitude(self : Complex) -> Double

#
Complex::mul

fn Complex::mul(self : Complex, other : Complex) -> Complex

#
Complex::scale

fn Complex::scale(self : Complex, factor : Double) -> Complex

#
Complex::sub

fn Complex::sub(self : Complex, other : Complex) -> Complex

#
Complex::zero

fn Complex::zero() -> Complex

#
FrequencyResponse

pub(all) struct FrequencyResponse {
frequencies : Array[Double]
magnitude : Array[Double]
} derive(Eq, ToJson,
Debug
)

#
Peak

pub(all) struct Peak {
index : Int
frequency : Double
magnitude : Double
} derive(Eq, ToJson,
Debug
)

#
SignalPeak

pub(all) struct SignalPeak {
index : Int
value : Double
} derive(Eq, ToJson,
Debug
)

#
SignalStats

pub(all) struct SignalStats {
count : Int
mean : Double
variance : Double
stddev : Double
rms : Double
minimum : Double
maximum : Double
peak_to_peak : Double
} derive(Eq, ToJson,
Debug
)

#
SpectralSummary

pub(all) struct SpectralSummary {
centroid : Double
bandwidth : Double
flatness : Double
entropy : Double
rolloff : Double
} derive(Eq, ToJson,
Debug
)

#
SpectrogramResult

pub(all) struct SpectrogramResult {
frequencies : Array[Double]
frame_times : Array[Double]
magnitudes : Array[Array[Double]]
} derive(Eq, ToJson,
Debug
)

#
WelchResult

pub(all) struct WelchResult {
frequencies : Array[Double]
psd : Array[Double]
} derive(Eq, ToJson,
Debug
)

#
WindowKind

pub(all) enum WindowKind {
Rectangular
Hann
Hamming
Blackman
} derive(Eq, ToJson,
Debug
)

#
apply_window

fn apply_window(signal : ArrayView[Double], weights : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
approx_equal

fn approx_equal(a : Double, b : Double, eps? : Double) -> Bool

#
autocorrelation

fn autocorrelation(signal : ArrayView[Double], max_lag : Int) -> Array[Double] raise SpectrumError

#
autocovariance

fn autocovariance(signal : ArrayView[Double], lag : Int) -> Double raise SpectrumError

Returns the covariance between a signal and a lagged copy of itself.

#
bandpass_fir_taps

fn bandpass_fir_taps(length~ : Int, low_cutoff_hz~ : Double, high_cutoff_hz~ : Double, sample_rate~ : Double) -> Array[Double] raise SpectrumError

#
bandstop_fir_taps

fn bandstop_fir_taps(length~ : Int, low_cutoff_hz~ : Double, high_cutoff_hz~ : Double, sample_rate~ : Double) -> Array[Double] raise SpectrumError

#
baseline_corrected

fn baseline_corrected(signal : ArrayView[Double]) -> (Double, Array[Double]) raise SpectrumError

Returns the baseline-corrected signal and its estimated offset.

#
biquad_frequency_response

fn biquad_frequency_response(filter : Biquad, sample_rate~ : Double, fft_length~ : Int) -> FrequencyResponse raise SpectrumError

Measures the positive-frequency response of a biquad section.

#
center

fn center(signal : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
chirp

fn chirp(length~ : Int, sample_rate~ : Double, start_hz~ : Double, end_hz~ : Double, amplitude? : Double) -> Array[Double] raise SpectrumError

#
circular_convolve

fn circular_convolve(signal : ArrayView[Double], kernel : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
clip_signal

fn clip_signal(signal : ArrayView[Double], lower : Double, upper : Double) -> Array[Double] raise SpectrumError

Clamps every sample to the inclusive interval [lower, upper].

#
complex

fn complex(re~ : Double, im~ : Double) -> Complex

#
convolve

fn convolve(signal : ArrayView[Double], kernel : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
correlation_coefficient

fn correlation_coefficient(left : ArrayView[Double], right : ArrayView[Double]) -> Double raise SpectrumError

#
covariance

fn covariance(signal : ArrayView[Double], other : ArrayView[Double]) -> Double raise SpectrumError

Returns the population covariance of two equally sized signals.

Covariance is useful for comparing sensor channels without requiring a frequency-domain representation. The divisor is N, matching the other descriptive statistics in this package.

#
crest_factor

fn crest_factor(signal : ArrayView[Double]) -> Double raise SpectrumError

#
cross_correlation

fn cross_correlation(signal : ArrayView[Double], other : ArrayView[Double], max_lag : Int) -> Array[Double] raise SpectrumError

#
detect_outliers

fn detect_outliers(signal : ArrayView[Double], z_threshold~ : Double) -> Array[Int] raise SpectrumError

Returns sample indices whose robust z-score exceeds the given magnitude.

#
detrend_linear

fn detrend_linear(signal : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
dft

fn dft(signal : ArrayView[Complex]) -> Array[Complex] raise SpectrumError

#
differentiate

fn differentiate(signal : ArrayView[Double], sample_interval : Double) -> Array[Double] raise SpectrumError

#
dominant_peak

fn dominant_peak(spectrum : ArrayView[Complex], sample_rate : Double) -> Peak raise SpectrumError

#
dynamic_range_db

fn dynamic_range_db(signal : ArrayView[Double]) -> Double raise SpectrumError

#
envelope_follow

fn envelope_follow(signal : ArrayView[Double], attack~ : Double, release~ : Double) -> Array[Double] raise SpectrumError

#
fft

fn fft(signal : ArrayView[Complex]) -> Array[Complex] raise SpectrumError

#
fft_convolve

fn fft_convolve(signal : ArrayView[Double], kernel : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
find_peaks

fn find_peaks(signal : ArrayView[Double], threshold? : Double, min_distance? : Int) -> Array[SignalPeak] raise SpectrumError

#
fir_filter

fn fir_filter(signal : ArrayView[Double], taps : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
fir_frequency_response

fn fir_frequency_response(taps : ArrayView[Double], sample_rate~ : Double, fft_length~ : Int) -> FrequencyResponse raise SpectrumError

Measures the positive-frequency response of an FIR filter by zero-padded FFT.

#
fir_impulse_response

fn fir_impulse_response(taps : ArrayView[Double], response_length : Int) -> Array[Double] raise SpectrumError

Creates the response of a finite impulse response filter to a unit impulse.

#
frame_signal

fn frame_signal(signal : ArrayView[Double], frame_length~ : Int, hop_size~ : Int) -> Array[Array[Double]] raise SpectrumError

#
frequency_bins

fn frequency_bins(length : Int, sample_rate : Double) -> Array[Double] raise SpectrumError

#
full_cross_correlation

fn full_cross_correlation(signal : ArrayView[Double], other : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
highpass_taps

fn highpass_taps(length~ : Int, cutoff_hz~ : Double, sample_rate~ : Double) -> Array[Double] raise SpectrumError

#
ifft

fn ifft(spectrum : ArrayView[Complex]) -> Array[Complex] raise SpectrumError

#
impulse

fn impulse(length~ : Int, index? : Int, amplitude? : Double) -> Array[Double] raise SpectrumError

#
integrate_trapezoid

fn integrate_trapezoid(signal : ArrayView[Double], sample_interval : Double) -> Double raise SpectrumError

#
interquartile_range

fn interquartile_range(signal : ArrayView[Double]) -> Double raise SpectrumError

Returns the interquartile range using the package's linear quantile rule.

#
lowpass_taps

fn lowpass_taps(length~ : Int, cutoff_hz~ : Double, sample_rate~ : Double) -> Array[Double] raise SpectrumError

#
magnitude_spectrum

fn magnitude_spectrum(spectrum : ArrayView[Complex]) -> Array[Double]

#
mean_absolute_deviation

fn mean_absolute_deviation(signal : ArrayView[Double]) -> Double raise SpectrumError

Returns the mean absolute deviation around the arithmetic mean.

#
mean_absolute_error

fn mean_absolute_error(reference : ArrayView[Double], estimate : ArrayView[Double]) -> Double raise SpectrumError

#
median

fn median(signal : ArrayView[Double]) -> Double raise SpectrumError

#
moving_average

fn moving_average(signal : ArrayView[Double], width : Int) -> Array[Double] raise SpectrumError

#
moving_rms

fn moving_rms(signal : ArrayView[Double], width : Int) -> Array[Double] raise SpectrumError

#
next_power_of_two

fn next_power_of_two(value : Int) -> Int raise SpectrumError

#
normalize_peak

fn normalize_peak(signal : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
normalize_range

fn normalize_range(signal : ArrayView[Double], lower : Double, upper : Double) -> Array[Double] raise SpectrumError

#
normalized_correlation

fn normalized_correlation(signal : ArrayView[Double], max_lag : Int) -> Array[Double] raise SpectrumError

#
overlap_add

fn overlap_add(frames : ArrayView[Array[Double]], hop_size~ : Int) -> Array[Double] raise SpectrumError

#
pad_to_power_of_two

fn pad_to_power_of_two(signal : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
periodogram

fn periodogram(signal : ArrayView[Double], sample_rate : Double, window_kind? : WindowKind) -> Array[Double] raise SpectrumError

#
power_spectrum

fn power_spectrum(spectrum : ArrayView[Complex]) -> Array[Double]

#
pulse_train

fn pulse_train(length~ : Int, period~ : Int, amplitude? : Double) -> Array[Double] raise SpectrumError

#
quantile

fn quantile(signal : ArrayView[Double], probability : Double) -> Double raise SpectrumError

#
real_signal

fn real_signal(values : ArrayView[Double]) -> Array[Complex]

#
remove_baseline

fn remove_baseline(signal : ArrayView[Double], baseline : Double) -> Array[Double] raise SpectrumError

Subtracts a known sensor baseline without changing the input array.

#
resample_linear

fn resample_linear(signal : ArrayView[Double], target_length : Int) -> Array[Double] raise SpectrumError

#
resample_sinc

fn resample_sinc(signal : ArrayView[Double], target_length : Int) -> Array[Double] raise SpectrumError

#
response_peak

fn response_peak(response : FrequencyResponse) -> Peak raise SpectrumError

Returns the maximum magnitude and its frequency from a response.

#
robust_z_scores

fn robust_z_scores(signal : ArrayView[Double]) -> Array[Double] raise SpectrumError

Computes robust z-scores using median and median absolute deviation.

#
root_mean_square_error

fn root_mean_square_error(reference : ArrayView[Double], estimate : ArrayView[Double]) -> Double raise SpectrumError

#
sawtooth_wave

fn sawtooth_wave(length~ : Int, sample_rate~ : Double, frequency~ : Double, amplitude? : Double) -> Array[Double] raise SpectrumError

#
scale_signal

fn scale_signal(signal : ArrayView[Double], input_min~ : Double, input_max~ : Double, output_min~ : Double, output_max~ : Double) -> Array[Double] raise SpectrumError

Linearly maps sensor values from an input range to an engineering range.

#
signal_energy

fn signal_energy(signal : ArrayView[Double]) -> Double raise SpectrumError

#
signal_rms

fn signal_rms(signal : ArrayView[Double]) -> Double raise SpectrumError

#
signal_stats

fn signal_stats(signal : ArrayView[Double]) -> SignalStats raise SpectrumError

#
signal_to_noise_db

fn signal_to_noise_db(reference : ArrayView[Double], estimate : ArrayView[Double]) -> Double raise SpectrumError

#
sine_wave

fn sine_wave(length~ : Int, sample_rate~ : Double, frequency~ : Double, amplitude? : Double) -> Array[Double] raise SpectrumError

#
single_sided_psd

fn single_sided_psd(two_sided_psd : ArrayView[Double]) -> Array[Double] raise SpectrumError

#
spectral_band_energy

fn spectral_band_energy(magnitudes : ArrayView[Double], sample_rate : Double, low_hz : Double, high_hz : Double) -> Double raise SpectrumError

#
spectral_bandwidth

fn spectral_bandwidth(magnitudes : ArrayView[Double], sample_rate : Double) -> Double raise SpectrumError

#
spectral_centroid

fn spectral_centroid(magnitudes : ArrayView[Double], sample_rate : Double) -> Double raise SpectrumError

#
spectral_entropy

fn spectral_entropy(magnitudes : ArrayView[Double]) -> Double raise SpectrumError

#
spectral_flatness

fn spectral_flatness(magnitudes : ArrayView[Double]) -> Double raise SpectrumError

#
spectral_flux

fn spectral_flux(previous : ArrayView[Double], current : ArrayView[Double]) -> Double raise SpectrumError

#
spectral_rolloff

fn spectral_rolloff(magnitudes : ArrayView[Double], sample_rate : Double, ratio? : Double) -> Double raise SpectrumError

#
spectral_slope

fn spectral_slope(magnitudes : ArrayView[Double]) -> Double raise SpectrumError

#
spectral_summary

fn spectral_summary(spectrum : ArrayView[Complex], sample_rate : Double) -> SpectralSummary raise SpectrumError

#
spectrogram

fn spectrogram(signal : ArrayView[Double], window_kind : WindowKind, window_length~ : Int, hop_size~ : Int, sample_rate~ : Double) -> SpectrogramResult raise SpectrumError

Computes positive-frequency magnitude frames and their time coordinates.

#
spectrogram_dominant_peaks

fn spectrogram_dominant_peaks(result : SpectrogramResult) -> Array[Peak] raise SpectrumError

Returns the dominant positive-frequency peak for every spectrogram frame.

#
spectrogram_energy

fn spectrogram_energy(result : SpectrogramResult) -> Array[Double]

Returns the sum of squared positive-frequency magnitudes for each frame.

#
spectrogram_loudest_frame

fn spectrogram_loudest_frame(result : SpectrogramResult) -> Int raise SpectrumError

Returns the frame index with the highest total energy.

#
square_wave

fn square_wave(length~ : Int, sample_rate~ : Double, frequency~ : Double, amplitude? : Double) -> Array[Double] raise SpectrumError

#
stft

fn stft(signal : ArrayView[Double], window_kind : WindowKind, window_length : Int, hop_size : Int) -> Array[Array[Complex]] raise SpectrumError

#
threshold_crossings

fn threshold_crossings(signal : ArrayView[Double], threshold : Double) -> Array[Int] raise SpectrumError

Returns sample indices where a signal crosses a threshold in either direction.

#
triangle_wave

fn triangle_wave(length~ : Int, sample_rate~ : Double, frequency~ : Double, amplitude? : Double) -> Array[Double] raise SpectrumError

#
welch_psd

fn welch_psd(signal : ArrayView[Double], sample_rate~ : Double, segment_length~ : Int, overlap~ : Double, window_kind? : WindowKind) -> WelchResult raise SpectrumError

#
white_noise

fn white_noise(length~ : Int, seed~ : UInt, amplitude? : Double) -> Array[Double] raise SpectrumError

#
window

fn window(kind : WindowKind, length : Int) -> Array[Double] raise SpectrumError

#
window_coherent_gain

fn window_coherent_gain(kind : WindowKind, length : Int) -> Double raise SpectrumError

#
window_noise_bandwidth

fn window_noise_bandwidth(kind : WindowKind, length : Int) -> Double raise SpectrumError

#
zero_crossing_indices

fn zero_crossing_indices(signal : ArrayView[Double]) -> Array[Int] raise SpectrumError

Returns the indices at which adjacent samples have strictly opposite signs.

#
zero_crossing_rate

fn zero_crossing_rate(signal : ArrayView[Double]) -> Double raise SpectrumError