وما أَسأَلُكُم عَلَيهِ مِن أَجرٍ إِن أَجرِيَ إِلّا عَلىٰ رَبِّ العالَمينَ
الحمد لله
ReciteQuran — اتلو القران
Real-Time On-Device Quran Karim Recitation Tracking & Tajweed Verification
📑 Table of Contents
- Overview
- Architecture & Data Pipeline
- Platform Prerequisites (Microphone Setup)
- Installation & Model Setup
- Quick Start Guide
- UI Integration & Color Highlighting (Green / Yellow / Red)
- Core Features & Guides
- Complete API Reference
- Example App Code Architecture
- Troubleshooting & FAQ
- Sacred Covenant & License (لوجه الله تعالى)
- Acknowledgments
🌟 Overview
recite_quran is a high-performance, real-time on-device speech-to-text alignment and Tajweed evaluation engine for Flutter.
- Continuous Word Tracking: Zero-lag real-time word alignment powered by semi-global Dynamic Time Warping (DTW) and causal Zipformer CTC acoustic models.
- Deterministic Tajweed Rules:
- Madd Rules (1–7): Validates elongation duration (2, 4, 6 Harakat) against acoustic timestamps.
- Mushaddad Ghunnah (10): Verifies 2-Harakah nasal holding on Mushaddad Noon (
نّ) & Meem (مّ). - Shaddah (9): Inspects consonant closure duration (~1.5 Harakat) and doubling.
- Instant Voice Navigation: Recite any verse or phrase to instantly search across all Ayahs.
- 100% Private & Offline
- Cross-Platform Multi-Threading
Architecture & Data Pipeline
┌─────────────────────────┐
│ Microphone (16kHz) │
└────────────┬────────────┘
│ Raw PCM Chunks
▼
┌─────────────────────────┐
│ AudioProcessor │ ──► Raw 16kHz PCM Stream via record (Hardware DSP filters bypassed)
└────────────┬────────────┘
│ 480ms Float32 Chunks (TransferableTypedData zero-copy)
▼
┌─────────────────────────┐
│ SherpaEngine (Isolate) │ ──► Zipformer2 CTC ONNX Acoustic Neural Model (250 Phoneme Units)
└────────────┬────────────┘
│ Phoneme Tokens + Spike Timestamps
▼
┌─────────────────────────┐
│ Alignment (Isolate) │ ──► Semi-Global Dynamic Time Warping (DTW) + Phonetic Confusion Matrix
└────────────┬────────────┘
│
┌──────┴───────────────────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ WordMatchedEvent (UI) │ │ Tajweed Duration Checks │
│ Green / Red / Yellow │ │ Madd, Ghunnah, Shaddah │
└───────────────────────────┘ └───────────────────────────┘
Installation & Model Setup
1. Add Dependency
Add recite_quran to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
recite_quran: ^1.0.3
2. Download the Neural Model
The neural acoustic model (zipformer_p_arabic_v3.int8.onnx, ~72MB) is hosted on GitHub Releases to keep the initial pub download lightweight.
Run this single setup command from your Flutter project root:
dart run recite_quran:download_model
This command automatically:
- Downloads the INT8 ONNX acoustic model to
assets/model/zipformer_p_arabic_v3.int8.onnx. - Adds
assets/model/zipformer_p_arabic_v3.int8.onnxto yourpubspec.yaml.
(All other Quran phoneme metadata and search indices are already bundled internally in the package!)
💻 Quick Start Guide
Here is a minimal, complete example showing audio capture, recitation tracking, and Tajweed diagnostics:
import 'package:flutter/material.dart';
import 'package:recite_quran/recite_quran.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Initialize Quran Metadata
final metadataService = QuranMetadataService();
final repository = QuranRepository(metadataService);
await repository.loadSurahAsync(1); // Pre-load Surah Al-Fatihah (Surah #1)
// 2. Instantiate ReciteQuran Tracker
final tracker = ReciteQuran(
repository: repository,
config: TrackerConfig.normal(), // Presets: .easy(), .normal(), .strict()
isTajweed: true, // Enable Tajweed verification
);
// 3. Initialize background Isolates and ASR Engine
await tracker.initialize();
// 4. Set Surah Al-Fatihah as active reference
tracker.setTargetSurah(1);
// 5. Listen to real-time word match events
tracker.onWordMatched.listen((WordMatchedEvent event) {
if (event.isRed) {
print(' Word #${event.wordId} skipped or mispronounced');
} else {
print(' Matched Word #${event.wordId} (Score: ${(event.score * 100).toInt()}%)');
// Inspect Tajweed duration diagnostics (if any)
if (event.tajweedErrors != null && event.tajweedErrors!.isNotEmpty) {
for (final errorMap in event.tajweedErrors!) {
final error = ReciterError.fromMap(errorMap);
print(' Tajweed rule: ${error.expectedRule?.name.ar} | Status: ${error.durationStatus}');
}
}
}
});
// 6. Listen to live ASR phoneme stream
tracker.onTranscript.listen((String transcript) {
print('Live ASR Transcript: $transcript');
});
// 7. Start microphone audio stream
final audioProcessor = AudioProcessor();
await audioProcessor.start(
onChunk: (Float32List chunk, bool isFinal) {
tracker.feedAudioChunk(chunk, isFinal: isFinal);
},
);
}
UI Integration & Color Highlighting (Green / Yellow / Red)
Word State & Color Resolution
When users recite, each word transitions through a clear 3-color state machine:
| Color | Status | Condition in WordMatchedEvent |
Meaning |
|---|---|---|---|
| 🟢 Green | PASS | isRed == false && (tajweedErrors == null || tajweedErrors.isEmpty) |
Pronounced correctly with valid Tajweed duration. |
| 🟡 Yellow | WARNING | isRed == false && tajweedErrors.isNotEmpty |
Correct word, but held Madd/Ghunnah too short (defect) or too long (surplus). |
| 🔴 Red | FAIL | isRed == true |
Word was skipped or mispronounced (omission / substitution). |
| ⚪ Default | UNSPOKEN | Not yet emitted by stream | Upcoming unrecited Quran text. |
Building a Highlighting Mushaf Widget
Here is how to connect onWordMatched to a Flutter StatefulWidget using RichText and TextSpan:
class QuranAyahView extends StatefulWidget {
final List<ContinuousQuranWord> words;
final ReciteQuran tracker;
const QuranAyahView({super.key, required this.words, required this.tracker});
@override
State<QuranAyahView> createState() => _QuranAyahViewState();
}
class _QuranAyahViewState extends State<QuranAyahView> {
final Map<int, WordMatchedEvent> _matchedWords = {};
@override
void initState() {
super.initState();
widget.tracker.onWordMatched.listen((event) {
setState(() {
_matchedWords[event.wordId] = event;
});
});
}
Color _resolveColor(int wordIndex) {
final match = _matchedWords[wordIndex];
if (match == null) return Colors.black87; // Unspoken word
if (match.isRed) return Colors.red; // 🔴 Skipped / Mispronounced
if (match.tajweedErrors != null && match.tajweedErrors!.isNotEmpty) {
return Colors.amber.shade700; // 🟡 Tajweed Duration Warning
}
return Colors.green.shade600; // 🟢 Perfect Match
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: RichText(
text: TextSpan(
children: widget.words.map((w) {
return TextSpan(
text: '${w.uthmani} ',
style: TextStyle(
fontFamily: 'HafsSmart',
fontSize: 26,
color: _resolveColor(w.globalIndex),
),
recognizer: TapGestureRecognizer()
..onTap = () {
final match = _matchedWords[w.globalIndex];
if (match != null && match.tajweedErrors != null && match.tajweedErrors!.isNotEmpty) {
_showTajweedErrorDialog(context, match.tajweedErrors!);
}
},
);
}).toList(),
),
),
);
}
}
Tajweed Error BottomSheet / Dialog
When a user taps a 🟡 Yellow word, display the exact Tajweed rule breakdown:
void _showTajweedErrorDialog(BuildContext context, List<Map<String, dynamic>> errors) {
showModalBottomSheet(
context: context,
builder: (context) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('تنبيه تجويد (Tajweed Notice)', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
...errors.map((errorMap) {
final error = ReciterError.fromMap(errorMap);
final String statusText = error.durationStatus == TajweedDurationStatus.defect
? 'نقص في المد / الغنة (Held too short)'
: 'زيادة في المد / الغنة (Held too long)';
return ListTile(
leading: const Icon(Icons.warning_amber_rounded, color: Colors.amber),
title: Text(error.expectedRule?.name.ar ?? 'حكم تجويد'),
subtitle: Text('$statusText\nExpected: ${error.expectedDuration}s | Actual: ${error.actualDuration}s'),
);
}),
],
),
);
},
);
}
Core Features & Guides
1. Real-Time Word Tracking (Green / Red Matching)
The tracking engine processes the user's recitation sequentially using semi-global DTW:
- Green Match (
event.isRed == false): The spoken word matched the reference within the configured threshold. - Red Match (
event.isRed == true): The user skipped one or more words or made a substantial phonetic mistake. - Anchor Advancement: When a match occurs, the alignment window automatically advances to the next word.
- Partial Words: If a user is currently pronouncing a long word, the engine holds state until the full word is articulated.
2. Deterministic Tajweed Verification
When isTajweed: true is enabled, each matched word evaluates acoustic duration timestamps against canonical Tajweed rules:
Covered Tajweed Rules:
| Rule ID | Rule Name | Description | Target Harakat | Normal Target (0.20s) |
|---|---|---|---|---|
| 1 | Normal Madd (المد الطبيعي) |
Natural 2-beat vowel elongation | 2 Harakat | 0.40s |
| 2 | Monfasel Madd (المد المنفصل) |
Separated elongation before Hamzah | 4 Harakat | 0.80s |
| 3 | Mottasel Madd (المد المتصل) |
Connected elongation with Hamzah | 4 Harakat | 0.80s |
| 4 | Aared Lil-Sukoon (المد العارض للسكون) |
Optional pause elongation | 4 Harakat | 0.80s |
| 5 | Leen Madd (مد اللين) |
Soft vowel pause elongation | 4 Harakat | 0.80s |
| 6 | Lazem Madd (المد اللازم) |
Compulsory 6-beat elongation | 6 Harakat | 1.20s |
| 9 | Shaddah (الشدة) |
Consonant closure & doubling hold | ~1.5 Harakat | 0.30s |
| 10 | Mushaddad Ghunnah (النون والميم المشددتان) |
Nasal resonance holding on نّ and مّ |
2 Harakat | 0.40s |
3. Voice Navigation & Ayah Search (6,236 Ayahs)
Allow your users to recite any verse or fragment to jump directly to that Surah and Ayah:
final searchController = VoiceSearchController(engine: tracker.engine);
// Preload the 6,236-Ayah phonetic index
await searchController.preloadIndex();
// When user holds the mic button:
await searchController.startSearch();
// Feed streaming ASR text to search:
tracker.onTranscript.listen((transcript) async {
final AnchorResult? match = await searchController.processRealtime(transcript);
if (match != null) {
print('⚡ Instant Unique Match Found: Surah ${match.surah}, Ayah ${match.ayah}');
tracker.setTargetSurah(match.surah);
}
});
4. Difficulty Presets & Config Tuning
Adjust the dynamic alignment sensitivity and Harakat duration at runtime:
// 1. Easy Mode (0.150s / Harakah - For children, beginners, or noisy environments):
tracker.updateConfig(TrackerConfig.easy());
// 2. Normal Mode (0.200s / Harakah - Default balanced calibration):
tracker.updateConfig(TrackerConfig.normal());
// 3. Strict Mode (0.250s / Harakah - For certification / strict exams):
tracker.updateConfig(TrackerConfig.strict());
// 4. Custom Parameter Tuning:
tracker.updateConfig(
TrackerConfig(
defaultMaxPathCost: 0.30, // DTW distance threshold (0.0 to 1.0)
shortWordPathCost: 0.25, // Stricter threshold for 1-3 letter words
acousticConfusionCost: 0.25, // Cost for phonetically similar Arabic sounds
harakatDurationSeconds: 0.200, // Base beat speed in seconds (200ms)
maxSkipWords: 2, // Maximum words to lookahead on omission
),
);
Complete Reference
ReciteQuran (Main Facade)
| Method / Getter | Description |
|---|---|
initialize() |
Spawns background Isolates, loads ONNX model, and starts the ASR pipeline. |
setTargetSurah(int surah, {int startGlobalWord}) |
Loads reference phonemes and sets the tracking target Surah. |
jumpToWord(int globalWordIndex) |
Moves tracking cursor directly to a specific word index. |
feedAudioChunk(Float32List chunk, {bool isFinal}) |
Feeds 16 kHz mono PCM float audio chunks to recognizer. |
resetBuffer() |
Clears internal ASR audio buffer. |
setTajweedMode(bool active) |
Enables or disables Tajweed duration validation. |
updateConfig(TrackerConfig newConfig) |
Updates cost matrix and timing parameters at runtime. |
onWordMatched |
Stream<WordMatchedEvent> emitting real-time alignment and Tajweed errors. |
onTranscript |
Stream<String> emitting live phoneme transcriptions. |
dispose() |
Gracefully releases all Isolates, audio controllers, and memory. |
WordMatchedEvent
| Property | Type | Description |
|---|---|---|
wordId |
int |
Global 0-indexed word position within the active Surah. |
score |
double |
Acoustic match confidence score (0.0 to 1.0). |
cleanAsr |
String |
Matched phoneme substring produced by ASR. |
isRed |
bool |
true if the word was skipped or mispronounced. |
isNeutral |
bool |
true if the word was neutrally skipped. |
tajweedErrors |
List<Map<String, dynamic>>? |
Detailed Tajweed timing issues for this word. |
📁 Example App Code Architecture
The complete, production-ready sample application is located in the example/ directory:
File in example/ |
What it demonstrates |
|---|---|
example/lib/ui/tracking_screen.dart |
Full screen layout with mic button, auto-scrolling, and Surah picker. |
example/lib/ui/widgets/helpers/verse_span_builder.dart |
High-performance InlineSpan builder with Green/Yellow/Red color resolution. |
example/lib/ui/widgets/dialogs/error_detail_dialog.dart |
Interactive Tajweed diagnostic bottom-sheet popup. |
example/lib/ui/widgets/mic_bar.dart |
Audio waveform visualizer and push-to-talk voice search. |
cd example
flutter pub get
dart run recite_quran:download_model
flutter run -d windows # or -d android / -d chrome
❓ Troubleshooting
1. "Missing ONNX model on disk" error
- Cause: The neural model has not been downloaded to your project assets.
- Fix: Run
dart run recite_quran:download_modelin your project root and ensureassets/model/zipformer_p_arabic_v3.int8.onnxis listed in yourpubspec.yaml.
2. Microphone does not detect Arabic breathy sounds (like هـ or ح)
- Cause: System-level aggressive noise cancellation or echo suppression is filtering speech.
- Fix: Use
AudioProcessorfromrecite_quran. It automatically configuresAVAudioSessionand AndroidAudioRecordwithnoiseSuppress: falseandautoGain: falseto preserve subtle Arabic phonetic characteristics.
3. Words match too easily or are too strict
- Fix: Adjust difficulty using
tracker.updateConfig(TrackerConfig.easy())orTrackerConfig.strict().
License (لوجه الله تعالى)
مَا أَسْأَلُكُمْ عَلَيْهِ مِنْ أَجْرٍ ۖ إِنْ أَجْرِيَ إِلَّا عَلَىٰ رَبِّ الْعَالَمِينَ
THIS PACKAGE AND SOURCE CODE ARE DEDICATED FOR THE SAKE OF ALLAH ALONE.
Before viewing, using, distributing, or modifying any part of this repository, you explicitly agree to the following covenants:
- 100% Free to End Users: You may use, study, and redistribute this software or its logic ONLY in applications and services that are completely free of charge to all end users forever.
- Strict Prohibition on Commercialization & Profit: You are STRICTLY FORBIDDEN from selling this application, placing it behind paywalls, subscription models, in-app purchases, charging download fees, monetizing it with advertisements (AdMob, Unity Ads, etc.), or extracting any financial revenue from this codebase, models, or outputs.
- Pass-Through: These terms are immutable and strictly pass on to any fork, derivative work, or redistributed component.
Acknowledgments
Alhamdulillah (الحمد لله رب العالمين) — this work builds upon open-source research and contributions from:
- Zipformer Quran Streaming Model by Brother Mustafa
- quran-transcript by Brother Abdullah Aml
- Quranic Universal Aligner (qua_sdk) by Brother Ahmad Ibrahim
هذا من فضل ربي — ربنا تقبل منا إنك أنت السميع العليم
Libraries
- audio/audio_processor
- audio/audio_processor_io
- audio/audio_processor_web
- data/quran_data
- engine/models/sherpa_protocol
- engine/sherpa_engine
- engine/sherpa_engine_io
- engine/sherpa_engine_web
- recite_quran
- tracking/ayah_search/fuzzy_search
- tracking/ayah_search/phonetic_search
- tracking/ayah_search/voice_search_controller
- tracking/tajweed/error_explainer
- tracking/tajweed/tajweed_rules
- tracking/tracker_config
- tracking/word/dictation_matcher
- tracking/word/dictation_sequencer
- tracking/word/highlighting_controller
- tracking/word/phoneme_alignment_isolate
- tracking/word/phoneme_alignment_isolate_io
- tracking/word/phoneme_alignment_isolate_protocol
- tracking/word/phoneme_alignment_isolate_web
- utils/debug_logger