المجتمع المستهدف: مجتمع إتقان للتقنيات القرآنية والإسلامية (itqan.dev)
الكاتب: فريق تطوير تطبيق «تثبيت» (Tathbeet)
المستودعات مفتوحة المصدر ذات الصلة:
1. البداية: تجربة حية ونتيجة محبطة!
في مساء هذا اليوم، كنا نضع اللمسات الأخيرة لدعم رواية ورش عن نافع في محرك التسميع التفاعلي لتطبيق «تثبيت». فتحنا التطبيق على نظام macOS، قمنا بتفعيل خيار "أحكام التجويد"، واخترنا سورة الفاتحة برواية ورش، ثم بدأ القارئ يتلو بصوت شجي:
﴿ٱلۡحَمۡدُ لِلَّهِ رَبِّ ٱلۡعَٰلَمِينَ﴾
لكن كانت المفاجأة غير السارة: الكلمات على الشاشة لم تتلون، والمحرك ظل صامتاً تماماً وكأن أحداً لم يقرأ حرفاً!
وعند الرجوع إلى سجلات التتبع اللحظية (Console Logs)، وجدنا المشهد التالي:
[log] RecitationService: Loaded 21 words for Surah 1
flutter: [DP] 📖 Surah 1 | 21 words | cursor=0 | tajweed=false
flutter: [ASR] ⚡ Endpoint detected (22ms) | ASR: لحَمدُلِللَااهِرَببِلعَاالَمِۦۦۦۦن...
هذا السجل الصغير كان يحمل خلفه لغزين تقنيين وعلميين دقيقين للغاية، تطلب حلهما رحلة استكشاف ممتعة نضع خلاصة دروسها بين أيدي إخواننا المطورين في مجتمع «إتقان».
2. اللغز الأول: 21 كلمة أم 25 كلمة؟ (فخ العد الكوفي والمدني)
أول ما لفت انتباهنا في السجل هو هذا السطر:
RecitationService: Loaded 21 words for Surah 1
سورة الفاتحة في مصحف ورش تتكون من 25 كلمة، فلماذا يقول المحرك أنه قام بتحميل 21 كلمة فقط؟ والأدهى من ذلك: لماذا يقول tajweed=false رغم أن المستخدم قام بتفعيل التجويد صراحة؟!
كواليس التحقيق:
- تتبعنا مسار تحميل الملفات الصوتية على بيئة ماك، فاكتشفنا أن التطبيق كان يقرأ من مجلد المستندات المحلي ملفاً قديماً مخبأً (Cache) منذ شهر سبتمبر:
warsh_phonemes.json.
- ذلك الملف القديم كان يفتقر إلى كائن البيانات الوصفية
moshaf: { rewaya: "warsh" }.
- وبسبب غياب هذا الكائن، قامت دالة الفحص في مكتبة التسميع (
isNativeDataset) بإرجاع القيمة false.
- حينما اعتبر المحرك أن البيانات ليست "أصيلة" لورش، قام فوراً بتشغيل آلية التوافق القديمة (Fallback Mapper) المعتمدة على العد الكوفي (رواية حفص)!
الكارثة العلمية الناتجة عن إجبار ورش على قالب حفص:
- في حفص (العد الكوفي): الآية 1 هي البسملة (
بِسْمِ اللَّهِ الرَّحْمَـٰنِ الرَّحِيمِ - 4 كلمات). والآية 2 هي الْحَمْدُ لِلَّهِ رَبِّ الْعَالَمِينَ.
- في ورش (العد المدني الأخير): البسملة ليست آية معدودة في أول الفاتحة، بل الآية رقم 1 هي مباشرة:
الْحَمْدُ لِلَّهِ رَبِّ الْعَالَمِينَ!
- عندما حاول المحرك التوفيق بين العدين:
- قام بإسقاط البسملة (4 كلمات) من إجمالي كلمات الفاتحة (25 - 4 = 21 كلمة).
- واعتبر أن الآية رقم 1 في ورش تقابل الآية رقم 2 في حفص (
الرَّحْمَـٰنِ الرَّحِيمِ)!
النتيجة الصادمة:
القارئ يقرأ: "الحمد لله رب العالمين"، بينما المحرك في الذاكرة ينتظر منه أن يقرأ: "الرحمن الرحيم"!
ولذلك لم تتطابق أي كلمة وظل المؤشر متجمداً مكانه!
3. اللغز الثاني: لماذا ترفض خوارزمية DTW مطابقة الحروف؟
حتى بعد أن أدركنا ضرورة معاملة مصحف ورش كمصحف أصيل (Native Dataset) دون إخضاعه للعد الكوفي، واجهتنا معضلة فونيمية أعمق داخل خوارزمية البرمجة الديناميكية (Semi-Global DTW) التي تطابق مخرجات نموذج التعرف الصوتي (Zipformer On-Device ASR) مع النص القرآني.
قارنا بين المخرج الذي سجله الميكروفون وبين ما كان ينتجه سكريبت التصدير القديم:
| المرجع الصوتي المولد قديماً | مخرج نموذج الذكاء الاصطناعي الحقيقي (ASR) | موضع الخلل |
لِللااهِ | لِللَااهِ | غياب حركة الفتحة َ على اللام المشددة قبل الألف |
لعاااالَمۦۦۦۦن | لعَاالَمِۦۦۦۦن | تضخيم مد الألف لـ 4 حركات، وغياب الكسرة ِ قبل الياء |
لماذا حدث هذا التباين؟
طبيعة النموذج الصوتي (CTC Spikes):
النموذج الصوتي لا يلتقط حرف المد بمفرده كرمز صوتي عارٍ، بل يرصد الحركة الصوتية السابقة له مباشرة:
- الفتحة مع امتداد الألف:
َ + اا = عَاا.
- الكسرة مع امتداد الياء:
ِ + ۦۦۦۦ = مِۦۦۦۦ.
- الضمة مع امتداد الواو:
ُ + ۥۥ = عُۥۥ.
خطأ "نطاق الحكم" (Word-Scope vs. Sound-Scope):
في كود التصدير السابق، كان السكريبت يفحص الكلمة ككل: فإذا وجد في آخر كلمة العالمين حكماً لـ "المد العارض للسكون" (4 حركات)، قام بتطبيق الطول (4 حركات) على جميع حروف المد داخل الكلمة! فأصبحت الألف (التي هي مد طبيعي حركتان فقط) ممدودة بـ 4 حركات (لعاااالَم...)، مما ضاعف تكلفة التعديل (Edit Cost) وجعل الخوارزمية تعتبر الكلمة خاطئة!
4. الحل المعماري الشامل
لإنهاء هذه المشكلة جذرياً وبناء حل مؤسسي يخدم الأمة في كافة الروايات، قمنا بالخطوات الهندسية التالية:
الخطوة الأولى: الترقية الصوتية في quranic-phonemizer
مشروع quranic-phonemizer هو محرك مفتوح المصدر تم تطويره بلغة بايثون ليقوم بتحويل الرسم العثماني إلى أصوات دقيقة (Grapheme-to-Phoneme).
قمنا بتعديل سكريبت تصدير البيانات [export_recite_quran_dataset.py](file:///Users/m97chahboun/Development/quranic-phonemizer/tools/export_recite_quran_dataset.py) ليقوم بربط كل حكم تجويدي بالصوت الفردي (Sound) لا بالكلمة المجردة:
def sound_to_recite_token(s, res, is_warsh=True):
tok = s.token
# جلب الأحكام المرتبطة بهذا الصوت تحديداً
rules = [res.rule_occurrences[oid.value].rule_id.value
for oid in s.rule_occurrence_ids
if oid.value < len(res.rule_occurrences)]
rule_set = set(rules)
# حساب طول المد الخاص بهذا الحرف حصراً
madd_len = 2
if 'madd_lazim' in rule_set:
madd_len = 6
elif any(r in rule_set for r in ('madd_munfasil', 'madd_muttasil', 'madd_mim_al_jam')):
madd_len = 6 if is_warsh else 4 # ورش يقرأ بالطول (6 حركات) وجوباً
elif any(r in rule_set for r in ('madd_arid_lissukun', 'madd_leen', 'madd_leen_mahmuz')):
madd_len = 4 # المد العارض والتوسط في اللين المهموز
elif 'madd_badal' in rule_set:
madd_len = 4 if is_warsh else 2 # توسط البدل لورش
# صياغة الحروف الممدودة بإلزامية الحركة القصيرة المجانسة
if tok in ('a:', 'aˤ:'):
return 'َ' + ('ا' * madd_len)
elif tok == 'u:':
return 'ُ' + ('ۥ' * madd_len)
elif tok == 'i:':
return 'ِ' + ('ۦ' * madd_len)
elif tok in ('e:', 'ɛ:'):
return '۪' + ('ا' * (madd_len - 1)) # تقليل الإمالة
النتيجة لمصحف ورش (6,214 آية بالتمام والكمال):
أنتجنا ملف warsh_phonemes.json بحجم 9.41 ميجابايت، يتضمن كافة خصوصيات ورش الصوتية (نقل الهمز، توسط البدل، إشباع المتصل والمنفصل، القلقلة، والغنن).
الآية 1:1 في ورش:
النص: اِ۬لْحَمْدُ لِلهِ رَبِّ اِ۬لْعَٰلَمِينَ
الأصوات: ['ءَلحَمدُ', 'لِللَااهِ', 'رَببِ', 'لعَاالَمِۦۦۦۦن']
الأحكام: [[12, 1, 2], [24, 1, 2], [24, 5, 4]]
(الكلمات: 4 كلمات مستقلة، ومجموع الفاتحة: 25 كلمة مطابقة للواقع بنسبة 100%)
الخطوة الثانية: ترقية حزمة ReciteQuran وتخطي المحول للروايات الأصيلة
في حزمة التسميع في فلاتر (ReciteQuran):
- أضفنا فحص
isNativeDataset، والذي يفحص معرف الرواية المضمن في رأس الملف moshaf.rewaya.
- إذا كانت البيانات المجهزة أصيلة لورش، يتم تجاوز جداول تحويل الآيات تماماً، وتحميل آيات ورش 1:1 كما رُسمت وعُدّت في المصحف.
- تفعيل أحكام التجويد للروايات المدعومة (
isTajweedSupported = isHafs || isWarsh).
// lib/data/quran_data.dart
if (mapper == null || mapper.system == QuranCountingSystem.kufi || isNativeDataset) {
// تحميل مباشر للآيات بدون أي تشويه أو إزاحة في الترقيم
for (int ayah = 1; ayah <= 300; ayah++) {
final key = '$surah:$ayah';
final phonemeObj = versesMap[key];
if (phonemeObj != null) {
verses.add(QuranVerse.fromJson(surah, ayah, phonemeObj));
} else {
break;
}
}
}
الخطوة الثالثة: معمارية التنزيل عند الطلب (Zero-MB Binary Overhead)
كان التحدي الإداري والتقني: كيف نضيف دعماً كاملاً لمصحف ورش بدون زيادة حجم التطبيق على متجري App Store و Google Play؟
- الحل: تجنبنا حزم ملف
warsh_phonemes.json (9.41 ميجابايت) داخل الـ Assets.
- تم رفع الملف على خوادم الإصدارات العامة مع حفظ بصمة التشفير
SHA-256.
- في تطبيق «تثبيت»، قمنا بإنشاء خدمة
RecitationAssetService.ensureRiwayahPhonemes(riwayaId):
- عند اختيار المستخدم لرواية ورش لأول مرة، يقوم التطبيق بتنزيل الملف وحفظه محلياً في الذاكرة التخزينية.
- في حال انقطاع الإنترنت أو فشل التنزيل، يتراجع المحرك تلقائياً وبأمان إلى نمط الحفظ العام دون أن ينهار التطبيق.
5. الاختبار والاعتماد (CI & Verification)
قبل إطلاق التحديث إلى بيئة الإنتاج:
- اختبارات الحزمة (
ReciteQuran): نجاح 52 اختباراً من أصل 52، بما يشمل محاكاة أزمنة المدود والغنن وقراءة الفاتحة وسورة الناس بنقل الهمز (قُلَ اَعُوذُ).
- اختبارات التطبيق (
Tathbeet): نجاح كافة اختبارات الفهرسة والتجويد والتكامل مع فلاتر.
- التجربة الميدانية الحية:
عند إعادة تشغيل التطبيق وقراءة الفاتحة برواية ورش:
[log] RecitationService: Loaded 25 words for Surah 1
flutter: [DP] 📖 Surah 1 | 25 words | cursor=0 | tajweed=true
وتلألأت الكلمات باللون الأخضر كلمة تلو كلمة بالتزامن الفوري مع صوت القارئ، مع تلوين المدود باللون الأصفر في حال التقصير في حركاتها، واكتشاف إسقاط الكلمات بدقة بالغة.
تم بعد ذلك دمج هذه التحسينات واعتمادها رسمياً في بنية تطبيق «تثبيت» لتعمل بكفاءة وسلاسة للمستخدمين.
6. تجربة حية للمحرك (Live Web Demo)
بينما ننتظر اكتمال مراجعات المتاجر لإطلاق التحديث الجديد على تطبيقات الهواتف الذكية (iOS و Android) وحواسيب سطح المكتب (Desktop):
يسعدنا أن ندعوكم لخوض تجربة حية ومباشرة لمحرك التسميع القرآني برواية ورش واختبار التلوين الفوري لأحكام التجويد عبر متصفح الويب مباشرة من خلال منصتنا:
👉 app.tathbeet.space
خطوات التجربة السريعة:
- افتح الرابط في المتصفح وامنح الإذن لاستخدام الميكروفون.
- اختر «ورش عن نافع» كرواية التلاوة الحالية.
- فعّل خيار «أحكام التجويد» وابدأ بتلاوة سورة الفاتحة لمشاهدة التفاعل اللحظي!
6. الدروس المستفادة للمطورين القرآنيين
- لا تختزل الروايات في مجرد فروق كلمات:
الروايات القرآنية ليست مجرد كلمات متباينة، بل هي منظومات متكاملة تشمل: أعداد الآي ورؤوسها، وتوزيع الكلمات، ومقادير المدود، وقواعد صوتية جذرية كالنقل والإمالة.
- الـ ASR يحتاج تفاصيل الحركات الصوتية:
في معالجة اللغة الطبيعية للقرآن الكريم (Quranic NLP/Speech)، تجريد الحروف من حركاتها القصيرة السابقة لحروف المد يفسد مصفوفات المسافة في خوارزميات الـ DTW.
- احرص على أوزان التطبيقات (App Size Budget):
تقسيم الموارد وتنزيل البيانات الصوتية التخصصية عند الطلب (On-Demand Assets) مع التحقق من الـ Checksums يحافظ على سرعة انتشار التطبيق وصغر حجمه للمستخدمين في الدول ذات سرعات الإنترنت المحدودة.
روابط ومصادر للمجتمع
نسأل الله تعالى أن يتقبل هذا الجهد، وأن يكون دافعاً لجميع المطورين والمؤسسات القرآنية لتعزيز دعم القراءات والروايات المتواترة في الفضاء الرقمي بأعلى معايير الإتقان التقني والعلمي.