AudioSummarizer: دقة صوتية محيدة غامضة (العربية (Arabic))

AudioSummarizer: دقة صوتية محيدة غامضة

Saturday, 10 January 2026

//

18 minute read

الوضع:AudioSummarizer.Core حاليا في طور التطوير كجزء من lucidRAG, منتج أكثر وضوحاً في المستقبل لـ multi- RAG مودلة . التنفيذ مكتمل و يعمل — هذا المقال يوثيق العمارة والتصميم | . | إدماج CLI سيكون متاحاً فى إصدار RAG الشفاف القادم |.

المصدر: github.comM SK1scottgal/lucidrag (branch: v2)

أين يلائم هذا: AudioSummarizer جزء من lucidRAG مجموعة من تطبيقات RAG المنخفضة. كل منها يتعامل مع طريقة مختلفة:

  • DocSummarizer - مستندات ( إستخراج الكائنات, رسمول المعرفةM SK3
  • ImageSummarizer - الصور (22-ذكاء بصري موجي)
  • المزيج للبيانات - البيانات ( إستنتاج الرسم التخطيطيM SK2 رسم الخرائط)
  • AudioSummarizer (هذا المقال) - الصوت (الرسم الصوتي,استهلاك السماعةMSC5

كلهم يتبعون نفس الشيء تخفيض نمط RAG: نستخرج الإشارات مرة واحدة , نحفظ الأدلة , ن синтезها مع مدخل LLM محدود


العديد من التجارب -أول أنابيب التحليل الصوتي تقوم بنفس الخطأ : إنهم يتعاملون مع LLMs كما لو أنهم مهندسين صوتيين

ويقومون بتغذية أشكال الموجات في النماذج..,.. يطلبون منهم "..إكتشاف جودة الكلام،" أو "..يتعرفون على المتكلمين،,".. ويأملون أن يستطيع النموذج infer properties structural from what is fundamentally a pattern.

AudioSummarizer يجمع نمطين متكاملين:

  1. تقلل من RAG للبحث - استخراج الإشارات مرة , تخزين الأدلة, سؤال ضد الحقائق
  2. الغموض المقيد من أجل الأوركسترا - موجة- أسلاك مبنية على , substrate deterministic يقيد النماذج الإحتمالية

بدلاً من إدخال الصوت الخام إلى LLM في وقت الاستفسار, فإنه تقلل كل ملف صوتي إلى دليل إشارات (signals deterministic like RMS loudnessM SK1spectral features, speaker embeddings ) with إخراج الأدلة (transcripts, sample clips of speakers , diarization turns). هذا ال ledger يمثّل مرة واحدةMSC4 indexed for retrievalM SK5

في وقت التساؤل, يقوم LLM بتحليل الاستجابات عن طريق إستخلاص و استدلال على الإشارات البارزة—لا عن طريق إعادةMSC2تحليل الصوتM SK3 معالجة المؤشرات الثقيلة (تحويل المؤشرsMスク5 التدوينMST6 الإسهالM ST7 يحدث أثناء تناولهاM st8 يعمل LLM فقط في وقت الاسئلة لترجمة ما سبقهMst9 الحقائق المحسوبية و توليد إجابات بشريةM St10قابلة للقراءةMSt11

تركيب النمط: مقبضات RAG منخفضة ماذا لإ storing and retrieve. مقبضات الاحماض المحدودة كيف لإستخراج الإشارات بشكل موثوق . سوياً تمكنها من رسم الصوت القضائي بتكلفة LLM محدودة .

أتعلم المزيد عن نمط RAG المنخفض: Reduced RAG: Signal-Driven Document Understanding

المصطلحات الرئيسية

  • الإشارات: الحقائق المكتوبة مع درجات الثقة audio.content_type = "speech", الثقة: 0.85)
  • الأدلة: القطع الصوتية ( النص الافتراضي, أجزاء عينة من المكبرات الصوتية
  • وثيقة إشارات: مجموعة مستمرة من جميع الإشارات, إشارات الدلائلM SK2 ودمجات تم استخراجها من ملف صوتي
  • إسم المتحدث: محدد محلي داخل فايل واحدM SK1تشغيل التماثل (e.gMSC4 SPEAKER_00)
  • لوحة الصوت: Cross-file stable hash derived from voice embedding vprint:a3f9c2e1)
  • الشخص: لا يتم استنتاجها بشكل واضح— نحن لا ندعي " هذا جون سميث

نموذج الهوية: SPEAKER_00 هو محلي لملف صوتي واحد. VoiceprintId stable across files but anonymous. We never map to human names.

المتطلبات الجنائية: كل إشارة تتضمن الموطن (ما الموجة التي أنتجتها الثقة (درجة اليقين), و إصدار (نموذجM SK1إصدارات مُحدّدة). هذا يُمكن التّرجّل : نفس المدخل | + نفس الإعداد |→ نفس ledger السلكية |

والنتيجة هي:

  • أسرع,خصوصية-حفاظ على قياس صوتي أطبائي
  • استهلاك المتحدث بدون إعتماد Python (pure .NET)
  • إكتشاف تشابه المتحدث المجهول (لا PIIM SK1 لا أسماء)
  • دمج الصوت لـ "بحث عن مكبرات صوتية مشابهة " الأسئلة
  • كل ذلك بدون إرسال الصوت إلى واجهات الويب السحابية أثناء الإدخال

هذا يبني مباشرة على نمط الغموض المقيد والهندسة المعمارية من موجة - ImageSummarizer.

رؤية أساسية: يجب أن يفكّر الـ LLM في الحقائق الصوتية.

هذا المقال يغطي

  • كيف يقوم AudioSummarizer بتطبيق نمط RAG المنخفض
  • إستخراج الإشارة: الحقائق الصوتية الإفتراضية (لا خلاصات LLMM SK2
  • تخزين الأدلة
  • أصفر .استهلاك مكبر NET (لا ب Python , لا ب pyannote )
  • Query-synthesis of timeM SK1 filter signals → retrieve evidence → LLM synthesizes

المقالات المترابطة:

  • تقلل من RAG - النمط الأساسي الذي يطبقه هذا
  • نمط الغموض المقيد - النمط الأساسي
  • تطبيقات RAG المنخفضة
    • DocSummarizer - رسم بياني للوثائق مع إستخراج الكيانات ورسم البياني للمعرفة
    • المزيج للبيانات - رسم البيانات و إستنتاج الرسم البياني RAG
    • ImageSummarizer - RAG للصور مع 22- أنابيب الذكاء البصري الموجي
    • AudioSummarizer (هذا المقال) - تشخصية طبية صوتية مع تشويه السماعة

المشكلة : التحليل الصوتي صعب ( ومحمّل ثقافياً

فشل التحليل الصوتي بطرق متوقعة:

  • الإدعاءات الثقافية: "هذا هو موسيقى الجاز" ♫( يقول منM SK4 بناء على أي بيانات التدريب
  • إسم المتحدث: " المتحدث هو جون سميث" | | ( انتهاك للخصوصيةM SK4 هلوس
  • تحديد الموسيقى: "SongM SK2 Sandstorm by Darude"
  • تخمين متسارع: " يتحدث بالإنجليزية" ♫( خاطئ في كثير من الأحيانM SK4 محتمل للإهانةMSC5
  • إعتمادات بيثون: معظم أدوات التلوث تحتاج إلى قفل نظام بيئي (Python

المقاربة التقليدية: M SK1Run Whisper for transcription, pyannote for diarization , send to LLM for summaryMSC4

المشكلة: هذا إما أن يتسرب PII ( أسماء المتكلمين | ), يكلف الكثير ♫ ( | واجهات الويب الcloud |), | أو يحتاج إلى وقت تشغيل ب Python | ( | pyannote | مsk6 | DIART | ومsk7

الحل: بناء أنابيب خالصة . موجة شبيهة ,- تتميز الصوت بتركيبها الهيكلي و الصوتي , — , بدون إثباتات ثقافية .


إطار RAG الصوتي المنخفض

AudioSummarizer يطبق تخفيض نمط RAG لملفات صوتية, تبعاً لثلاثة مبادئ أساسية:

1. إستخراج الإشارة ( مرحلة الإمتصاص)

الإشارات التنقيبية التي تم استخراجها مرة واحدة—لا LLM-خلاصات معالجةM SK2

  • مؤقتة: مدة , ضربات زمنية , حدود segmento
  • الصوتي: ضخامة RMS, مركزية स्पكتريةM SK2 نطاق ديناميكي , نسبة التقطيع
  • الهوية: SHAM SK1 hash,ទ្រង់ទ្រة الឯកសារ , معدل العينات ,قنوات
  • الجودة: ثقة في التحويل, الثقة في الإسهال
  • المتحدث: إختبارات بصمة صوتية ( هشات مجهولة | ), | أرقام الدوران |, | نسبة المشاركة
  • التصنيف: نوع المحتوى ( الكلامM SK2 الموسيقى/ الصمتMSC4 ترتيب المتحدثين (singleMska6twoMske7multiMsek8

حزمة إشارة مثالية للبوستاد:

{
  "audio.hash.sha256": "3f2a9c8b1e4d...",
  "audio.duration_seconds": 912.0,
  "audio.rms_db": -18.2,
  "audio.spectral_centroid_hz": 2418.3,
  "audio.content_type": "speech",
  "speaker.count": 2,
  "speaker.classification": "two_speakers",
  "transcription.confidence": 0.87,
  "voice.voiceprint_id.speaker_00": "vprint:a3f9c2e1d4b8"
}

هذه الإشارات هي إختباري (نفس الصوت → نفس القيم), يمكن تصنيفها ( يمكن تصفيه/ ترتيب في قاعدة البياناتM SK2 و قابلة للحساب دون LLMs ( معالجة الإشارة النظيفة + نماذج ONNX

2. تخزين الأدلة

بدلاً من تخزين فقط "chunks," AudioSummarizer stores :

  • الإشارات: الحقول المبنية (JSON) للصفر القطعي
  • الملحقات: مداخلات الصوت (512-dim ECAPAM SK2TDNN) للتشابه مع المتحدث
    • ملاحظة الخصوصية: المداخلات ليست قابلة للعكس في هذا النظام , لكنها تتعامل معها كبيانات حساسة
  • أثار الأدلة:
    • نص الترجمة الكامل (searchable)
    • شريحة عينة صوتية (BaseM SK1 WAV, 2-شريحة ثانية للتحقق من الدقةMSC4
    • تدور المزودة (JSON مع مكبر الصوت_idM SK2 البدايةMSC3التقريبات الزمنية النهائيةMスク4
  • المؤشرات: File hash + IDs of evidence for auditable provenance

الدليل قابل للتحقق منه: يمكن للمستخدمين أن يلعبوا عينات صوتيّة, قراءة المخطوطاتM SK2 تفحص دورة التلوثMSC3 لا يثقون فقط في خلاصة LLM

3.البحث-استخلاص الوقت (إدخال LLM المحدودM SK3

في وقت الاستفسار, LLM أبداً يرى الصوت الخام. بدلاً من ذلك:

  1. فلتر بشكل محدد ( قاعدة بيانات أين فقرة):

    WHERE audio.content_type = 'speech'
      AND speaker.count >= 2
      AND audio.rms_db > -25.0
      AND transcription.confidence > 0.8
    
  2. البحث الهجين مسك0BM25 M SK2 فيكتور, |~5 نتائج

    • BM25 على النص التحويلي (تطابق الكلماتM SK2
    • شبيهة الفيكتور على مداخلات الصوت ( شبيهية المتكلمين)
    • أعود إلى الأعلى 5 الملفات الصوتية
  3. تجميع من الإشارات (LLM يرى مجموعة من الأدلة المبنية

    Audio 1: podcast_ep42.mp3
    - Duration: 15m 12s
    - Speakers: 2 (SPEAKER_00: 52%, SPEAKER_01: 48%)
    - Quality: RMS -18.2dB, no clipping
    - Transcript: "Welcome to Tech Insights. Today we're discussing..."
    - Entities: ["quantum computing", "Google", "IBM"]
    

يستقبل الـ LLM 5 مجموعات الأدلة المبنية بدلاً من chunks of raw audio metadata 500. تخفيض النافذة السياقيةM SK1 ~50× أصغر.

تأثير التكاليف ( إذا استخدمت APIs LLM المدفوعة): معالجة 100 الملفات السمعية تذهب من |$5 | ( |re |- |التحليل الصوتي لكل سؤال |ـ) | إلى |مسك7 | |ماسك8 |إستبيان مقابل الإشارات المبرمجة | ماسك10 *ملاحظة: lucidRAG محلي-أول (Ollama تلقائياًM SK3تكلفة صفرةMSC4 أجهزة الAPI المدفوعة |( |Claude | , | GPT |-4) |optional |. | تقديرات التكلفة تفترض استخدام ال API المدفوع |ـ: | ــ$0.01/1 | نصوص ( | إختلافات من قبل المُقدم ٬), |*

هذا هو الجوهر لرؤية RAG المنخفضة: يقرر ما يهم مقدماً (signals), تخزينها بشكل محدد (أدلة), إشراك LLM فقط لsynthesis (questM SK1time).


أنابيب الصوت المقيدة الغموض

تشغيل النظام الموجات في ترتيب أولي (أعلى رقم = يعمل أولا):

Wave Priority Order:
  100: IdentityWave          → SHA-256, file metadata, duration
   90: FingerprintWave       → Chromaprint perceptual hash (optional)
   80: AcousticProfileWave   → RMS, spectral features, SNR
   70: ContentClassifierWave → Speech vs music heuristics (routing)
   65: TranscriptionWave     → Whisper.NET (optional)
   60: SpeakerDiarizationWave→ Pure .NET speaker separation
   30: VoiceEmbeddingWave    → ECAPA-TDNN speaker similarity

ملاحظة: العاطفة

العمارة

flowchart LR
    A[Audio file] --> B[IdentityWave]
    B --> C[AcousticProfileWave]
    C --> D[ContentClassifierWave]
    D --> E[TranscriptionWave]
    E --> F[SpeakerDiarizationWave]
    F --> G[VoiceEmbeddingWave]
    G --> H[Signal Ledger]
    H --> I[Optional LLM Synthesis]

    style B stroke:#333,stroke-width:4px
    style D stroke:#333,stroke-width:4px
    style F stroke:#333,stroke-width:4px
    style H stroke:#333,stroke-width:4px

هذا يحافظ على المألوفة موجة → إشارة → إختيار LLM حلقة , لكنها تربطها في الحقائق المعرفية :

  • الرسم الصوتي هو محدد (نفس المدخل = نفس المخرج )
  • يعمل الـ LLM على الأدلة , وليس الصوت الخام

حقيقي-أمثلة عالمية: معالجة حلقة ال podcast

دعونا نتبع حلقة 15-دقيقة من المذياع عبر الأسلاك

Input: podcast_ep42.mp3
  - File size: 14.2 MB
  - Format: MP3, 44.1kHz stereo, 192 kbps
  - Duration: 15m 12s (912 seconds)
  - Content: 2-person interview

Wave Execution (priority order 100 → 30):

1. IdentityWave (Priority 100, 87ms):
   ✓ SHA-256: 3f2a9c8b1e4d...
   ✓ Duration: 912.0s
   ✓ Channels: 2 (stereo)
   ✓ Sample rate: 44100 Hz
   ✓ File size: 14,897,234 bytes

2. AcousticProfileWave (Priority 80, 142ms):
   ✓ RMS loudness: -18.2 dB (good mastering)
   ✓ Peak amplitude: 0.94 (no clipping)
   ✓ Dynamic range: 22.1 dB
   ✓ Spectral centroid: 2418 Hz (speech-like)
   ✓ Spectral rolloff: 7892 Hz

3. ContentClassifierWave (Priority 70, 89ms):
   ✓ Zero-crossing rate: 0.17 (high → speech)
   ✓ Spectral flux: 0.28 (low → not music)
   → Classification: "speech" (confidence: 0.85)

4. TranscriptionWave (Priority 65, 12.3s):
   ✓ Whisper.NET base model
   ✓ Segments: 142
   ✓ Total words: 2,341
   ✓ Confidence: 0.87 (high)
   ✓ Text: "Welcome to Tech Insights. Today we're discussing..."

5. SpeakerDiarizationWave (Priority 60, 3.8s):
   ✓ VAD detected: 47 speech segments
   ✓ Embeddings extracted: 47 × 512-dim vectors
   ✓ Clustering (threshold 0.75): 2 speakers
   ✓ Turns before merge: 47
   ✓ Turns after merge: 23
   ✓ SPEAKER_00 participation: 52% (474s)
   ✓ SPEAKER_01 participation: 48% (438s)
   ✓ Sample clips extracted: 2 (Base64 WAV, ~30KB each)

6. VoiceEmbeddingWave (Priority 30, 178ms):
   ✓ ECAPA-TDNN inference
   ✓ Embedding dimension: 512
   ✓ Voiceprint ID (SPEAKER_00): "vprint:a3f9c2e1d4b8"
   ✓ Voiceprint ID (SPEAKER_01): "vprint:7e2d8f1a9c3b"

Total processing time: 16.6s
Signals emitted: 47
LLM calls: 0 (fully offline)
Cost: $0 (local processing only)

الـ وثيقة إشارات هو ما يستمر إلى قاعدة البيانات —مجموعة كاملة من الإشارات , اشارات إثباتية ٬ , ٬ ودمجات ٬. . هنا ٬ МSK4 . هذا ما يبدو عليه هذا اللوحة ٬

دليل إشارات (خلاصة):

{
  "identity.filename": "podcast_ep42.mp3",
  "audio.hash.sha256": "3f2a9c8b1e4d...",
  "audio.duration_seconds": 912.0,
  "audio.format": "mp3",
  "audio.sample_rate": 44100,
  "audio.channels": 2,
  "audio.channel_layout": "stereo",
  "audio.rms_db": -18.2,
  "audio.dynamic_range_db": 22.1,
  "audio.spectral_centroid_hz": 2418.3,
  "audio.spectral_rolloff_hz": 7892.1,
  "audio.content_type": "speech",
  "content.confidence": 0.85,
  "speaker.count": 2,
  "speaker.classification": "two_speakers",
  "speaker.turn_count": 23,
  "speaker.avg_turn_duration": 39.7,
  "speaker.diarization_method": "agglomerative_clustering",
  "speaker.participation": {
    "SPEAKER_00": 52.0,
    "SPEAKER_01": 48.0
  },
  "transcription.full_text": "Welcome to Tech Insights...",
  "transcription.word_count": 2341,
  "transcription.confidence": 0.87,
  "voice.embedding.speaker_00": [0.023, -0.511, 0.882, ...],
  "voice.voiceprint_id.speaker_00": "vprint:a3f9c2e1d4b8",
  "speaker.sample.speaker_00": "UklGRiQAAABXQVZF...",
  "speaker.sample.speaker_01": "UklGRiQBBBXQVZF..."
}

ما الذي يمكنه هذا

  • البحث عن "Tech Insights discusses cloud infrastructure" → finds this episode via transcript
  • Query "Find audio with voiceprint vprintMSC1a3fM SK3cMST4eMS1" → finds all episodes with same speaker |
  • أسأل "ماذا'ما هو نطاق الديناميكي لسلسلات ال podcast لديناM SK2 → الإشارات الصوتية المتراكمة
  • فلتر "بحث عن كميات منخفضة- تسجيلات عالية الجودةM SK2 → RMS < |-25 ثنائي أمتار أو قطعة مقطعية | > | |
  • أتأكد من "عمل تحديد مكبر الصوت ID?" → اللعب speaker.sample.speaker_00 clip (withinM SK1file)

لماذا يهم الإشارات (حتى بدون LLM)

دليل إشارات يجيب على الأسئلة المملة

  • هل هذا الكلام الصوتي؟
  • كم عدد المكبرات التي تم الكشف عنها?
  • ما هي المؤشر
  • هل هناك آثار مزق أو تشوه?
  • ما هو؟
  • ما هو نطاق الديناميكي؟

هذه هي الأسئلة التي تكتشفها عادة 30 دقائق في علم الآثار أوداسي

المؤشر يعطيها في ثواني.


أنابيب الموجة: من الهوية إلى الاستخبارات

الموجة 1: الهوية ( طبقة إختبارية

الخط الأساسي. الهوية الشفرية وبيانات البيانات المجازة للឯកសារ.

public class IdentityWave : IAudioWave
{
    public string Name => "IdentityWave";
    public int Priority => 100;  // Runs first

    public async Task<IEnumerable<Signal>> AnalyzeAsync(
        string audioPath,
        AnalysisContext context,
        CancellationToken ct)
    {
        var signals = new List<Signal>();

        // Cryptographic hash (deterministic identity)
        var fileHash = await ComputeSha256Async(audioPath, ct);
        signals.Add(new Signal
        {
            Name = "audio.hash.sha256",
            Value = fileHash,
            Type = SignalType.Identity,
            Confidence = 1.0,
            Source = Name
        });

        // File-level metadata
        var fileInfo = new FileInfo(audioPath);
        signals.Add(new Signal
        {
            Name = "audio.file_size_bytes",
            Value = fileInfo.Length,
            Type = SignalType.Metadata,
            Source = Name
        });

        // Audio format metadata
        using var reader = new AudioFileReader(audioPath);

        signals.Add(new Signal
        {
            Name = "audio.duration_seconds",
            Value = reader.TotalTime.TotalSeconds,
            Type = SignalType.Metadata,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.sample_rate",
            Value = reader.WaveFormat.SampleRate,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.channels",
            Value = reader.WaveFormat.Channels,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.format",
            Value = Path.GetExtension(audioPath).TrimStart('.'),
            Type = SignalType.Metadata,
            Source = Name
        });

        return signals;
    }
}

الإشارات الرئيسية المنبعثة

  • audio.hash.sha256 - الهوية الشفرية
  • audio.duration_seconds - طول (إختباري)
  • audio.sample_rate - 44100, ≥48000, وهلم جرا.
  • audio.channels - 1
  • audio.format - mpM SK1 wav , flac, etcMSC4

لماذا ديناميكية?

  • نفس الملف → نفس الشفرة → نفس الهوية
  • لا يوجد عشوائية في العينات, لا يوجد متغير درجة الحرارة
  • الثقة = 1.0 ≥( غير محتمل)

موجة 2: Profil acoustic (Signal Processing)

استخرج خصائص الصوتيات الهيكلية باستخدام الNAudio و FftSharp.

public class AcousticProfileWave : IAudioWave
{
    private readonly ILogger<AcousticProfileWave> _logger;

    public string Name => "AcousticProfileWave";
    public int Priority => 80;

    public async Task<IEnumerable<Signal>> AnalyzeAsync(
        string audioPath,
        AnalysisContext context,
        CancellationToken ct)
    {
        var signals = new List<Signal>();

        using var reader = new AudioFileReader(audioPath);

        // Convert to mono for analysis
        ISampleProvider sampleProvider = reader.WaveFormat.Channels == 1
            ? reader
            : new StereoToMonoSampleProvider(reader) { LeftVolume = 0.5f, RightVolume = 0.5f };

        // Read all samples
        var samples = ReadAllSamples(sampleProvider);

        // Time-domain analysis
        var rms = CalculateRms(samples);
        var peakAmplitude = samples.Max(Math.Abs);
        var dynamicRange = CalculateDynamicRange(samples);
        var clippingRatio = CalculateClippingRatio(samples, threshold: 0.99);

        signals.Add(new Signal
        {
            Name = "audio.rms_db",
            Value = 20 * Math.Log10(rms),  // Convert to decibels
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.peak_amplitude",
            Value = peakAmplitude,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.dynamic_range_db",
            Value = dynamicRange,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.clipping_ratio",
            Value = clippingRatio,
            Type = SignalType.Acoustic,
            Confidence = clippingRatio > 0.01 ? 0.9 : 1.0,  // Low confidence if clipping detected
            Source = Name
        });

        // Frequency-domain analysis (FFT)
        var spectralFeatures = CalculateSpectralFeatures(samples, reader.WaveFormat.SampleRate);

        signals.Add(new Signal
        {
            Name = "audio.spectral_centroid_hz",
            Value = spectralFeatures.Centroid,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.spectral_rolloff_hz",
            Value = spectralFeatures.Rolloff,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.spectral_bandwidth_hz",
            Value = spectralFeatures.Bandwidth,
            Type = SignalType.Acoustic,
            Source = Name
        });

        return signals;
    }

    private SpectralFeatures CalculateSpectralFeatures(float[] samples, int sampleRate)
    {
        // Use FftSharp for frequency analysis
        int fftSize = 2048;
        var fftInput = new double[fftSize];

        // Take middle section of audio
        int offset = Math.Max(0, (samples.Length - fftSize) / 2);
        for (int i = 0; i < fftSize; i++)
        {
            fftInput[i] = samples[offset + i];
        }

        // Apply Hamming window
        var window = FftSharp.Window.Hamming(fftSize);
        for (int i = 0; i < fftSize; i++)
        {
            fftInput[i] *= window[i];
        }

        // Compute FFT
        var fft = FftSharp.Transform.FFT(fftInput);
        var magnitudes = fft.Select(c => Math.Sqrt(c.Real * c.Real + c.Imaginary * c.Imaginary)).ToArray();

        // Calculate spectral centroid (brightness)
        double sumWeightedFreq = 0;
        double sumMagnitude = 0;
        for (int i = 0; i < magnitudes.Length / 2; i++)
        {
            double freq = i * sampleRate / (double)fftSize;
            sumWeightedFreq += freq * magnitudes[i];
            sumMagnitude += magnitudes[i];
        }
        double centroid = sumMagnitude > 0 ? sumWeightedFreq / sumMagnitude : 0;

        // Calculate spectral rolloff (85% energy threshold)
        double totalEnergy = magnitudes.Take(magnitudes.Length / 2).Sum(m => m * m);
        double cumulativeEnergy = 0;
        double rolloff = 0;
        for (int i = 0; i < magnitudes.Length / 2; i++)
        {
            cumulativeEnergy += magnitudes[i] * magnitudes[i];
            if (cumulativeEnergy >= 0.85 * totalEnergy)
            {
                rolloff = i * sampleRate / (double)fftSize;
                break;
            }
        }

        return new SpectralFeatures
        {
            Centroid = centroid,
            Rolloff = rolloff,
            Bandwidth = CalculateBandwidth(magnitudes, centroid, sampleRate, fftSize)
        };
    }
}

مخرج مثالي:

Input: podcast.mp3 (15 minutes, 44.1kHz stereo)

Signals emitted:
  audio.rms_db = -18.2 dB (good loudness)
  audio.peak_amplitude = 0.94 (no clipping)
  audio.dynamic_range_db = 22 dB (moderate dynamics)
  audio.clipping_ratio = 0.003 (0.3% clipping, minimal)
  audio.spectral_centroid_hz = 2400 Hz (mid-brightness, speech-like)
  audio.spectral_rolloff_hz = 8000 Hz (most energy below 8kHz)
  audio.spectral_bandwidth_hz = 4200 Hz (moderate spread)

لماذا يهم هذا؟

  • وضوح RMS يشير إلى جودة التفوق
  • معدل التقاط يكشف عن آثار التصوير
  • مركز الطيف يميز الكلام (2-4kHzM SK1 من الموسيقى (variable)
  • جميع المؤشرات الثابتة— نفس الصوت ينتج نفس القيم

موجة 3:تصنيف المحتوى (حديث مقابل الموسيقى)

التصنيف الهرمي القوي باستخدام الصفر - نسبة عبور وتدفق الطيف لأغراض التوجيه .

ملاحظة: هذه المنطقات هي نوعية / معتمدة على السياق | — | غير دقيقة بشكل موثوق في جميع أنواع المحتوى |. | مستخدمة فقط لقرارات توجيه الموجة |

public class ContentClassifierWave : IAudioWave
{
    public string Name => "ContentClassifierWave";
    public int Priority => 70;

    public async Task<IEnumerable<Signal>> AnalyzeAsync(
        string audioPath,
        AnalysisContext context,
        CancellationToken ct)
    {
        var signals = new List<Signal>();

        using var reader = new AudioFileReader(audioPath);
        var samples = ReadAllSamples(reader);

        // Zero-crossing rate (heuristic: speech tends higher ZCR - not universal)
        var zcr = CalculateZeroCrossingRate(samples);

        // Spectral flux (heuristic: music tends more consistent - varies by genre)
        var spectralFlux = CalculateSpectralFlux(samples, reader.WaveFormat.SampleRate);

        // Simple heuristic classifier (for routing, not identity)
        // Thresholds calibrated for typical podcast/interview content
        // Production: calibrate on your corpus for best routing accuracy
        string contentType;
        double confidence;

        if (zcr > 0.15 && spectralFlux < 0.3)  // Speech heuristic
        {
            contentType = "speech";
            confidence = 0.85;
        }
        else if (zcr < 0.10 && spectralFlux > 0.5)
        {
            contentType = "music";
            confidence = 0.80;
        }
        else
        {
            contentType = "mixed";
            confidence = 0.70;
        }

        // Check for silence
        var rmsDb = context.GetValue<double>("audio.rms_db");
        if (rmsDb < -50)
        {
            contentType = "silence";
            confidence = 0.95;
        }

        signals.Add(new Signal
        {
            Name = "audio.content_type",
            Value = contentType,
            Type = SignalType.Classification,
            Confidence = confidence,
            Source = Name,
            Metadata = new Dictionary<string, object>
            {
                ["zero_crossing_rate"] = zcr,
                ["spectral_flux"] = spectralFlux,
                ["rms_db"] = rmsDb
            }
        });

        return signals;
    }
}

مثال توجيهي:

Speech (ZCR=0.18, flux=0.25):
  → audio.content_type = "speech"
  → Enables: TranscriptionWave, SpeakerDiarizationWave

Music (ZCR=0.08, flux=0.65):
  → audio.content_type = "music"
  → Disables: SpeakerDiarizationWave (no speakers to detect)

Silence (RMS=-52dB):
  → audio.content_type = "silence"
  → Disables: All downstream waves (early exit)

Pure .NET Speaker Diarization (No Python)

بالنسبة لعوائق نشر lucidRAG': استهلاك السماعة بدون البينوتوت, DIARTM SK2 أو أي إعتمادات بيتونية .

المشكلة مع Python-Based Diarization

النهج التقليدي:

pyannote.audio (Python) → ONNX export (experimental) → C# wrapper (fragile)

مشاكل:

  • pyannote 3.1+ أزيلت دعم ONNX ( انتقلت إلى PyTorch النقي)
  • تستلزم وقت تشغيل Python ( كابوس نشر )
  • مُلف HTTP يضيف تأخيراً ومعقدة
  • لا يوجد دعم خارجي

الحل: نفذ الإزدياد في الـ . NET باستعمال نماذج الاندماج الصوتي موجودة .

الخوارزمية خالصة .NET

1. Voice Activity Detection (VAD) → Detect speech segments (energy-based RMS)
2. Segment Embedding → Extract ECAPA-TDNN embeddings for each segment
3. Agglomerative Clustering → Group segments by speaker (cosine similarity)
4. Speaker Turns → Merge consecutive turns from same speaker

التنفيذ

csharp public class SpeakerDiarizationService { private readonly ILogger _logger; private readonly VoiceEmbeddingService _embeddingService; private readonly AudioConfig _config;

public virtual async Task<DiarizationResult> DiarizeAsync(
    string audioPath,
    CancellationToken ct = default)
{
    _logger.LogInformation("Starting speaker diarization for {AudioPath}", audioPath);

    // Step 1: Detect speech segments using VAD
    var segments = DetectSpeechSegments(audioPath);
    _logger.LogDebug("Detected {Count} speech segments", segments.Count);

    if (segments.Count == 0)
    {
        return new DiarizationResult
        {
            Turns = new List<SpeakerTurn>(),
            SpeakerCount = 0
        };
    }

    // Step 2: Extract embeddings for each segment
    var embeddings = new List<(SpeechSegment Segment, float[] Embedding)>();
    foreach (var segment in segments)
    {
        try
        {
            var embedding = await ExtractSegmentEmbeddingAsync(audioPath, segment, ct);
            embeddings.Add((segment, embedding));
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to extract embedding for segment {Start}-{End}",
                segment.StartSeconds, segment.EndSeconds);
        }
    }

    // Step 3: Cluster embeddings to identify speakers
    var speakerClusters = ClusterSpeakers(embeddings);
    _logger.LogInformation("Identified {Count} speakers", speakerClusters.Keys.Count);

    // Step 4: Create speaker turns
    var turns = new List<SpeakerTurn>();
    foreach (var (segment, embedding) in embeddings)
    {
        var speakerId = FindSpeakerForEmbedding(embedding, speakerClusters);
        turns.Add(new SpeakerTurn
        {
            SpeakerId = speakerId,
            StartSeconds = segment.StartSeconds,
            EndSeconds = segment.EndSeconds,
            Confidence = 1.0  // TODO: Calculate based on cluster distance
        });
    }

    // Step 5: Merge consecutive turns from same speaker
    var mergedTurns = MergeConsecutiveTurns(turns);

    return new DiarizationResult
    {
        Turns = mergedTurns,
        SpeakerCount = speakerClusters.Keys.Count
    };
}

// Simple VAD using energy-based speech detection
private List<SpeechSegment> DetectSpeechSegments(string audioPath)
{
    using var reader = new AudioFileReader(audioPath);
    ISampleProvider sampleProvider = reader.WaveFormat.Channels == 1
        ? reader
        : new StereoToMonoSampleProvider(reader) { LeftVolume = 0.5f, RightVolume = 0.5f };

    var sampleRate = sampleProvider.WaveFormat.SampleRate;
    var windowSize = sampleRate / 10; // 100ms windows
    var buffer = new float[windowSize];

    var segments = new List<SpeechSegment>();
    SpeechSegment? currentSegment = null;

    double timeSeconds = 0;
    int samplesRead;

    while ((samplesRead = sampleProvider.Read(buffer, 0, buffer.Length)) > 0)
    {
        // Calculate RMS energy for this window
        double rms = Math.Sqrt(buffer.Take(samplesRead).Sum(s => s * s) / samplesRead);

        // Speech detection threshold (simple baseline - fragile across gain levels)
        // Production: use relative threshold (noise floor / percentile) or per-file calibration
        bool isSpeech = rms > 0.02;  // Fixed threshold for demonstration

        if (isSpeech)
        {
            if (currentSegment == null)
            {
                // Start new segment
                currentSegment = new SpeechSegment
                {
                    Star
Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.