When to use Signal Core
Signal Core is a lightweight, pure‑Dart engine that brings professional‑grade signal processing capabilities to any Flutter application. Whether you are building a medical‑grade EEG viewer, a real‑time audio visualizer, or a sensor‑driven IoT dashboard, Signal Core provides a unified API for handling multiple data channels, applying advanced filters, and navigating large data sets with minimal latency. The package abstracts the underlying byte source, allowing you to work with files, network streams, or in‑memory buffers without worrying about format specifics. Its core features include summary pyramids for rapid zoom‑in/zoom‑out operations, a suite of display filters such as SOS (second‑order sections) and zero‑phase filters, and flexible re‑referencing tools that let you transform raw channel data into meaningful representations.
When to use Signal Core? The library shines in scenarios where you need deterministic, low‑overhead processing of high‑frequency signals on the client side. For example, a neuro‑feedback app that streams EEG data at 500 Hz can use the pyramid summarizer to render a smooth overview while still allowing users to drill down to individual samples. Similarly, a music‑learning app can apply zero‑phase filtering to eliminate phase distortion when visualizing waveforms in real time. Because the engine is format‑agnostic, you can plug in custom parsers for proprietary binary protocols, making it a versatile choice for research prototypes and production‑grade products alike.
From an architectural perspective, Signal Core fits naturally into a clean or layered Flutter architecture. It lives in the data layer as a service that exposes streams of processed samples. UI widgets subscribe to these streams via state‑management solutions like Provider, Riverpod, or Bloc, keeping the UI reactive and decoupled from heavy computation. The package does not impose any specific state‑manager, but its stream‑based design aligns well with reactive patterns. In a typical setup, a repository fetches raw bytes from a local file, a SignalCoreProcessor builds the pyramid and applies filters, and the presentation layer renders the results using custom painters or charting widgets.
Getting started is straightforward. After adding the dependency, you instantiate a `SignalEngine` with a byte source, configure the desired filters, and listen to the output stream. The API is intentionally minimal: you define channel metadata, select a summarization level, and request re‑referencing on the fly. The package also offers built‑in quality metrics that flag noisy channels, which is especially useful in biomedical applications where signal integrity is critical. For production use, consider running the heavy lifting on isolates to keep the UI thread responsive, and be mindful of memory consumption when working with very long recordings – the pyramid structure can be trimmed or persisted to disk as needed.
For beginners, Signal Core provides a gentle learning curve. The documentation includes a step‑by‑step tutorial that walks you through loading a CSV of simulated sensor data, applying a low‑pass SOS filter, and visualizing the result with a simple `CustomPaint` widget. Because the library is pure Dart, it works on all Flutter platforms – Android, iOS, web, macOS, Linux and Windows – without additional native dependencies. However, when targeting web or desktop, ensure that the underlying byte source is accessible (e.g., via `http` or `file` APIs) and that you handle large buffers with care to avoid browser memory limits.
In summary, Signal Core delivers a robust, platform‑agnostic foundation for any Flutter app that needs real‑time or offline signal analysis. Its modular design, comprehensive filter suite, and efficient summarization algorithm make it a compelling choice for developers ranging from hobbyists building hobby‑level audio visualizers to researchers developing clinical‑grade neuro‑monitoring tools. By keeping the core logic in pure Dart, the package stays lightweight, testable, and easy to integrate into existing Flutter projects.
Pros
- pure Dart – works everywhere
- fast zoom via summary pyramids
- comprehensive filter library
- channel quality assessment built‑in
- no native dependencies
Watch outs
- advanced usage may require isolates
- limited UI widgets – you must build custom painters
- documentation still growing
Setup notes
Add the dependency with the command: ``` flutter pub add signal_core ``` Then run `flutter pub get` and import the package: ```dart import 'package:signal_core/signal_core.dart'; ```
Requires Flutter 3.0.0 or newer and Dart SDK >=2.17.0. The package is pure Dart and works on Android, iOS, web, macOS, Linux and Windows. For large recordings consider using isolates to avoid UI jank. No platform‑specific native code is required.
```dart
import 'package:flutter/material.dart';
import 'package:signal_core/signal_core.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Signal Core Demo')),
body: const SignalViewer(),
),
);
}
}
class SignalViewer extends StatefulWidget {
const SignalViewer({super.key});
@override
State<SignalViewer> createState() => _SignalViewerState();
}
class _SignalViewerState extends State<SignalViewer> {
late final SignalEngine _engine;
late final Stream<List<double>> _filteredStream;
@override
void initState() {
super.initState();
// Simulated byte source – replace with a real file or network stream.
final source = ByteSource.fromList([/* raw bytes */]);
_engine = SignalEngine(source: source, channelCount: 2);
// Apply a low‑pass SOS filter and request a summary level of 4.
_engine.applySosFilter(lowPassCutoff: 30);
_engine.setSummaryLevel(4);
_filteredStream = _engine.outputStream;
}
@override
Widget build(BuildContext context) {
return StreamBuilder<List<double>>(
stream: _filteredStream,
builder: (context, snapshot) {
if (!snapshot.hasData) return const Center(child: CircularProgressIndicator());
final samples = snapshot.data!;
return CustomPaint(
size: const Size(double.infinity, 200),
painter: WaveformPainter(samples),
);
},
);
}
}
class WaveformPainter extends CustomPainter {
final List<double> samples;
WaveformPainter(this.samples);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.blue..strokeWidth = 1.0;
final step = size.width / samples.length;
for (int i = 0; i < samples.length - 1; i++) {
final x1 = i * step;
final y1 = size.height / 2 - samples[i] * 50;
final x2 = (i + 1) * step;
final y2 = size.height / 2 - samples[i + 1] * 50;
canvas.drawLine(Offset(x1, y1), Offset(x2, y2), paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
```