Skip to content

feat: audio ai stack - onnx runtime, whisper, tts, audiogen, and audio analysis - #684

Merged
ooples merged 69 commits into
masterfrom
feat/audio-ai-stack
Dec 31, 2025
Merged

ooples merged 69 commits into
masterfrom
feat/audio-ai-stack

Conversation

@ooples

@ooples ooples commented Dec 29, 2025

Copy link
Copy Markdown
Owner

Summary

This PR implements a comprehensive Audio AI stack covering multiple GitHub issues:

ONNX Infrastructure (#280)

  • OnnxModel<T> wrapper with InferenceSession and multiple execution providers
  • OnnxModelOptions for CPU, CUDA, TensorRT, DirectML configuration
  • OnnxTensorConverter for AiDotNet Tensor <-> ONNX tensor conversion
  • OnnxModelDownloader for HuggingFace model downloads
  • OnnxExporter for exporting neural networks to ONNX format

Audio Models (#269, #270, #271)

  • WhisperModel<T> - Speech-to-text with encoder-decoder architecture
  • TtsModel<T> - Text-to-speech with FastSpeech2 + HiFi-GAN pipeline
  • AudioGenModel<T> - Text-conditioned audio generation

Audio Features (#396)

  • MfccExtractor<T> - Mel-frequency cepstral coefficients
  • ChromaExtractor<T> - Chromagram (12 pitch classes)
  • SpectralFeatureExtractor<T> - Centroid, rolloff, flux, bandwidth
  • ConstantQTransform<T> - CQT for music analysis

Audio Analysis (#396)

  • MusicSourceSeparator<T> - HPSS-based stem separation (vocals, drums, bass, other)
  • GenreClassifier<T> - Rule-based and model-based genre classification
  • AudioEventDetector<T> - AudioSet-style event detection
  • SceneClassifier<T> - DCASE-style acoustic scene classification
  • SoundLocalizer<T> - GCC-PHAT, MUSIC, SRP-PHAT localization algorithms

Audio Fingerprinting (#396)

  • ChromaprintFingerprinter<T> - AcoustID-compatible fingerprinting
  • SpectrogramFingerprinter<T> - Peak-based (Shazam-style)

Speaker Recognition (#396)

  • SpeakerEmbeddingExtractor<T> - d-vector extraction
  • SpeakerVerifier<T> - Cosine similarity verification
  • SpeakerDiarizer<T> - Clustering-based diarization

Music Analysis (#396)

  • BeatTracker<T> - Tempo and beat detection
  • ChordRecognizer<T> - Chord recognition via chroma templates
  • KeyDetector<T> - Musical key detection

Test plan

  • All 91 audio tests pass
  • All 59 ONNX tests pass
  • Build succeeds on net8.0
  • Build succeeds on net471
  • Manual testing of audio models with real audio files

Closes

Closes #280, #269, #270, #271, #396

🤖 Generated with Claude Code

ooples and others added 5 commits December 28, 2025 22:11
Phase 1 - OnnxModel Infrastructure (#280):
- Add IOnnxModel and IOnnxModelDownloader interfaces
- Implement OnnxModel<T> wrapper with multi-provider support
- Add OnnxTensorConverter for Tensor<T> <-> ONNX conversion
- Implement OnnxModelDownloader for HuggingFace Hub
- Support CPU, CUDA, TensorRT, DirectML execution providers

Phase 2 - Audio Features (#396):
- Add IAudioFeatureExtractor interface and base class
- Implement MfccExtractor using existing MelSpectrogram
- Implement ChromaExtractor for pitch class profiles
- Implement SpectralFeatureExtractor (centroid, bandwidth,
  rolloff, flux, flatness, contrast, zcr)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Whisper ASR (#269):
- WhisperModel with encoder-decoder ONNX pipeline
- WhisperTokenizer with multilingual support (99+ languages)
- WhisperOptions for model size, language, translate mode
- Auto-download from HuggingFace Hub

Text-to-Speech (#270):
- TtsModel with acoustic model + vocoder pipeline
- TtsPreprocessor for text normalization and G2P
- Griffin-Lim fallback when neural vocoder unavailable
- Speaking rate and pitch control

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… recognition

Implements remaining audio AI components:

- AudioGenModel: Text-conditioned audio generation with classifier-free
  guidance, top-k/top-p sampling, and EnCodec decoder
- Audio Fingerprinting: Chromaprint-style and Shazam-style peak-based
  fingerprinting with similarity matching
- Music Analysis: Beat tracking, chord recognition using chroma templates,
  and key detection with Krumhansl-Kessler profiles
- Speaker Recognition: Speaker embedding extraction, verification,
  identification, and diarization via agglomerative clustering

Also fixes .NET Framework 4.7.1 compatibility:
- Replace Math.Clamp with Math.Max/Math.Min pattern
- Replace Math.Log2 with MathHelper.Log2
- Fix string.Split and string.Replace overloads
- Add preprocessor directives for async Stream methods
- Fix nullable reference type warnings with pattern matching

Closes #271, #396

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add unit tests for all audio AI components:
- ONNX model tests (OnnxModelOptions, OnnxExecutionProvider, OnnxModelDownloader)
- Audio feature tests (MFCC, Chroma, Spectral features)
- Fingerprinting tests (Chromaprint, Spectrogram fingerprinting)
- Music analysis tests (BeatTracker, ChordRecognizer, KeyDetector)
- Speaker recognition tests (SpeakerEmbeddingExtractor, SpeakerVerifier, SpeakerDiarizer)

All 150 tests pass on net8.0.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ConstantQTransform for music analysis with logarithmic frequency resolution
- Add MusicSourceSeparator with HPSS-based harmonic-percussive separation
- Add GenreClassifier for rule-based and model-based music genre classification
- Add AudioEventDetector for AudioSet-style event detection
- Add SceneClassifier for DCASE-style acoustic scene classification
- Add SoundLocalizer with GCC-PHAT, MUSIC, and SRP-PHAT algorithms
- Add OnnxExporter for exporting neural networks to ONNX format

Addresses #280, #396

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings December 29, 2025 06:08
@coderabbitai

coderabbitai Bot commented Dec 29, 2025 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Major audio AI expansion: text-to-audio generation, multiple TTS engines, ASR/transcription, language ID, contrastive audio-text models, and audio latent-diffusion.
    • Rich analysis & music tools: genre/scene/event classification, beat/chord/key detection, pitch/chroma/MFCC/spectral features, emotion recognition.
    • Speaker tools: embeddings, enrollment/verification, diarization.
    • Audio ops: source separation, sound localization, fingerprinting, VAD, enhancement (ConvTasNet, DCCRN, DeepFilterNet), EQ/compressor/reverb.
    • ONNX & tooling: model loading, downloader, execution options, exporter and tensor converters.
  • Tests

    • Extensive unit tests for extractors, fingerprinting, music analysis, speaker pipeline, classification, and ONNX utilities.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds a large audio subsystem: ONNX runtime tooling and downloader, a shared AudioNeuralNetworkBase for ONNX/native dual-mode models, many audio models (generative AudioGen/AudioLDM, TTS, Whisper/Wav2Vec2 ASR, LID), feature extractors, classifiers, fingerprinters, separation/enhancement/effects, localization/VAD/pitch, many interfaces/DTOs, helpers/enums, and unit tests.

Changes

Cohort / File(s) Summary
ONNX core & tooling
src/Onnx/*, src/Onnx/OnnxModel.cs, src/Onnx/OnnxModelDownloader.cs, src/Onnx/OnnxExporter.cs, src/Onnx/OnnxModelOptions.cs, src/Onnx/OnnxExecutionProvider.cs, src/Onnx/OnnxModelMetadata.cs, src/Onnx/OnnxTensorConverter.cs
Add OnnxModel wrapper, metadata/tensor converters, exporter, HuggingFace downloader with cache/resume, execution provider enum and rich options. Review provider selection, session options, warm‑up, tensor conversions, and downloader caching.
Audio NN base & ONNX integration
src/Audio/AudioNeuralNetworkBase.cs
New abstract base enabling ONNX/native routing (OnnxEncoder/Decoder/Model holders), RunOnnxInference/Forward hooks, mel‑spectrogram helper, lifecycle and disposal for many audio models.
Generative audio
src/Audio/AudioGen/*, src/Audio/AudioLDM/*
Add AudioGenModel and AudioLDMModel with ONNX and native modes, options, model-size enums, result DTOs and generation APIs. Validate tokenizer/path checks, sampling parameters, duration controls and ONNX vs native decoder branches.
Text‑to‑Speech & preprocessing
src/Audio/TextToSpeech/*
Add TtsModel/Tacotron2/VITS models, TtsPreprocessor (G2P/normalization), options, vocoder enums, result DTOs, and streaming/stub APIs. Review G2P, phoneme/token mapping, Griffin‑Lim fallback, and serialization.
ASR / Whisper / Wav2Vec2
src/Audio/Whisper/*, src/Audio/SpeechRecognition/Wav2Vec2Model.cs
Add WhisperModel, WhisperTokenizer/Options/Results and Wav2Vec2Model with ONNX/native paths, token/timestamp utilities, decoding and metadata. Check token mapping and ONNX I/O shapes.
Feature extractors & spectral tools
src/Audio/Features/*, src/Interfaces/IAudioFeatureExtractor.cs
Add AudioFeatureExtractorBase, MfccExtractor, ChromaExtractor, ConstantQTransform, SpectralFeatureExtractor and related options/enums (WindowType, SpectralFeatureType, MfccOptions, ChromaOptions). Verify STFT/CQT/mel/DCT/filterbank correctness and generic numeric usage.
Classification & music analysis
src/Audio/Classification/*, src/Audio/MusicAnalysis/*
Add AudioClassifierBase, AudioEventDetector, GenreClassifier, SceneClassifier, BeatTracker, ChordRecognizer, KeyDetector and DTOs/options. Review softmax/top‑k, windowing, streaming APIs, event merging and threshold logic.
Fingerprinting & retrieval
src/Audio/Fingerprinting/*, src/Interfaces/IAudioFingerprinter.cs
Add AudioFingerprinterBase, ChromaprintFingerprinter, SpectrogramFingerprinter, options, fingerprint DTOs and matching/similarity algorithms. Check hash generation, Hamming heuristics and match alignment logic.
Speaker tooling
src/Audio/Speaker/*, src/Interfaces/*
Add SpeakerEmbeddingExtractor, SpeakerDiarizer, SpeakerVerifier, SpeakerEmbedding DTOs/options and interfaces. Inspect embedding normalization, enrollment centroid logic and clustering thresholds.
Separation, enhancement & effects
src/Audio/SourceSeparation/*, src/Audio/Enhancement/*, src/Audio/Effects/*
Add MusicSourceSeparator, NeuralNoiseReducer, ConvTasNet, DCCRN, DeepFilterNet, effects (Compressor, ParametricEqualizer, Reverb), separation/enhancer DTOs and options. Review STFT/ISTFT, overlap‑add, mask application, streaming buffers and numeric stability.
Localization, VAD & pitch
src/Audio/Localization/*, src/Audio/VoiceActivity/*, src/Audio/Pitch/*
Add SoundLocalizer (GCC‑PHAT/MUSIC/SRPPHAT + optional ONNX), EnergyBasedVad, PitchDetectorBase and YinPitchDetector. Validate TDOA, CMNDF/tau interpolation, windowing and geometry helpers.
Interfaces & DTOs
src/Interfaces/*
Many new interfaces and DTOs (IOnnxModel, IOnnxModelDownloader, IAudioFeatureExtractor, IAudioFingerprinter, IAudioGenerator, ISpeechRecognizer, ITextToSpeech, IBeatTracker, ISpeakerDiarizer, etc.). Verify one‑type‑per‑file, namespaces, XML docs and signatures.
Helpers, layers & enums
src/Helpers/*, src/NeuralNetworks/*, src/Enums/*
Add layer factories, LanguageModelBackbone and many model-size enums. Note duplicate CreateDefaultWhisperLayers in LayerHelper — deduplicate.
Tests & project
tests/AiDotNet.Tests/..., src/AiDotNet.csproj
Add unit tests for feature extractors, fingerprinting, music analysis and speaker tooling; add PackageReference FftSharp. Verify test coverage, numeric tolerances and presence of required ONNX/runtime package in csproj.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AudioGen as AudioGenModel<T>
    participant TextEnc as Text Encoder (ONNX/native)
    participant LM as Language Model (ONNX/native)
    participant Decoder as Audio Decoder (ONNX/native)

    Client->>AudioGen: GenerateAudio(prompt, options)
    activate AudioGen

    AudioGen->>TextEnc: EncodeText(prompt)
    activate TextEnc
    TextEnc-->>AudioGen: tokenEmbeddings
    deactivate TextEnc

    AudioGen->>LM: AutoregressiveGenerate(tokenEmbeddings, guidanceScale)
    activate LM
    note right of LM `#DDEBF7`: Generates discrete audio codes\n(possibly CFG-guided sampling)
    LM-->>AudioGen: audioCodes
    deactivate LM

    AudioGen->>Decoder: DecodeAudio(audioCodes, duration)
    activate Decoder
    note right of Decoder `#F7F1E1`: Decodes codes → waveform\n(ONNX or native decoder)
    Decoder-->>AudioGen: waveformTensor
    deactivate Decoder

    AudioGen-->>Client: Return waveformTensor
    deactivate AudioGen
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

"🐰 In my burrow I composed a tune,
From tokens, mel and midnight moon.
I hopped through tests and chased a bug,
Ate carrot‑bytes and shared a hug.
New models bloom beneath the moon."

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning ONNX runtime and exporter code are present, but the PR does not add the required Microsoft.ML.OnnxRuntime package reference mandated by issue #280. Add Microsoft.ML.OnnxRuntime (>=1.16) to AiDotNet.csproj, add explicit OnnxExporter->OnnxModel round-trip integration tests, and document execution-provider/opset choices for CI.
Out of Scope Changes check ⚠️ Warning This PR includes a very large audio AI stack (Whisper, TTS, AudioGen, classifiers, feature extractors, fingerprinting, etc.) that extends far beyond the ONNX/runtime/export scope of #280. Split the work: isolate ONNX runtime/exporter and its tests into a focused PR for #280, and submit the extensive audio-models and analysis features as separate PRs targeting #269, #270, #271, and #396.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.46% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: adding an audio AI stack with ONNX runtime, Whisper, TTS, AudioGen, and analysis components.
Description check ✅ Passed Description clearly summarizes the added components, linked issues, and test/build status and matches the changeset content.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/audio-ai-stack

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the feature Feature work item label Dec 29, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (16)
src/Audio/SourceSeparation/MusicSourceSeparator.cs-149-154 (1)

149-154: Cancellation token doesn't cancel in-progress separation.

The CancellationToken is only passed to Task.Run, which means once Separate starts executing, it cannot be interrupted. For long audio files, this could lead to unresponsive cancellation.

Consider adding cancellation checks within the separation loops, or documenting this limitation.

src/Audio/SourceSeparation/MusicSourceSeparator.cs-55-58 (1)

55-58: IsReady returns false for CPU-only instances.

When created via CreateCpuOnly, _model is null, so IsReady will always return false even though the separator can still perform spectral-based separation. Consider clarifying the property documentation or adjusting the logic to reflect actual separation capability.

🔎 Suggested fix
     /// <summary>
-    /// Gets whether the model is ready for separation.
+    /// Gets whether the neural network model is loaded and ready.
+    /// Note: CPU-only instances can still perform separation via HPSS even when this returns false.
     /// </summary>
-    public bool IsReady => _model?.IsLoaded == true;
+    public bool IsReady => _model?.IsLoaded ?? false;

Alternatively, add a separate property:

/// <summary>
/// Gets whether the separator can perform separation (either model-based or CPU-based).
/// </summary>
public bool CanSeparate => !_disposed;
src/Interfaces/IOnnxModelDownloader.cs-96-99 (1)

96-99: Minor: XML documentation says "Gets or sets" but property is read-only.

The FileProgress property is a computed read-only property (getter only), but the XML doc says "Gets or sets."

🔎 Proposed fix
     /// <summary>
-    /// Gets or sets the current file progress (0.0 to 1.0).
+    /// Gets the current file progress (0.0 to 1.0).
     /// </summary>
     public double FileProgress => TotalBytes > 0 ? (double)BytesDownloaded / TotalBytes : 0;
src/Audio/Features/AudioFeatureExtractorBase.cs-72-96 (1)

72-96: Tensor shape assumption may cause issues.

The Extract(Vector<T> audio) method assumes the output tensor has exactly 2 dimensions (features.Shape[0] and features.Shape[1]). If a derived class returns a different shape, this will throw an IndexOutOfRangeException.

🔎 Proposed defensive check
 public virtual Matrix<T> Extract(Vector<T> audio)
 {
     var audioTensor = new Tensor<T>([audio.Length]);
     for (int i = 0; i < audio.Length; i++)
     {
         audioTensor[i] = audio[i];
     }

     var features = Extract(audioTensor);

+    if (features.Shape.Length != 2)
+        throw new InvalidOperationException($"Expected 2D tensor output, got {features.Shape.Length}D");
+
     // Convert to Matrix
     int frames = features.Shape[0];
     int featureDim = features.Shape[1];
src/Audio/Features/AudioFeatureExtractorBase.cs-119-135 (1)

119-135: Potential division by zero in window functions.

When length is 1, the expression (length - 1) becomes 0, causing a division by zero in 2 * Math.PI * i / (length - 1).

🔎 Proposed fix
 protected T[] CreateHannWindow(int length)
 {
+    if (length <= 0)
+        throw new ArgumentOutOfRangeException(nameof(length), "Window length must be positive.");
+    if (length == 1)
+        return [NumOps.FromDouble(1.0)];
+
     var window = new T[length];

     for (int i = 0; i < length; i++)
src/Audio/TextToSpeech/TtsPreprocessor.cs-161-216 (1)

161-216: Integer overflow risk for Math.Abs(int.MinValue).

When number is int.MinValue, Math.Abs(number) throws OverflowException because int.MinValue has no positive counterpart in int.

🔎 Proposed fix
 private static string NumberToWords(int number)
 {
     if (number == 0) return "zero";
-    if (number < 0) return "minus " + NumberToWords(Math.Abs(number));
+    if (number == int.MinValue) return "minus two billion one hundred forty seven million four hundred eighty three thousand six hundred forty eight";
+    if (number < 0) return "minus " + NumberToWords(-number);
src/Audio/Speaker/SpeakerVerifier.cs-185-236 (1)

185-236: ComputeCentroid returns reference to original embedding when count is 1.

When only one embedding is provided, the method returns embeddings[0] directly. If the caller later modifies this embedding, it could corrupt the enrolled speaker's centroid. Consider returning a copy.

🔎 Proposed fix
 private SpeakerEmbedding<T> ComputeCentroid(IReadOnlyList<SpeakerEmbedding<T>> embeddings)
 {
     if (embeddings.Count == 1)
-        return embeddings[0];
+    {
+        // Return a copy to avoid external modifications affecting the enrolled centroid
+        var original = embeddings[0];
+        return new SpeakerEmbedding<T>
+        {
+            Vector = (T[])original.Vector.Clone(),
+            Duration = original.Duration,
+            NumFrames = original.NumFrames
+        };
+    }
src/Audio/Classification/SceneClassifier.cs-272-281 (1)

272-281: Potential division by zero if audio produces only one frame.

Line 280 divides by (numFrames - 1). If numFrames == 1, this causes a division by zero.

🔎 Proposed fix
             // Delta (first derivative)
             double sumDelta = 0;
             for (int t = 1; t < numFrames; t++)
             {
                 double prev = _numOps.ToDouble(mfccs[t - 1, c]);
                 double curr = _numOps.ToDouble(mfccs[t, c]);
                 sumDelta += Math.Abs(curr - prev);
             }
-            mfccDelta[c] = sumDelta / (numFrames - 1);
+            mfccDelta[c] = numFrames > 1 ? sumDelta / (numFrames - 1) : 0;
src/Audio/TextToSpeech/TtsModel.cs-153-180 (1)

153-180: Missing cleanup on partial failure in FromFiles.

If OnnxModel<T> construction for the vocoder (line 168) or GriffinLim<T> (line 173-176) throws after acousticModel is created (line 162), the acoustic model won't be disposed.

🔎 Proposed fix
 public static TtsModel<T> FromFiles(
     string acousticModelPath,
     string? vocoderPath = null,
     TtsOptions? options = null)
 {
     options ??= new TtsOptions();
     options.AcousticModelPath = acousticModelPath;
     options.VocoderModelPath = vocoderPath;

     var acousticModel = new OnnxModel<T>(acousticModelPath, options.OnnxOptions);
     OnnxModel<T>? vocoder = null;
     GriffinLim<T>? griffinLim = null;

+    try
+    {
         if (vocoderPath is not null && vocoderPath.Length > 0)
         {
             vocoder = new OnnxModel<T>(vocoderPath, options.OnnxOptions);
         }

         if (options.UseGriffinLimFallback || vocoder is null)
         {
             griffinLim = new GriffinLim<T>(
                 nFft: options.FftSize,
                 hopLength: options.HopLength,
                 iterations: options.GriffinLimIterations);
         }

         return new TtsModel<T>(options, acousticModel, vocoder, griffinLim);
+    }
+    catch
+    {
+        acousticModel.Dispose();
+        vocoder?.Dispose();
+        throw;
+    }
 }
src/Audio/Features/MfccExtractor.cs-51-60 (1)

51-60: FeatureDimension calculation is incorrect when both deltas are enabled.

When _appendDelta = true and _appendDeltaDelta = true:

  • Line 56: dim = 13 * 2 = 26
  • Line 57: dim = 26 / 2 * 3 = 39

But the actual output has 13 + 13 + 13 = 39 features. The math works here, but the logic is confusing and fragile. If _appendDeltaDelta is enabled without _appendDelta, it produces 13 / 2 * 3 = 18 (integer division), which doesn't match the actual behavior.

🔎 Proposed clearer implementation
 public override int FeatureDimension
 {
     get
     {
         int dim = _numCoefficients;
-        if (_appendDelta) dim *= 2;
-        if (_appendDeltaDelta) dim = dim / 2 * 3;
+        if (_appendDelta) dim += _numCoefficients;
+        if (_appendDeltaDelta) dim += _numCoefficients;
         return dim;
     }
 }
src/Audio/Classification/AudioEventDetector.cs-321-330 (1)

321-330: Guard against division by zero in ComputeEnergy.

If an empty audio tensor is passed, audio.Length will be 0, causing a division-by-zero.

🔎 Suggested fix
 private double ComputeEnergy(Tensor<T> audio)
 {
+    if (audio.Length == 0) return 0;
+    
     double sum = 0;
     for (int i = 0; i < audio.Length; i++)
     {
         double val = _numOps.ToDouble(audio[i]);
         sum += val * val;
     }
     return sum / audio.Length;
 }
src/Audio/Classification/GenreClassifier.cs-258-266 (1)

258-266: Guard against division by zero in ComputeMean.

If an empty tensor is passed, this will cause a division-by-zero exception.

🔎 Suggested fix
 private double ComputeMean(Tensor<T> tensor)
 {
+    if (tensor.Length == 0) return 0;
+    
     double sum = 0;
     for (int i = 0; i < tensor.Length; i++)
     {
         sum += _numOps.ToDouble(tensor[i]);
     }
     return sum / tensor.Length;
 }
src/Onnx/OnnxModelDownloader.cs-400-407 (1)

400-407: Misleading constant name in OnnxModelRepositories.AudioGen.

AudioGen.Small is assigned the value "facebook/audiogen-medium", which is contradictory. Either the constant should be renamed to Medium or the value should point to a "small" model.

🔎 Suggested fix
     public static class AudioGen
     {
-        /// <summary>AudioGen small model.</summary>
-        public const string Small = "facebook/audiogen-medium";
+        /// <summary>AudioGen medium model.</summary>
+        public const string Medium = "facebook/audiogen-medium";
 
         /// <summary>MusicGen small model.</summary>
         public const string MusicGenSmall = "facebook/musicgen-small";
     }
src/Audio/Classification/AudioEventDetector.cs-332-343 (1)

332-343: Guard against division by zero in ComputeZeroCrossingRate.

Similar to ComputeEnergy, this method will throw if an empty tensor is passed.

🔎 Suggested fix
 private double ComputeZeroCrossingRate(Tensor<T> audio)
 {
+    if (audio.Length <= 1) return 0;
+    
     int crossings = 0;
     for (int i = 1; i < audio.Length; i++)
     {
         double prev = _numOps.ToDouble(audio[i - 1]);
         double curr = _numOps.ToDouble(audio[i]);
         if ((prev >= 0 && curr < 0) || (prev < 0 && curr >= 0))
             crossings++;
     }
-    return (double)crossings / audio.Length;
+    return (double)crossings / (audio.Length - 1);
 }

Note: The normalization should arguably be audio.Length - 1 since that's the number of transitions examined.

src/Audio/Whisper/WhisperModel.cs-201-206 (1)

201-206: DetectedLanguage returns configured language, not detected.

When _options.Language is null or when the model could detect a different language than configured, this field is misleading. Consider renaming to Language or implementing actual language detection if Whisper's language detection output is available.

src/Onnx/OnnxModel.cs-246-251 (1)

246-251: Returning empty tensor on null output may cause downstream errors.

If output is null, returning new Tensor<T>([0]) could cause shape mismatches or unexpected behavior in calling code. Consider throwing an exception with a descriptive message instead.

🔎 Proposed fix
-        return output is not null
-            ? OnnxTensorConverter.FromOnnxFloat<T>(output)
-            : new Tensor<T>([0]);
+        if (output is null)
+            throw new InvalidOperationException($"ONNX model returned null output for input '{inputName}'.");
+        
+        return OnnxTensorConverter.FromOnnxFloat<T>(output);
🧹 Nitpick comments (38)
src/Audio/Features/ConstantQTransform.cs (2)

83-119: Add input parameter validation for robustness.

The constructor accepts several parameters but does not validate them. Invalid values could lead to division by zero, invalid array allocations, or nonsensical results. Consider adding validation:

  • sampleRate > 0
  • fMin > 0
  • binsPerOctave > 0 (critical: line 101 divides by this)
  • numOctaves > 0
  • hopLength > 0
🔎 Suggested validation
 public ConstantQTransform(
     int sampleRate = 22050,
     double fMin = 32.70,
     int binsPerOctave = 12,
     int numOctaves = 7,
     int hopLength = 512,
     WindowType windowType = WindowType.Hann)
 {
+    if (sampleRate <= 0)
+        throw new ArgumentOutOfRangeException(nameof(sampleRate), "Sample rate must be positive.");
+    if (fMin <= 0)
+        throw new ArgumentOutOfRangeException(nameof(fMin), "Minimum frequency must be positive.");
+    if (binsPerOctave <= 0)
+        throw new ArgumentOutOfRangeException(nameof(binsPerOctave), "Bins per octave must be positive.");
+    if (numOctaves <= 0)
+        throw new ArgumentOutOfRangeException(nameof(numOctaves), "Number of octaves must be positive.");
+    if (hopLength <= 0)
+        throw new ArgumentOutOfRangeException(nameof(hopLength), "Hop length must be positive.");
+
     _numOps = MathHelper.GetNumericOperations<T>();
     _sampleRate = sampleRate;

316-334: Consider adding bounds checking for bin index parameters.

Both GetMidiNote and GetNoteName accept a binIndex parameter but don't validate that it's within the valid range [0, _numBins). While callers should provide valid indices, adding a bounds check would provide clearer error messages for invalid usage.

🔎 Suggested bounds check
 public double GetMidiNote(int binIndex)
 {
+    if (binIndex < 0 || binIndex >= _numBins)
+        throw new ArgumentOutOfRangeException(nameof(binIndex), 
+            $"Bin index must be between 0 and {_numBins - 1}.");
     double frequency = _frequencies[binIndex];
     return 69 + 12 * (Math.Log(frequency / 440.0) / Math.Log(2));
 }
src/Audio/SourceSeparation/MusicSourceSeparator.cs (2)

417-418: Unused parameter harmonic.

The harmonic parameter is declared but never used in this method. Consider removing it.

🔎 Proposed fix
-private Tensor<T> ExtractBassline(Tensor<T> harmonic, Tensor<T> harmonicMag, Tensor<T> phase)
+private Tensor<T> ExtractBassline(Tensor<T> harmonicMag, Tensor<T> phase)

Update the call site on line 212:

-    Bass = ExtractBassline(harmonic, harmonicMag, phase),
+    Bass = ExtractBassline(harmonicMag, phase),

444-485: Silent fallback when mask dimensions don't match.

The method assumes 4 stems and silently uses mask=0 when dimensions don't match (line 465). If the ONNX model outputs unexpected shapes, this could produce empty stems without any warning.

Consider adding a log warning when the mask shape doesn't match expected dimensions to aid debugging.

src/Interfaces/IAudioFeatureExtractor.cs (1)

66-97: Consider adding validation for configuration values.

The options class lacks validation for edge cases that could cause downstream issues (e.g., negative or zero values for SampleRate, FftSize, or HopLength). Since these would cause division-by-zero or invalid array allocations in extractors, consider adding guards.

🔎 Proposed validation approach
 public class AudioFeatureOptions
 {
+    private int _sampleRate = 16000;
+    private int _fftSize = 512;
+    private int _hopLength = 160;
+
     /// <summary>
     /// Gets or sets the sample rate of the audio.
     /// </summary>
-    public int SampleRate { get; set; } = 16000;
+    public int SampleRate
+    {
+        get => _sampleRate;
+        set => _sampleRate = value > 0 ? value : throw new ArgumentOutOfRangeException(nameof(value), "SampleRate must be positive.");
+    }
src/Audio/MusicAnalysis/BeatTracker.cs (2)

133-167: Edge case: MinTempo > MaxTempo could cause unexpected behavior.

If MinTempo exceeds MaxTempo in options, minLag will be greater than maxLag, causing the loop at line 143 to never execute and bestLag to remain at minLag, potentially producing an invalid tempo.

🔎 Add validation in constructor or EstimateTempo
 public BeatTracker(BeatTrackerOptions? options = null)
 {
     _numOps = MathHelper.GetNumericOperations<T>();
     _options = options ?? new BeatTrackerOptions();
+
+    if (_options.MinTempo >= _options.MaxTempo)
+        throw new ArgumentException("MinTempo must be less than MaxTempo.");

107-112: Hardcoded feature index creates fragile coupling.

The spectral flux is accessed via hardcoded index 4, which assumes a specific feature order from SpectralFeatureExtractor. If that extractor's feature ordering changes, this will silently produce incorrect onset envelopes.

🔎 Consider using named feature access or a constant
+    // Spectral flux is at index 4 in SpectralFeatureExtractor output:
+    // [0]=Centroid, [1]=Bandwidth, [2]=Rolloff, [3]=Flux, [4]=Flatness
+    private const int SpectralFluxIndex = 3; // Verify against SpectralFeatureExtractor
+
     private double[] ComputeOnsetEnvelope(Tensor<T> audio)
     {
         // ...
         for (int f = 0; f < numFrames; f++)
         {
-            // Use spectral flux (index 4) as onset strength
-            onsetEnvelope[f] = Math.Max(0, _numOps.ToDouble(features[f, 4]));
+            onsetEnvelope[f] = Math.Max(0, _numOps.ToDouble(features[f, SpectralFluxIndex]));
         }

Verify the correct index for spectral flux in SpectralFeatureExtractor:

#!/bin/bash
# Check the feature ordering in SpectralFeatureExtractor
rg -n "Flux|flux" --type=cs -A2 -B2 src/Audio/Features/SpectralFeatureExtractor.cs
src/Audio/Fingerprinting/ChromaprintFingerprinter.cs (2)

96-112: Storing hash as generic type T may lose precision.

Converting uint hash to T via _numOps.FromDouble(hash) could lose precision when T is float (which has ~7 significant digits) for hash values exceeding ~16 million. This affects the Data array but not the Hash array which correctly stores uint[].

This is acceptable if Data is primarily used for correlation-based similarity (which normalizes values anyway), but worth documenting.


212-248: Cross-correlation alignment has O(n·m) complexity.

For long audio files, the hash arrays can be large, and the nested loop scanning all offsets may become slow. This is acceptable for typical use cases but worth noting for very long recordings.

Consider documenting performance characteristics or adding an optional early-exit threshold for production use with long audio.

src/Audio/Localization/SoundLocalizer.cs (2)

75-78: Use string.IsNullOrEmpty for consistency and clarity.

The current check _options.ModelPath is not null && _options.ModelPath.Length > 0 works but is verbose. The idiomatic C# approach is clearer.

🔎 Proposed simplification
-        if (_options.ModelPath is not null && _options.ModelPath.Length > 0)
+        if (!string.IsNullOrEmpty(_options.ModelPath))
         {
             _model = new OnnxModel<T>(_options.ModelPath, _options.OnnxOptions);
         }

510-518: Confidence calculation may produce unexpected results with negative GCC values.

gcc.Average() can be negative when GCC-PHAT has negative correlation values, making Math.Abs(mean) + 0.01 the denominator. While this works, using peak - mean or the standard deviation might provide a more meaningful sharpness metric.

tests/AiDotNet.Tests/Audio/Features/MfccExtractorTests.cs (1)

130-163: Frequency discrimination test could be more robust.

The test only compares the first 5 coefficients of the first frame with a threshold of 0.1. Consider comparing more frames or using a statistical measure across all frames to reduce brittleness.

tests/AiDotNet.Tests/Onnx/OnnxModelTests.cs (2)

47-55: Test verifies no exception but not cache directory behavior.

The test only asserts the downloader is not null. Consider also verifying that a default cache directory property is accessible or set.


191-219: Good coverage of factory methods, but missing ForTensorRT test.

The AI summary mentions ForTensorRT as a factory method, but there's no test for it here. Consider adding one for completeness.

🔎 Add test for ForTensorRT
[Fact]
public void OnnxModelOptions_ForTensorRT_SetsCorrectProvider()
{
    // Act
    var options = OnnxModelOptions.ForTensorRT();

    // Assert
    Assert.Equal(OnnxExecutionProvider.TensorRT, options.ExecutionProvider);
}
tests/AiDotNet.Tests/Audio/Fingerprinting/FingerprintingTests.cs (2)

63-88: Consider extracting audio generation into reusable helpers.

Lines 69-78 duplicate the signal generation pattern that could be generalized in the CreateTestAudio helper by adding a frequency parameter. This would reduce duplication and improve maintainability.

🔎 Proposed refactor
-        private static Tensor<float> CreateTestAudio(int sampleRate = 22050, double durationSeconds = 5.0)
+        private static Tensor<float> CreateTestAudio(int sampleRate = 22050, double durationSeconds = 5.0, double frequency = 440)
         {
             int numSamples = (int)(sampleRate * durationSeconds);
             var audio = new Tensor<float>([numSamples]);
 
-            // Create a complex audio signal with multiple frequencies
             for (int i = 0; i < numSamples; i++)
             {
                 double t = (double)i / sampleRate;
-                // Mix of frequencies to create more interesting fingerprint
-                audio[i] = (float)(
-                    0.5 * Math.Sin(2 * Math.PI * 440 * t) +
-                    0.3 * Math.Sin(2 * Math.PI * 880 * t) +
-                    0.2 * Math.Sin(2 * Math.PI * 1320 * t));
+                audio[i] = (float)Math.Sin(2 * Math.PI * frequency * t);
             }
 
             return audio;
         }
+
+        private static Tensor<float> CreateComplexTestAudio(int sampleRate = 22050, double durationSeconds = 5.0)
+        {
+            int numSamples = (int)(sampleRate * durationSeconds);
+            var audio = new Tensor<float>([numSamples]);
+
+            for (int i = 0; i < numSamples; i++)
+            {
+                double t = (double)i / sampleRate;
+                audio[i] = (float)(
+                    0.5 * Math.Sin(2 * Math.PI * 440 * t) +
+                    0.3 * Math.Sin(2 * Math.PI * 880 * t) +
+                    0.2 * Math.Sin(2 * Math.PI * 1320 * t));
+            }
+
+            return audio;
+        }

90-135: Consider adding symmetry for SpectrogramFingerprinter tests.

The SpectrogramFingerprinter has fewer tests compared to ChromaprintFingerprinter. Consider adding tests for DifferentAudio_DifferentFingerprint and similarity computation to ensure parity in coverage.

src/Audio/AudioGen/AudioGenOptions.cs (1)

35-43: Consider documenting the relationship between DurationSeconds and MaxDurationSeconds.

It's not immediately clear what happens if DurationSeconds exceeds MaxDurationSeconds. Consider adding XML documentation clarifying whether validation occurs or which value takes precedence.

src/Audio/Whisper/WhisperTokenizer.cs (1)

189-194: Consider validating negative time values.

GetTimestampToken doesn't validate if timeSeconds is negative, which would produce invalid token IDs (less than TimestampBeginId).

🔎 Proposed fix
 public int GetTimestampToken(double timeSeconds)
 {
+    if (timeSeconds < 0)
+    {
+        throw new ArgumentOutOfRangeException(nameof(timeSeconds), "Time must be non-negative.");
+    }
+
     // Whisper uses 20ms precision for timestamps
     int index = (int)(timeSeconds / 0.02);
     return TimestampBeginId + index;
 }
src/Audio/MusicAnalysis/KeyDetector.cs (1)

104-112: Consider using Span or avoiding element-by-element copy.

The Vector<T> overload copies each element individually to a new tensor. If performance becomes a concern for large audio, consider whether a more efficient conversion is possible.

src/Audio/TextToSpeech/TtsPreprocessor.cs (1)

45-68: Placeholder dictionary acknowledged; consider documenting production expectations.

The comments appropriately indicate this is a simplified implementation. The phoneme IDs in WordToPhonemes and CharToPhoneme conflict (e.g., CharToPhoneme['a'] = 1 and WordToPhonemes["hello"] = [1, 2, 3, 4]). This is fine for placeholder purposes but could cause confusion if extended without refactoring.

src/Audio/Features/AudioFeatureExtractorBase.cs (1)

188-203: Minor inefficiency in PadAudioCenter.

The loop initializing padded with zeros is unnecessary since new T[] already initializes value types to their default (zero) values.

🔎 Proposed simplification
 protected T[] PadAudioCenter(T[] audio)
 {
     int padAmount = WindowLength / 2;
     var padded = new T[audio.Length + 2 * padAmount];

-    // Initialize with zeros
-    for (int i = 0; i < padded.Length; i++)
-    {
-        padded[i] = NumOps.Zero;
-    }
-
     // Copy audio to center
     Array.Copy(audio, 0, padded, padAmount, audio.Length);

     return padded;
 }
src/Interfaces/IAudioFingerprinter.cs (1)

77-113: Consider using init accessor for immutable properties after construction.

Properties like Duration, SampleRate, and Algorithm are typically set once during fingerprint creation. Using init accessors would better express this intent and prevent accidental modification.

🔎 Proposed change
-    public double Duration { get; set; }
+    public double Duration { get; init; }
 
-    public int SampleRate { get; set; }
+    public int SampleRate { get; init; }
 
-    public string Algorithm { get; set; } = string.Empty;
+    public string Algorithm { get; init; } = string.Empty;
src/Audio/Features/SpectralFeatureExtractor.cs (1)

107-116: Redundant centroid computation when both Centroid and Bandwidth are enabled.

Spectral centroids are computed twice: once for the Centroid feature and once for Bandwidth calculation. Consider caching the result.

🔎 Proposed optimization
 // Extract features
 var features = new List<double[]>();
+double[]? centroids = null;
+
+// Compute centroids if needed by either feature
+if (_featureTypes.HasFlag(SpectralFeatureType.Centroid) || 
+    _featureTypes.HasFlag(SpectralFeatureType.Bandwidth))
+{
+    centroids = ComputeSpectralCentroid(magnitude, freqBins, numFrames, numFreqs);
+}

 if (_featureTypes.HasFlag(SpectralFeatureType.Centroid))
 {
-    features.Add(ComputeSpectralCentroid(magnitude, freqBins, numFrames, numFreqs));
+    features.Add(centroids!);
 }

 if (_featureTypes.HasFlag(SpectralFeatureType.Bandwidth))
 {
-    var centroids = ComputeSpectralCentroid(magnitude, freqBins, numFrames, numFreqs);
     features.Add(ComputeSpectralBandwidth(magnitude, freqBins, centroids, numFrames, numFreqs));
 }
src/Audio/Fingerprinting/SpectrogramFingerprinter.cs (1)

174-225: Unused hashToTime dictionary.

The hashToTime dictionary is populated but never used in this method or returned in the fingerprint. Either remove it or include it in the fingerprint metadata for debugging/matching purposes.

🔎 Option 1: Remove unused variable
 private AudioFingerprint<T> CreateFingerprintFromPeaks(List<SpectralPeak> peaks, int audioLength)
 {
     var hashes = new List<uint>();
-    var hashToTime = new Dictionary<uint, double>();

     // Create hashes from peak pairs (anchor + target)
     for (int i = 0; i < peaks.Count; i++)
     {
         var anchor = peaks[i];

         // Find target peaks within the target zone
         for (int j = i + 1; j < peaks.Count; j++)
         {
             var target = peaks[j];

             int timeDiff = target.Frame - anchor.Frame;
             if (timeDiff < _options.TargetZoneStart)
                 continue;
             if (timeDiff > _options.TargetZoneEnd)
                 break;

             // Create hash: freq1 (10 bits) | freq2 (10 bits) | time delta (12 bits)
             uint hash = CreateHash(anchor.Bin, target.Bin, timeDiff);
             hashes.Add(hash);
-
-            // Store time for this hash
-            double time = anchor.Frame * _options.HopLength / (double)_options.SampleRate;
-            if (!hashToTime.ContainsKey(hash))
-            {
-                hashToTime[hash] = time;
-            }
         }
     }
tests/AiDotNet.Tests/Audio/Speaker/SpeakerTests.cs (2)

36-50: Consider disposing IDisposable instances in tests.

SpeakerEmbeddingExtractor<T> implements IDisposable. While xUnit handles some cleanup, explicitly disposing or using using statements ensures resources are released promptly, especially if the extractor holds ONNX sessions or other native resources.

🔎 Example fix for one test
 [Fact]
 public void SpeakerEmbeddingExtractor_Extract_ReturnsEmbedding()
 {
     // Arrange
-    var extractor = new SpeakerEmbeddingExtractor<float>();
-    var audio = CreateSpeakerAudio();
+    using var extractor = new SpeakerEmbeddingExtractor<float>();
+    var audio = CreateSpeakerAudio();

     // Act
     var embedding = extractor.Extract(audio);

226-240: Dispose SpeakerDiarizer instances after use.

Same as above—SpeakerDiarizer<T> implements IDisposable and owns a SpeakerEmbeddingExtractor. Use using to ensure cleanup.

src/Onnx/OnnxTensorConverter.cs (1)

32-47: Unused variable shape on line 35.

The shape variable is computed but never used. The DenseTensor constructor on line 36 uses tensor.Shape directly.

🔎 Proposed fix
 public static OnnxTensors.DenseTensor<float> ToOnnxFloat<T>(Tensor<T> tensor)
 {
     var numOps = MathHelper.GetNumericOperations<T>();
-    var shape = tensor.Shape.Select(d => (long)d).ToArray();
     var onnxTensor = new OnnxTensors.DenseTensor<float>(tensor.Shape);

     var sourceData = tensor.ToArray();
src/Audio/Classification/AudioEventDetector.cs (2)

120-131: CreateAsync mutates the passed-in options object.

The options.ModelPath is assigned in the CreateAsync method, which mutates the caller's options object if they passed one. This could lead to unexpected side effects.

🔎 Suggested fix: Clone options before mutation
 public static async Task<AudioEventDetector<T>> CreateAsync(
     AudioEventDetectorOptions? options = null,
     IProgress<double>? progress = null,
     CancellationToken cancellationToken = default)
 {
     options ??= new AudioEventDetectorOptions();
+    
+    // Clone to avoid mutating caller's options
+    options = new AudioEventDetectorOptions
+    {
+        SampleRate = options.SampleRate,
+        FftSize = options.FftSize,
+        HopLength = options.HopLength,
+        NumMels = options.NumMels,
+        FMin = options.FMin,
+        FMax = options.FMax,
+        WindowSize = options.WindowSize,
+        WindowOverlap = options.WindowOverlap,
+        Threshold = options.Threshold,
+        CustomLabels = options.CustomLabels,
+        ModelPath = options.ModelPath,
+        OnnxOptions = options.OnnxOptions
+    };
 
     if (options.ModelPath is null || options.ModelPath.Length == 0)
     {

146-150: Potential timing mismatch between window start calculation and hop logic.

The startTime calculation at line 149 computes windowIdx * _options.WindowSize * (1 - _options.WindowOverlap), but this should ideally use the same hop calculation as SplitIntoWindows for consistency: windowIdx * hopSamples / _options.SampleRate.

🔎 Suggested fix
         for (int windowIdx = 0; windowIdx < windows.Count; windowIdx++)
         {
             var window = windows[windowIdx];
-            double startTime = windowIdx * _options.WindowSize * (1 - _options.WindowOverlap);
+            int windowSamples = (int)(_options.WindowSize * _options.SampleRate);
+            int hopSamples = (int)(windowSamples * (1 - _options.WindowOverlap));
+            double startTime = (double)(windowIdx * hopSamples) / _options.SampleRate;
src/Audio/Speaker/SpeakerEmbeddingExtractor.cs (1)

278-298: Similarity calculation silently truncates to minimum length.

When comparing embeddings of different lengths, CosineSimilarity uses Math.Min(Vector.Length, other.Vector.Length). This could lead to incorrect similarity scores if embeddings were extracted with different EmbeddingDimension settings. Consider throwing or warning when lengths differ.

🔎 Suggested fix - add validation
 public double CosineSimilarity(SpeakerEmbedding<T> other)
 {
+    if (Vector.Length != other.Vector.Length)
+    {
+        throw new ArgumentException(
+            $"Embedding dimensions must match. Got {Vector.Length} and {other.Vector.Length}.",
+            nameof(other));
+    }
+
     double dot = 0;
     double norm1 = 0;
     double norm2 = 0;
 
-    int len = Math.Min(Vector.Length, other.Vector.Length);
+    int len = Vector.Length;
tests/AiDotNet.Tests/Audio/MusicAnalysis/MusicAnalysisTests.cs (1)

119-132: Test validates internal consistency rather than correctness.

The BeatTracker_AverageBeatInterval_MatchesTempo test calculates expectedInterval = 60.0 / result.Tempo and compares it to result.AverageBeatInterval. This only verifies that these two properties are consistent with each other, not that either value is correct. Consider adding a test that validates against the known input BPM (120).

🔎 Suggested additional test
[Fact]
public void BeatTracker_DetectedTempo_CloseToInputBpm()
{
    // Arrange
    double inputBpm = 120;
    var tracker = new BeatTracker<float>();
    var audio = CreateRhythmicAudio(bpm: inputBpm);

    // Act
    var result = tracker.Track(audio);

    // Assert - detected tempo should be within 10% of input
    Assert.True(Math.Abs(result.Tempo - inputBpm) < inputBpm * 0.1,
        $"Detected tempo {result.Tempo} should be close to input {inputBpm}");
}
src/Audio/AudioGen/AudioGenModel.cs (1)

373-386: Quadratic memory allocation in autoregressive loop.

Each iteration creates a new tensor and copies all previous values. For numTokens = DurationSeconds * 50 tokens, this results in O(n²) memory allocations and copies. Consider pre-allocating a larger buffer or using a different data structure.

🔎 Suggested optimization
+        // Pre-allocate for all tokens
+        var allTokens = new Tensor<T>([1, numCodebooks, numTokens + 1]);
+        
+        // Initialize with start token
         for (int cb = 0; cb < numCodebooks; cb++)
         {
-            currentTokens[0, cb, 0] = _numOps.FromDouble(0);
+            allTokens[0, cb, 0] = _numOps.FromDouble(0);
         }
 
         for (int t = 0; t < numTokens; t++)
         {
+            // Create view of current tokens (0 to t inclusive)
+            var currentLength = t + 1;
+            var currentTokens = new Tensor<T>([1, numCodebooks, currentLength]);
+            for (int cb = 0; cb < numCodebooks; cb++)
+            {
+                for (int i = 0; i < currentLength; i++)
+                {
+                    currentTokens[0, cb, i] = allTokens[0, cb, i];
+                }
+            }
+            
             // ... rest of loop ...
             
             // Store generated tokens directly
             for (int cb = 0; cb < numCodebooks; cb++)
             {
                 int nextToken = SampleFromLogits(logits, cb, random);
                 codes[0, cb, t] = _numOps.FromDouble(nextToken);
-
-                // Update current tokens for next iteration
-                if (t < numTokens - 1)
-                {
-                    // ... expensive reallocation ...
-                }
+                allTokens[0, cb, t + 1] = _numOps.FromDouble(nextToken);
             }
         }

Note: A better approach would be if the language model can accept a padded tensor with an attention mask.

src/Onnx/OnnxModelDownloader.cs (1)

247-261: Error handling after resume check could leave partial file in inconsistent state.

If the server returns an error status other than 416 (e.g., 404 Not Found), EnsureSuccessStatusCode() throws, but the .partial file isn't cleaned up. Subsequent download attempts might try to resume from a corrupted state.

🔎 Suggested improvement
         // Handle non-resumable download
         if (response.StatusCode == System.Net.HttpStatusCode.RequestedRangeNotSatisfiable)
         {
             existingLength = 0;
             File.Delete(tempPath);
         }
-        else
+        else if (!response.IsSuccessStatusCode)
         {
-            response.EnsureSuccessStatusCode();
+            // Clean up partial file on error
+            if (File.Exists(tempPath))
+            {
+                File.Delete(tempPath);
+            }
+            response.EnsureSuccessStatusCode(); // Will throw
         }
src/Onnx/OnnxExporter.cs (2)

34-37: Unused constants.

IrVersion (line 35) is defined but the hardcoded value 8 is used directly in Build() at line 521. Consider using the constant for consistency.

🔎 Suggested fix
-        modelBytes.AddRange(CreateVarintField(FieldIrVersion, 8)); // ir_version
+        modelBytes.AddRange(CreateVarintField(FieldIrVersion, OnnxExporter.IrVersion)); // ir_version

Note: This requires making IrVersion accessible to OnnxModelBuilder, either by passing it as a parameter or making the constant internal.


247-249: Silently skipping unsupported layers could produce incorrect models.

Returning null for unsupported layers causes them to be silently skipped, which could result in a disconnected graph or incorrect model behavior. Consider logging a warning or throwing for critical layers.

🔎 Suggested improvement
         if (layerTypeName.Contains("Flatten"))
         {
             var outputName = $"flatten_{nodeIndex}";
             builder.AddFlatten(inputName, outputName);
             return outputName;
         }
 
-        // Skip unsupported layers
+        // Log warning for unsupported layers - they will be skipped
+        System.Diagnostics.Debug.WriteLine(
+            $"Warning: Skipping unsupported layer type '{layerTypeName}' at index {nodeIndex}");
+        
         return null;
     }

Alternatively, consider throwing NotSupportedException with the layer type name to fail fast.

src/Audio/Whisper/WhisperModel.cs (1)

244-277: Consider using Span<T> or Array.Copy for better performance.

The element-by-element copy loop works correctly but could be optimized. However, this is a minor optimization and the current implementation is correct.

src/Onnx/OnnxModel.cs (2)

259-270: Silent exception swallowing hinders debugging.

Catching and ignoring all exceptions when trying providers makes it difficult to diagnose why a specific provider failed. Consider logging the exception or collecting failures to report if all providers fail.

🔎 Proposed improvement
+        var exceptions = new List<Exception>();
         foreach (var provider in providers)
         {
             try
             {
                 var sessionOptions = CreateSessionOptions(provider);
                 var session = new InferenceSession(modelPath, sessionOptions);
                 return (session, provider.ToString());
             }
-            catch (Exception)
+            catch (Exception ex)
             {
-                // Try next provider
+                exceptions.Add(ex);
                 continue;
             }
         }

         // Fallback to CPU
-        var cpuOptions = CreateSessionOptions(OnnxExecutionProvider.Cpu);
-        return (new InferenceSession(modelPath, cpuOptions), "CPU");
+        try
+        {
+            var cpuOptions = CreateSessionOptions(OnnxExecutionProvider.Cpu);
+            return (new InferenceSession(modelPath, cpuOptions), "CPU");
+        }
+        catch (Exception ex)
+        {
+            throw new AggregateException(
+                "Failed to create ONNX session with any provider.", 
+                exceptions.Append(ex));
+        }

517-545: Inefficient memory handling for large model downloads.

Using List<byte> with AddRange(buffer.Take(bytesRead)) is inefficient:

  • Take() creates an intermediate enumerable each iteration
  • List<byte> may reallocate multiple times during download
  • For large models (hundreds of MB), this causes significant memory pressure
🔎 Proposed fix using MemoryStream
-            var bytes = new List<byte>();
+            using var memoryStream = new MemoryStream();

 #if NET6_0_OR_GREATER
             using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
 #else
             using var stream = await response.Content.ReadAsStreamAsync();
 #endif
             var buffer = new byte[8192];
             int bytesRead;
             long totalBytesRead = 0;

 #if NET6_0_OR_GREATER
             while ((bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken)) > 0)
 #else
             while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
 #endif
             {
-                bytes.AddRange(buffer.Take(bytesRead));
+                memoryStream.Write(buffer, 0, bytesRead);
                 totalBytesRead += bytesRead;

                 if (totalBytes > 0)
                 {
                     progress?.Report((double)totalBytesRead / totalBytes);
                 }
             }

             progress?.Report(1.0);
-            return new OnnxModel<T>([.. bytes], options);
+            return new OnnxModel<T>(memoryStream.ToArray(), options);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b657b05 and 5aa7391.

📒 Files selected for processing (44)
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/AudioGen/AudioGenOptions.cs
  • src/Audio/Classification/AudioEventDetector.cs
  • src/Audio/Classification/GenreClassifier.cs
  • src/Audio/Classification/SceneClassifier.cs
  • src/Audio/Features/AudioFeatureExtractorBase.cs
  • src/Audio/Features/ChromaExtractor.cs
  • src/Audio/Features/ConstantQTransform.cs
  • src/Audio/Features/MfccExtractor.cs
  • src/Audio/Features/SpectralFeatureExtractor.cs
  • src/Audio/Fingerprinting/ChromaprintFingerprinter.cs
  • src/Audio/Fingerprinting/SpectrogramFingerprinter.cs
  • src/Audio/Localization/SoundLocalizer.cs
  • src/Audio/MusicAnalysis/BeatTracker.cs
  • src/Audio/MusicAnalysis/ChordRecognizer.cs
  • src/Audio/MusicAnalysis/KeyDetector.cs
  • src/Audio/SourceSeparation/MusicSourceSeparator.cs
  • src/Audio/Speaker/SpeakerDiarizer.cs
  • src/Audio/Speaker/SpeakerEmbeddingExtractor.cs
  • src/Audio/Speaker/SpeakerVerifier.cs
  • src/Audio/TextToSpeech/TtsModel.cs
  • src/Audio/TextToSpeech/TtsOptions.cs
  • src/Audio/TextToSpeech/TtsPreprocessor.cs
  • src/Audio/Whisper/WhisperModel.cs
  • src/Audio/Whisper/WhisperOptions.cs
  • src/Audio/Whisper/WhisperTokenizer.cs
  • src/Interfaces/IAudioFeatureExtractor.cs
  • src/Interfaces/IAudioFingerprinter.cs
  • src/Interfaces/IOnnxModel.cs
  • src/Interfaces/IOnnxModelDownloader.cs
  • src/Onnx/OnnxExecutionProvider.cs
  • src/Onnx/OnnxExporter.cs
  • src/Onnx/OnnxModel.cs
  • src/Onnx/OnnxModelDownloader.cs
  • src/Onnx/OnnxModelMetadata.cs
  • src/Onnx/OnnxModelOptions.cs
  • src/Onnx/OnnxTensorConverter.cs
  • tests/AiDotNet.Tests/Audio/Features/ChromaExtractorTests.cs
  • tests/AiDotNet.Tests/Audio/Features/MfccExtractorTests.cs
  • tests/AiDotNet.Tests/Audio/Features/SpectralFeatureExtractorTests.cs
  • tests/AiDotNet.Tests/Audio/Fingerprinting/FingerprintingTests.cs
  • tests/AiDotNet.Tests/Audio/MusicAnalysis/MusicAnalysisTests.cs
  • tests/AiDotNet.Tests/Audio/Speaker/SpeakerTests.cs
  • tests/AiDotNet.Tests/Onnx/OnnxModelTests.cs
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-18T08:49:25.295Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningMask.cs:1-102
Timestamp: 2025-12-18T08:49:25.295Z
Learning: In the AiDotNet repository, the project-level global using includes AiDotNet.Tensors.LinearAlgebra via AiDotNet.csproj. Therefore, Vector<T>, Matrix<T>, and Tensor<T> are available without per-file using directives. Do not flag missing using directives for these types in any C# files within this project. Apply this guideline broadly to all C# files (not just a single file) to avoid false positives. If a file uses a type from a different namespace not covered by the global using, flag as usual.

Applied to files:

  • tests/AiDotNet.Tests/Onnx/OnnxModelTests.cs
  • tests/AiDotNet.Tests/Audio/Features/MfccExtractorTests.cs
  • src/Onnx/OnnxExecutionProvider.cs
  • src/Audio/TextToSpeech/TtsOptions.cs
  • src/Interfaces/IOnnxModelDownloader.cs
  • tests/AiDotNet.Tests/Audio/Features/SpectralFeatureExtractorTests.cs
  • src/Audio/MusicAnalysis/BeatTracker.cs
  • tests/AiDotNet.Tests/Audio/Fingerprinting/FingerprintingTests.cs
  • src/Interfaces/IAudioFeatureExtractor.cs
  • tests/AiDotNet.Tests/Audio/Features/ChromaExtractorTests.cs
  • src/Audio/Whisper/WhisperTokenizer.cs
  • src/Audio/Features/ChromaExtractor.cs
  • src/Interfaces/IOnnxModel.cs
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/AudioGen/AudioGenOptions.cs
  • src/Audio/Features/AudioFeatureExtractorBase.cs
  • src/Audio/Classification/SceneClassifier.cs
  • src/Audio/Features/ConstantQTransform.cs
  • src/Audio/Features/MfccExtractor.cs
  • src/Audio/Speaker/SpeakerVerifier.cs
  • src/Onnx/OnnxTensorConverter.cs
  • src/Audio/MusicAnalysis/KeyDetector.cs
  • src/Audio/Classification/AudioEventDetector.cs
  • src/Audio/Features/SpectralFeatureExtractor.cs
  • src/Audio/Classification/GenreClassifier.cs
  • src/Onnx/OnnxModelMetadata.cs
  • src/Audio/TextToSpeech/TtsPreprocessor.cs
  • tests/AiDotNet.Tests/Audio/MusicAnalysis/MusicAnalysisTests.cs
  • src/Audio/TextToSpeech/TtsModel.cs
  • src/Audio/Speaker/SpeakerDiarizer.cs
  • src/Interfaces/IAudioFingerprinter.cs
  • src/Audio/Speaker/SpeakerEmbeddingExtractor.cs
  • src/Audio/Whisper/WhisperModel.cs
  • tests/AiDotNet.Tests/Audio/Speaker/SpeakerTests.cs
  • src/Onnx/OnnxModelDownloader.cs
  • src/Audio/MusicAnalysis/ChordRecognizer.cs
  • src/Audio/Fingerprinting/SpectrogramFingerprinter.cs
  • src/Audio/SourceSeparation/MusicSourceSeparator.cs
  • src/Onnx/OnnxModel.cs
  • src/Onnx/OnnxExporter.cs
  • src/Audio/Localization/SoundLocalizer.cs
  • src/Audio/Fingerprinting/ChromaprintFingerprinter.cs
  • src/Onnx/OnnxModelOptions.cs
  • src/Audio/Whisper/WhisperOptions.cs
📚 Learning: 2025-12-18T08:49:53.103Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningStrategy.cs:1-4
Timestamp: 2025-12-18T08:49:53.103Z
Learning: In this repository, global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*) and common system types. When reviewing C# files, assume these global usings are in effect; avoid adding duplicate using statements for these namespaces and for types like Vector<T>, Matrix<T>, Tensor<T>, etc. If a type is not found, verify the global usings or consider adding a file-scoped using if needed. Prefer relying on global usings to reduce boilerplate.

Applied to files:

  • tests/AiDotNet.Tests/Onnx/OnnxModelTests.cs
  • tests/AiDotNet.Tests/Audio/Features/MfccExtractorTests.cs
  • src/Onnx/OnnxExecutionProvider.cs
  • src/Audio/TextToSpeech/TtsOptions.cs
  • src/Interfaces/IOnnxModelDownloader.cs
  • tests/AiDotNet.Tests/Audio/Features/SpectralFeatureExtractorTests.cs
  • src/Audio/MusicAnalysis/BeatTracker.cs
  • tests/AiDotNet.Tests/Audio/Fingerprinting/FingerprintingTests.cs
  • src/Interfaces/IAudioFeatureExtractor.cs
  • tests/AiDotNet.Tests/Audio/Features/ChromaExtractorTests.cs
  • src/Audio/Whisper/WhisperTokenizer.cs
  • src/Audio/Features/ChromaExtractor.cs
  • src/Interfaces/IOnnxModel.cs
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/AudioGen/AudioGenOptions.cs
  • src/Audio/Features/AudioFeatureExtractorBase.cs
  • src/Audio/Classification/SceneClassifier.cs
  • src/Audio/Features/ConstantQTransform.cs
  • src/Audio/Features/MfccExtractor.cs
  • src/Audio/Speaker/SpeakerVerifier.cs
  • src/Onnx/OnnxTensorConverter.cs
  • src/Audio/MusicAnalysis/KeyDetector.cs
  • src/Audio/Classification/AudioEventDetector.cs
  • src/Audio/Features/SpectralFeatureExtractor.cs
  • src/Audio/Classification/GenreClassifier.cs
  • src/Onnx/OnnxModelMetadata.cs
  • src/Audio/TextToSpeech/TtsPreprocessor.cs
  • tests/AiDotNet.Tests/Audio/MusicAnalysis/MusicAnalysisTests.cs
  • src/Audio/TextToSpeech/TtsModel.cs
  • src/Audio/Speaker/SpeakerDiarizer.cs
  • src/Interfaces/IAudioFingerprinter.cs
  • src/Audio/Speaker/SpeakerEmbeddingExtractor.cs
  • src/Audio/Whisper/WhisperModel.cs
  • tests/AiDotNet.Tests/Audio/Speaker/SpeakerTests.cs
  • src/Onnx/OnnxModelDownloader.cs
  • src/Audio/MusicAnalysis/ChordRecognizer.cs
  • src/Audio/Fingerprinting/SpectrogramFingerprinter.cs
  • src/Audio/SourceSeparation/MusicSourceSeparator.cs
  • src/Onnx/OnnxModel.cs
  • src/Onnx/OnnxExporter.cs
  • src/Audio/Localization/SoundLocalizer.cs
  • src/Audio/Fingerprinting/ChromaprintFingerprinter.cs
  • src/Onnx/OnnxModelOptions.cs
  • src/Audio/Whisper/WhisperOptions.cs
📚 Learning: 2025-12-19T19:05:13.598Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 445
File: src/Interfaces/IPredictionModelBuilder.cs:7-8
Timestamp: 2025-12-19T19:05:13.598Z
Learning: In ooples/AiDotNet, IAggregationStrategy<TModel> is declared in AiDotNet.Interfaces (src/Interfaces/IAggregationStrategy.cs). For src/Interfaces/IPredictionModelBuilder.cs (also in AiDotNet.Interfaces), referencing IAggregationStrategy does not require an additional using. Avoid flagging missing using directives for this case in future reviews.

Applied to files:

  • src/Interfaces/IOnnxModelDownloader.cs
  • src/Interfaces/IOnnxModel.cs
  • src/Onnx/OnnxModel.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CodeQL analysis (csharp)
  • GitHub Check: CodeQL Analysis
  • GitHub Check: Build (Windows)

Comment thread src/Audio/AudioGen/AudioGenModel.cs Outdated
Comment thread src/Audio/AudioGen/AudioGenModel.cs
Comment thread src/Audio/SourceSeparation/MusicSourceSeparator.cs
Comment thread src/Audio/SourceSeparation/MusicSourceSeparator.cs
Comment thread src/Audio/Speaker/SpeakerVerifier.cs Outdated
Comment thread src/Audio/Whisper/WhisperModel.cs Outdated
Comment thread src/Onnx/OnnxModel.cs
Comment thread src/Onnx/OnnxModelDownloader.cs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive Audio AI stack covering ONNX Runtime integration, speech-to-text (Whisper), text-to-speech (FastSpeech2 + HiFi-GAN), audio generation (AudioGen), and extensive audio analysis capabilities including speaker recognition, music analysis, and audio fingerprinting.

Key Changes:

  • ONNX Runtime infrastructure with multi-provider support (CPU, CUDA, TensorRT, DirectML)
  • Whisper ASR model with encoder-decoder architecture and tokenization
  • TTS pipeline with acoustic model and vocoder components
  • Comprehensive audio analysis: MFCCs, chroma features, spectral features, beat tracking, chord recognition, key detection
  • Speaker recognition: embedding extraction, verification, and diarization
  • Audio fingerprinting with Chromaprint and spectrogram-based approaches
  • Music source separation using HPSS and neural network-based methods

Reviewed changes

Copilot reviewed 44 out of 44 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/AiDotNet.Tests/Onnx/OnnxModelTests.cs Unit tests for ONNX model infrastructure
tests/AiDotNet.Tests/Audio/Speaker/SpeakerTests.cs Tests for speaker embedding, verification, and diarization
tests/AiDotNet.Tests/Audio/MusicAnalysis/MusicAnalysisTests.cs Tests for beat tracking, chord recognition, and key detection
tests/AiDotNet.Tests/Audio/Fingerprinting/FingerprintingTests.cs Tests for audio fingerprinting algorithms
tests/AiDotNet.Tests/Audio/Features/*.cs Tests for MFCC, chroma, and spectral feature extraction
src/Onnx/*.cs ONNX Runtime integration with model loading, tensor conversion, and model downloading
src/Interfaces/*.cs Interface definitions for ONNX models, audio feature extraction, and fingerprinting
src/Audio/Whisper/*.cs Whisper speech recognition implementation with tokenization
src/Audio/TextToSpeech/*.cs TTS model with text preprocessing and synthesis pipeline
src/Audio/Speaker/*.cs Speaker recognition components including embeddings, verification, and diarization
src/Audio/SourceSeparation/*.cs Music source separation with HPSS and neural network approaches
src/Audio/MusicAnalysis/*.cs Chord recognition and key detection using chromagram analysis
src/Audio/AudioGen/*.cs Audio generation configuration and options

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

ooples and others added 8 commits December 29, 2025 11:37
Add comprehensive interfaces following IFullModel pattern:
- ISpeechRecognizer for ASR/transcription (Whisper-style)
- ITextToSpeech for TTS synthesis
- IAudioGenerator for audio/music generation

Speaker recognition:
- ISpeakerEmbeddingExtractor for d-vector extraction
- ISpeakerVerifier for speaker verification
- ISpeakerDiarizer for speaker diarization

Music analysis:
- IBeatTracker for tempo/beat detection
- IChordRecognizer for chord recognition
- IKeyDetector for musical key detection
- IMusicSourceSeparator for source separation

Classification:
- IGenreClassifier for genre classification
- IAudioEventDetector for event detection
- ISceneClassifier for scene classification
- ISoundLocalizer for sound localization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Create 5 intermediate base classes for audio processing:

- AudioNeuralNetworkBase<T>: Core audio NN base extending NeuralNetworkBase<T>
  with ONNX support, mel spectrogram utilities, and dual-mode inference
- SpeakerRecognitionBase<T>: Speaker recognition base with cosine similarity,
  embedding normalization, and MFCC extractor utilities
- MusicAnalysisBase<T>: Music analysis base with onset detection, tempogram,
  and chromagram extraction for beat/chord/key analysis
- AudioClassifierBase<T>: Classification base with softmax, top-k predictions,
  and class weight computation for genre/event/scene classification
- AudioFingerprinterBase<T>: Fingerprinting base implementing IAudioFingerprinter<T>
  with Hamming distance and alignment utilities

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Refactor WhisperModel to extend AudioNeuralNetworkBase and implement ISpeechRecognizer
- Refactor TtsModel to extend AudioNeuralNetworkBase and implement ITextToSpeech
- Refactor AudioGenModel to extend AudioNeuralNetworkBase and implement IAudioGenerator
- Add proper NeuralNetworkArchitecture configuration for ONNX-only models
- Implement required NeuralNetworkBase abstract methods (Predict, Train, UpdateParameters, etc.)
- Add correct ModelMetadata implementation using proper property names
- Use appropriate task types (SpeechRecognition, Generative) and complexity (VeryDeep, Deep)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Refactor ChromaprintFingerprinter to extend AudioFingerprinterBase<T>
- Refactor SpectrogramFingerprinter to extend AudioFingerprinterBase<T>
- Add override keyword for abstract/virtual methods from base class
- Use NumOps from base class instead of private field
- Set SampleRate and FingerprintLength through base class properties

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- BeatTracker now extends MusicAnalysisBase<T>
- ChordRecognizer now extends MusicAnalysisBase<T>
- KeyDetector now extends MusicAnalysisBase<T>
- Use NumOps from base class instead of private field
- Set base class properties (SampleRate, HopLength, FftSize) in constructors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use protected NumOps field instead of private _numOps for extensibility
- Add useful public properties (SampleRate, thresholds, etc.)
- Add ExtractTensor and ComputeSimilarity helper methods to extractor
- Maintain lightweight utility class design (no heavy interface inheritance)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

♻️ Duplicate comments (4)
src/Audio/Speaker/SpeakerVerifier.cs (1)

40-73: Thread safety concern already flagged.

The _enrolledSpeakers dictionary is not thread-safe for concurrent enrollment/verification operations.

src/Audio/AudioGen/AudioGenModel.cs (2)

205-224: Resource leak if model loading fails - already flagged.

FromFiles doesn't clean up already-loaded models if a subsequent model load fails.


476-508: Tokenization bug for digits - already flagged.

The tokenization computes c - 'a' + 10 for both letters and digits, producing negative values for digits.

src/Audio/Whisper/WhisperModel.cs (1)

207-227: Resource leak if decoder loading fails - already flagged.

FromFiles doesn't dispose the encoder if the decoder constructor throws.

🧹 Nitpick comments (26)
src/Interfaces/IMusicSourceSeparator.cs (3)

137-143: Consider documenting why double is used for volume multipliers.

The Remix method uses double for volume multipliers while the rest of the interface is generic over T. This is likely intentional (volume is always floating-point regardless of audio sample type), but adding a brief note in the documentation would clarify this design choice.


150-184: Consider making result class immutable or using required/init modifiers.

SourceSeparationResult<T> is a result container but has mutable public setters. For result objects, immutability is typically preferred to prevent accidental modification after creation.

Additionally, OriginalMix uses default! which suppresses the null warning but doesn't prevent runtime null issues if not properly initialized.

🔎 Suggested approach using init-only properties
 public class SourceSeparationResult<T>
 {
-    public IReadOnlyDictionary<string, Tensor<T>> Sources { get; set; } = new Dictionary<string, Tensor<T>>();
-    public Tensor<T> OriginalMix { get; set; } = default!;
-    public int SampleRate { get; set; }
-    public double Duration { get; set; }
-    public SeparationQuality<T>? Quality { get; set; }
+    public required IReadOnlyDictionary<string, Tensor<T>> Sources { get; init; }
+    public required Tensor<T> OriginalMix { get; init; }
+    public required int SampleRate { get; init; }
+    public required double Duration { get; init; }
+    public SeparationQuality<T>? Quality { get; init; }

190-206: Same immutability suggestion applies to SeparationQuality<T>.

For consistency with the recommended refactor on SourceSeparationResult<T>, consider using init setters here as well. Quality metrics are typically computed once and shouldn't change afterward.

🔎 Suggested approach
 public class SeparationQuality<T>
 {
-    public IReadOnlyDictionary<string, T> SDR { get; set; } = new Dictionary<string, T>();
-    public IReadOnlyDictionary<string, T> SIR { get; set; } = new Dictionary<string, T>();
-    public IReadOnlyDictionary<string, T> SAR { get; set; } = new Dictionary<string, T>();
+    public IReadOnlyDictionary<string, T> SDR { get; init; } = new Dictionary<string, T>();
+    public IReadOnlyDictionary<string, T> SIR { get; init; } = new Dictionary<string, T>();
+    public IReadOnlyDictionary<string, T> SAR { get; init; } = new Dictionary<string, T>();
 }
src/Interfaces/ISoundLocalizer.cs (3)

37-37: Consider adding a generic type constraint for numeric operations.

The interface and all related types use T without any constraint, but this is a numeric computation library. Properties like Distance (line 189) use T?, which behaves differently for value types vs reference types. For a sound localization API dealing with angles and distances, constraining T ensures type safety.

Suggested constraint
-public interface ISoundLocalizer<T>
+public interface ISoundLocalizer<T> where T : struct, INumber<T>

Apply similar constraints to the supporting classes (LocalizationResult<T>, SoundSource<T>, etc.).


147-168: Multiple types defined in a single interface file.

The PR objectives specify "one class per file and matching namespaces" as an architectural requirement. This file contains 1 interface, 8 classes, and 1 enum. Consider splitting into separate files:

  • ISoundLocalizer.cs (interface only)
  • LocalizationResult.cs, SoundSource.cs, DirectionEstimate.cs, etc.
  • Alternatively, group all DTOs into a SoundLocalization subfolder under Models/.

This is flagged based on the stated architectural requirements in the PR objectives.

Also applies to: 174-205, 211-227, 233-249, 255-276, 282-303, 309-330, 351-362


147-168: Result classes are mutable; consider using init-only setters.

LocalizationResult<T> and SoundTrackingResult<T> are result objects returned from methods. Using public setters allows consumers to mutate results after creation, which can lead to unexpected behavior. Using init setters or making these records would improve safety.

Example with init setters
 public class LocalizationResult<T>
 {
-    public IReadOnlyList<SoundSource<T>> Sources { get; set; } = Array.Empty<SoundSource<T>>();
+    public IReadOnlyList<SoundSource<T>> Sources { get; init; } = Array.Empty<SoundSource<T>>();
     
     public int NumSources => Sources.Count;
     
-    public SoundSource<T>? DominantSource { get; set; }
+    public SoundSource<T>? DominantSource { get; init; }
     
-    public double Duration { get; set; }
+    public double Duration { get; init; }
 }

Also applies to: 233-249

src/Interfaces/IChordRecognizer.cs (2)

178-182: Consider documenting the EndTime >= StartTime invariant.

The Duration property assumes EndTime >= StartTime. While the recognizer implementation should enforce this, adding a brief XML doc note about this assumption would help consumers understand the expected contract.

🔎 Optional documentation enhancement
     /// <summary>
     /// Gets the duration of this segment.
     /// </summary>
+    /// <remarks>
+    /// Assumes EndTime >= StartTime. Negative values indicate invalid segment data.
+    /// </remarks>
     public double Duration => EndTime - StartTime;

114-209: Consider splitting result types into separate files for consistency.

The PR objectives mention "one class per file" as an architectural requirement. This file contains the interface plus three result/DTO classes. While co-locating tightly coupled result types with their interface is a common pattern, you may want to split these for consistency with the project's conventions.

This is a low-priority suggestion given that the current organization is pragmatic and the types are cohesive.

src/Interfaces/IBeatTracker.cs (1)

220-243: Consider adding validation for time signature values.

The constructor accepts any int values, but a denominator of 0 or negative values would be musically invalid and could cause division-by-zero issues downstream.

🔎 Proposed validation
 public TimeSignature(int numerator, int denominator)
 {
+    if (numerator <= 0)
+        throw new ArgumentOutOfRangeException(nameof(numerator), "Numerator must be positive.");
+    if (denominator <= 0)
+        throw new ArgumentOutOfRangeException(nameof(denominator), "Denominator must be positive.");
     Numerator = numerator;
     Denominator = denominator;
 }
src/Audio/MusicAnalysis/KeyDetector.cs (1)

107-115: Vector-to-Tensor conversion could be optimized.

The element-by-element copy pattern appears in multiple files. Consider adding a utility method or constructor to Tensor<T> that accepts a Vector<T>.

🔎 Inline simplification (if Tensor supports span/array constructor)
 public KeyDetectionResult Detect(Vector<T> audio)
 {
-    var tensor = new Tensor<T>([audio.Length]);
-    for (int i = 0; i < audio.Length; i++)
-    {
-        tensor[i] = audio[i];
-    }
+    // If Tensor<T> supports this pattern:
+    var tensor = Tensor<T>.FromVector(audio);
     return Detect(tensor);
 }
src/Audio/MusicAnalysis/ChordRecognizer.cs (1)

143-165: Redundant normalization in template creation.

The template is already normalized when weights are assigned as 1.0 / intervals.Length (since all non-zero values sum to 1.0). The second normalization (lines 155-162) is unnecessary.

🔎 Proposed simplification
 private static double[] CreateTemplate(int root, int[] intervals)
 {
     var template = new double[12];
     double weight = 1.0 / intervals.Length;

     foreach (int interval in intervals)
     {
         int bin = (root + interval) % 12;
         template[bin] = weight;
     }

-    // Normalize
-    double sum = template.Sum();
-    if (sum > 0)
-    {
-        for (int i = 0; i < 12; i++)
-        {
-            template[i] /= sum;
-        }
-    }
-
     return template;
 }
src/Audio/MusicAnalysis/BeatTracker.cs (1)

120-125: Magic index for spectral flux is fragile.

The comment explains index 4 is spectral flux, but this relies on a specific feature ordering in SpectralFeatureExtractor. If the extractor changes, this code will silently break.

🔎 Consider using a named constant or feature lookup
+private const int SpectralFluxFeatureIndex = 4; // Corresponds to SpectralFeatureType.Flux ordering
+
 // Get spectral flux (column 4 in our SpectralFeatureExtractor)
 for (int f = 0; f < numFrames; f++)
 {
-    // Use spectral flux (index 4) as onset strength
-    onsetEnvelope[f] = Math.Max(0, NumOps.ToDouble(features[f, 4]));
+    onsetEnvelope[f] = Math.Max(0, NumOps.ToDouble(features[f, SpectralFluxFeatureIndex]));
 }
src/Audio/TextToSpeech/TtsModel.cs (2)

246-251: Unused pitch parameter.

The pitch parameter is accepted but never used in the synthesis logic. Consider removing it or adding a TODO comment if pitch shifting is planned for future implementation.


477-480: Cloned instance will be non-functional.

CreateNewInstance creates a TtsModel with null models, so IsReady will be false. If this is intentional for deep-clone workflows where models are loaded separately, consider adding a clarifying comment.

src/Interfaces/IAudioGenerator.cs (1)

215-246: Consider separating AudioGenerationOptions<T> to its own file.

Per project conventions (one class per file), AudioGenerationOptions<T> could be moved to a separate file. Additionally, the generic parameter <T> appears unused in this class—it has no T-typed members—so it could potentially be a non-generic AudioGenerationOptions.

src/Interfaces/ISpeakerVerifier.cs (1)

161-182: Consider using required modifier for mandatory properties.

SpeakerVerificationResult<T>.Score, Threshold, and Confidence are initialized with default! which suppresses null warnings but could lead to uninitialized value types behaving unexpectedly. If C# 11+ features are available, consider using required or ensuring these are always set during construction.

src/Audio/AudioNeuralNetworkBase.cs (1)

169-177: No-op forward pass when Layers is empty.

If Layers is empty (which is likely in ONNX mode), Forward returns the input unchanged. This is probably intentional, but consider adding a guard or documentation clarifying this behavior.

src/Audio/Speaker/SpeakerEmbeddingExtractor.cs (1)

318-338: Silent handling of mismatched embedding lengths.

CosineSimilarity uses Math.Min when vector lengths differ, which is defensive but may mask configuration issues. If embeddings should always match, consider adding validation or at least a debug assertion.

src/Interfaces/ITextToSpeech.cs (1)

183-218: Consider using record for immutable voice metadata.

VoiceInfo<T> is essentially a data transfer object. Using a record would provide immutability and value-based equality, which is often preferable for configuration/metadata types. However, this is optional since mutable DTOs work fine here.

src/Interfaces/IGenreClassifier.cs (1)

129-230: Consider consistency in numeric type usage.

GenreDistribution in GenreTrackingResult<T> uses double while other confidence/probability fields use T. This asymmetry may be intentional for distribution percentages, but consider documenting the rationale or using IReadOnlyDictionary<string, T> for consistency.

src/Audio/Classification/GenreClassifier.cs (1)

309-364: Consider extending tempo range for faster genres.

The tempo estimation limits autocorrelation search to 30-120 BPM (lines 336-337), which may miss faster genres like metal (140+ BPM). Consider extending the range:

-int minLag = (int)(0.5 * _options.SampleRate / frameSize); // 120 BPM
+int minLag = (int)(0.25 * _options.SampleRate / frameSize); // 240 BPM
src/Audio/Fingerprinting/AudioFingerprinterBase.cs (2)

79-88: Consider extracting the Vector-to-Tensor conversion as a reusable utility.

Both derived classes (SpectrogramFingerprinter and ChromaprintFingerprinter) override this method with nearly identical implementations. The conversion logic could be centralized in a helper or the base class could provide a protected VectorToTensor method.

🔎 Proposed refactor
+    /// <summary>
+    /// Converts a vector to a 1D tensor.
+    /// </summary>
+    protected Tensor<T> VectorToTensor(Vector<T> audio)
+    {
+        var tensor = new Tensor<T>([audio.Length]);
+        for (int i = 0; i < audio.Length; i++)
+        {
+            tensor[i] = audio[i];
+        }
+        return tensor;
+    }
+
     public virtual AudioFingerprint<T> Fingerprint(Vector<T> audio)
     {
-        // Convert vector to tensor and delegate to main implementation
-        var tensor = new Tensor<T>([audio.Length]);
-        for (int i = 0; i < audio.Length; i++)
-        {
-            tensor[i] = audio[i];
-        }
-        return Fingerprint(tensor);
+        return Fingerprint(VectorToTensor(audio));
     }

122-134: Consider using BitOperations.PopCount for hardware-accelerated bit counting.

The current loop-based implementation is correct, but System.Numerics.BitOperations.PopCount provides O(1) hardware-accelerated population count on modern CPUs.

🔎 Proposed refactor
+using System.Numerics;
+
 protected int ComputeHammingDistance(uint fp1, uint fp2)
 {
-    uint xor = fp1 ^ fp2;
-    int distance = 0;
-
-    while (xor != 0)
-    {
-        distance += (int)(xor & 1);
-        xor >>= 1;
-    }
-
-    return distance;
+    return BitOperations.PopCount(fp1 ^ fp2);
 }
src/Audio/Fingerprinting/ChromaprintFingerprinter.cs (2)

294-315: Approximate matching generates many hash variants—consider documenting performance characteristics.

With MaxBitDifference=2, GetHashVariants produces 529 variants per query hash (1 + 32 + 496). For long audio with many hashes, this could impact performance. Consider documenting this trade-off or providing an option to disable approximate matching when exact matching suffices.


411-420: BitCount duplicates the bit-counting logic from the base class.

Consider reusing ComputeHammingDistance with an identity comparison (i.e., ComputeHammingDistance(n, 0) won't work directly) or using System.Numerics.BitOperations.PopCount(n) for consistency and hardware acceleration.

🔎 Proposed refactor
+using System.Numerics;
+
-private static int BitCount(uint n)
-{
-    int count = 0;
-    while (n != 0)
-    {
-        count++;
-        n &= (n - 1);
-    }
-    return count;
-}
+private static int BitCount(uint n) => BitOperations.PopCount(n);
src/Audio/Fingerprinting/SpectrogramFingerprinter.cs (1)

324-329: Consider using a readonly record struct for SpectralPeak.

Since SpectralPeak is a simple data container, a readonly record struct would provide value semantics, immutability, and better memory locality when stored in lists.

🔎 Proposed refactor
-/// <summary>
-/// Represents a spectral peak.
-/// </summary>
-internal class SpectralPeak
-{
-    public int Frame { get; set; }
-    public int Bin { get; set; }
-    public double Magnitude { get; set; }
-}
+/// <summary>
+/// Represents a spectral peak.
+/// </summary>
+internal readonly record struct SpectralPeak(int Frame, int Bin, double Magnitude);

Update usage to use constructor syntax:

-peaks.Add(new SpectralPeak
-{
-    Frame = f,
-    Bin = b,
-    Magnitude = value
-});
+peaks.Add(new SpectralPeak(f, b, value));
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5aa7391 and 6c353af.

📒 Files selected for processing (34)
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/AudioNeuralNetworkBase.cs
  • src/Audio/Classification/AudioClassifierBase.cs
  • src/Audio/Classification/AudioEventDetector.cs
  • src/Audio/Classification/GenreClassifier.cs
  • src/Audio/Classification/SceneClassifier.cs
  • src/Audio/Fingerprinting/AudioFingerprinterBase.cs
  • src/Audio/Fingerprinting/ChromaprintFingerprinter.cs
  • src/Audio/Fingerprinting/SpectrogramFingerprinter.cs
  • src/Audio/MusicAnalysis/BeatTracker.cs
  • src/Audio/MusicAnalysis/ChordRecognizer.cs
  • src/Audio/MusicAnalysis/KeyDetector.cs
  • src/Audio/MusicAnalysis/MusicAnalysisBase.cs
  • src/Audio/Speaker/SpeakerDiarizer.cs
  • src/Audio/Speaker/SpeakerEmbeddingExtractor.cs
  • src/Audio/Speaker/SpeakerRecognitionBase.cs
  • src/Audio/Speaker/SpeakerVerifier.cs
  • src/Audio/TextToSpeech/TtsModel.cs
  • src/Audio/Whisper/WhisperModel.cs
  • src/Interfaces/IAudioEventDetector.cs
  • src/Interfaces/IAudioGenerator.cs
  • src/Interfaces/IBeatTracker.cs
  • src/Interfaces/IChordRecognizer.cs
  • src/Interfaces/IGenreClassifier.cs
  • src/Interfaces/IKeyDetector.cs
  • src/Interfaces/IMusicSourceSeparator.cs
  • src/Interfaces/ISceneClassifier.cs
  • src/Interfaces/ISoundLocalizer.cs
  • src/Interfaces/ISpeakerDiarizer.cs
  • src/Interfaces/ISpeakerEmbeddingExtractor.cs
  • src/Interfaces/ISpeakerVerifier.cs
  • src/Interfaces/ISpeechRecognizer.cs
  • src/Interfaces/ITextToSpeech.cs
  • tests/AiDotNet.Tests/Audio/Classification/ClassificationTests.cs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-18T08:49:25.295Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningMask.cs:1-102
Timestamp: 2025-12-18T08:49:25.295Z
Learning: In the AiDotNet repository, the project-level global using includes AiDotNet.Tensors.LinearAlgebra via AiDotNet.csproj. Therefore, Vector<T>, Matrix<T>, and Tensor<T> are available without per-file using directives. Do not flag missing using directives for these types in any C# files within this project. Apply this guideline broadly to all C# files (not just a single file) to avoid false positives. If a file uses a type from a different namespace not covered by the global using, flag as usual.

Applied to files:

  • tests/AiDotNet.Tests/Audio/Classification/ClassificationTests.cs
  • src/Audio/Classification/AudioClassifierBase.cs
  • src/Interfaces/ISpeakerEmbeddingExtractor.cs
  • src/Interfaces/ISoundLocalizer.cs
  • src/Audio/Fingerprinting/SpectrogramFingerprinter.cs
  • src/Audio/Speaker/SpeakerVerifier.cs
  • src/Audio/MusicAnalysis/ChordRecognizer.cs
  • src/Audio/Speaker/SpeakerRecognitionBase.cs
  • src/Audio/Classification/SceneClassifier.cs
  • src/Interfaces/IBeatTracker.cs
  • src/Interfaces/IAudioGenerator.cs
  • src/Audio/AudioNeuralNetworkBase.cs
  • src/Interfaces/IAudioEventDetector.cs
  • src/Audio/Speaker/SpeakerDiarizer.cs
  • src/Audio/MusicAnalysis/BeatTracker.cs
  • src/Interfaces/IMusicSourceSeparator.cs
  • src/Interfaces/IGenreClassifier.cs
  • src/Audio/MusicAnalysis/MusicAnalysisBase.cs
  • src/Interfaces/IChordRecognizer.cs
  • src/Audio/MusicAnalysis/KeyDetector.cs
  • src/Audio/Classification/AudioEventDetector.cs
  • src/Audio/Speaker/SpeakerEmbeddingExtractor.cs
  • src/Audio/Fingerprinting/ChromaprintFingerprinter.cs
  • src/Audio/Fingerprinting/AudioFingerprinterBase.cs
  • src/Interfaces/ISceneClassifier.cs
  • src/Audio/Classification/GenreClassifier.cs
  • src/Audio/TextToSpeech/TtsModel.cs
  • src/Interfaces/ITextToSpeech.cs
  • src/Interfaces/ISpeechRecognizer.cs
  • src/Interfaces/IKeyDetector.cs
  • src/Interfaces/ISpeakerDiarizer.cs
  • src/Interfaces/ISpeakerVerifier.cs
  • src/Audio/Whisper/WhisperModel.cs
  • src/Audio/AudioGen/AudioGenModel.cs
📚 Learning: 2025-12-18T08:49:53.103Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningStrategy.cs:1-4
Timestamp: 2025-12-18T08:49:53.103Z
Learning: In this repository, global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*) and common system types. When reviewing C# files, assume these global usings are in effect; avoid adding duplicate using statements for these namespaces and for types like Vector<T>, Matrix<T>, Tensor<T>, etc. If a type is not found, verify the global usings or consider adding a file-scoped using if needed. Prefer relying on global usings to reduce boilerplate.

Applied to files:

  • tests/AiDotNet.Tests/Audio/Classification/ClassificationTests.cs
  • src/Audio/Classification/AudioClassifierBase.cs
  • src/Interfaces/ISpeakerEmbeddingExtractor.cs
  • src/Interfaces/ISoundLocalizer.cs
  • src/Audio/Fingerprinting/SpectrogramFingerprinter.cs
  • src/Audio/Speaker/SpeakerVerifier.cs
  • src/Audio/MusicAnalysis/ChordRecognizer.cs
  • src/Audio/Speaker/SpeakerRecognitionBase.cs
  • src/Audio/Classification/SceneClassifier.cs
  • src/Interfaces/IBeatTracker.cs
  • src/Interfaces/IAudioGenerator.cs
  • src/Audio/AudioNeuralNetworkBase.cs
  • src/Interfaces/IAudioEventDetector.cs
  • src/Audio/Speaker/SpeakerDiarizer.cs
  • src/Audio/MusicAnalysis/BeatTracker.cs
  • src/Interfaces/IMusicSourceSeparator.cs
  • src/Interfaces/IGenreClassifier.cs
  • src/Audio/MusicAnalysis/MusicAnalysisBase.cs
  • src/Interfaces/IChordRecognizer.cs
  • src/Audio/MusicAnalysis/KeyDetector.cs
  • src/Audio/Classification/AudioEventDetector.cs
  • src/Audio/Speaker/SpeakerEmbeddingExtractor.cs
  • src/Audio/Fingerprinting/ChromaprintFingerprinter.cs
  • src/Audio/Fingerprinting/AudioFingerprinterBase.cs
  • src/Interfaces/ISceneClassifier.cs
  • src/Audio/Classification/GenreClassifier.cs
  • src/Audio/TextToSpeech/TtsModel.cs
  • src/Interfaces/ITextToSpeech.cs
  • src/Interfaces/ISpeechRecognizer.cs
  • src/Interfaces/IKeyDetector.cs
  • src/Interfaces/ISpeakerDiarizer.cs
  • src/Interfaces/ISpeakerVerifier.cs
  • src/Audio/Whisper/WhisperModel.cs
  • src/Audio/AudioGen/AudioGenModel.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build (Windows)
  • GitHub Check: CodeQL Analysis
🔇 Additional comments (118)
src/Interfaces/IMusicSourceSeparator.cs (2)

1-44: Well-documented interface with beginner-friendly explanations.

The XML documentation is comprehensive, covering use cases, common separations, and popular models. This aligns well with the project's requirement for beginner-friendly documentation.


46-69: Interface properties are well-designed.

Read-only properties with appropriate types. Using IReadOnlyList<string> for SupportedSources correctly signals immutability to consumers.

src/Interfaces/ISoundLocalizer.cs (3)

1-41: Well-documented interface with beginner-friendly explanations.

The XML documentation is comprehensive, covering both technical details (ITD, ILD, azimuth/elevation) and practical use cases. This aligns well with the project's requirement for "beginner-friendly defaults with validation and documentation."


69-141: Interface method signatures are well-designed.

The API surface covers essential sound localization operations: synchronous/async localization, tracking, DOA estimation, spatial spectrum computation, and beamforming. Default parameter values (e.g., windowDuration = 0.1, azimuthResolution = 5.0) provide sensible defaults for beginners.


335-345: ArrayType enum is clear and covers common geometries.

The enum covers Linear, Circular, Spherical, and Custom array types, which aligns with standard microphone array configurations used in sound localization algorithms (GCC-PHAT, MUSIC, SRP-PHAT mentioned in the AI summary).

src/Interfaces/IChordRecognizer.cs (3)

1-112: Well-designed interface with excellent documentation.

The interface is cohesive, properly scoped, and includes both sync and async variants. The XML documentation with beginner-friendly explanations aligns well with the project's architectural requirements for documentation.


114-140: Result class design is acceptable.

The mutable DTO pattern with sensible defaults (empty arrays/dictionaries) is practical for result objects, especially given the multi-target framework support (net8.0 and net471).


184-209: Statistics class is well-structured.

Clean design with clear property semantics. Consistent with the other DTO classes in this file.

src/Interfaces/IKeyDetector.cs (2)

132-210: LGTM! Data models are well-structured.

The KeyDetectionResult<T>, MusicalMode, and KeyHypothesis<T> types follow project conventions:

  • Strict property initialization (string.Empty, default!, Array.Empty)
  • Clear XML documentation
  • Appropriate use of MusicalMode enum for type safety

The structure is intuitive and the defaults are sensible.


212-291: LGTM! Time-tracking models are well-designed.

The KeyTrackingResult<T>, KeySegment<T>, and ModulationPoint<T> types properly model temporal aspects of key detection:

  • Time properties use double (appropriate for seconds)
  • Proper initialization with Array.Empty for collections
  • Clear semantics for segments and modulation points
  • Good documentation explaining modulation detection

The design supports both simple key detection and advanced time-series key tracking.

src/Interfaces/IBeatTracker.cs (2)

1-119: Well-designed interface with comprehensive beat tracking API.

The interface provides a complete contract for beat tracking with good separation between tempo estimation, beat detection, and downbeat analysis. The async variant with CancellationToken support is appreciated.


125-156: I was unable to verify the review comment claims due to repository access issues. Manual verification is needed to confirm:

  1. Whether BeatTracker<T> class implements IBeatTracker<T>
  2. Whether a non-generic BeatTrackingResult class exists in BeatTracker.cs with different structure than the generic BeatTrackingResult<T>
  3. The actual return types and property types in both the interface and implementation
src/Audio/MusicAnalysis/KeyDetector.cs (4)

37-80: Well-structured key detector with standard Krumhansl-Kessler profiles.

The constructor properly initializes the chroma extractor and builds rotated profiles for all 12 keys. The beginner documentation is excellent.


150-174: LGTM!

The chroma averaging and normalization logic is correct. Handles the zero-sum edge case appropriately.


229-248: LGTM!

Standard Pearson correlation with proper handling of degenerate cases (zero variance).


254-323: LGTM!

Well-documented result and options classes with sensible defaults.

src/Audio/MusicAnalysis/ChordRecognizer.cs (4)

34-64: Well-designed chord recognizer with comprehensive chord vocabulary.

The constructor properly initializes templates for 9 chord qualities across all 12 roots (108 templates + "N"). Good use of the base class infrastructure.


167-220: LGTM with minor note.

The frame classification logic is correct. The "N" template iteration could be skipped (it always scores 0), but this doesn't affect correctness.


241-302: LGTM!

Segment merging correctly handles consecutive frames, duration filtering, and confidence averaging. The final segment is properly handled.


305-370: LGTM!

Clean data structures with computed Duration property and sensible option defaults.

src/Audio/MusicAnalysis/MusicAnalysisBase.cs (5)

28-66: LGTM!

Clean base class design with protected NumOps and sensible defaults for audio processing parameters.


124-161: LGTM!

Autocorrelation-based tempogram computation with proper generic arithmetic using NumOps.


198-237: LGTM!

Peak detection with proper threshold handling and minimum distance enforcement. The replacement logic correctly selects the highest peak within a cluster.


245-261: LGTM!

Standard frame-time conversion utilities with optional hop length override.


80-109: Verify spectral flux feature index consistency.

This method reads flux from features[i, 0], but BeatTracker.ComputeOnsetEnvelopeInternal reads from features[f, 4]. Confirm whether this discrepancy is intentional (different feature configurations) or a bug by examining the SpectralFeatureExtractor implementation and feature column ordering.

src/Audio/MusicAnalysis/BeatTracker.cs (4)

36-70: LGTM!

Constructor properly initializes options and spectral extractor. Good documentation for beginners.


146-180: LGTM!

Autocorrelation-based tempo estimation correctly finds the best lag and converts to BPM within the configured bounds.


182-265: Well-implemented dynamic programming beat tracker.

The forward pass with Gaussian penalty for tempo deviation and backtracking is a solid approach. The lookback window of 2× beat period provides flexibility for tempo variations.


291-356: LGTM!

Result and options classes are well-documented with sensible defaults. Note: The type mismatch with IBeatTracker<T>'s BeatTrackingResult<T> was already flagged in the interface file review.

src/Audio/TextToSpeech/TtsModel.cs (1)

589-601: LGTM!

The dispose pattern correctly handles _acousticModel locally while relying on the base class to dispose the vocoder (stored in OnnxModel). The comment clarifies this design.

src/Interfaces/ISpeakerEmbeddingExtractor.cs (1)

38-153: LGTM!

Well-designed interface with comprehensive XML documentation. The API surface covers the essential embedding operations (extraction, similarity, aggregation, normalization) and follows the established patterns in the codebase.

src/Audio/Speaker/SpeakerRecognitionBase.cs (1)

76-102: LGTM!

The cosine similarity implementation correctly handles the edge case where the norm product is zero, preventing division by zero.

src/Audio/Classification/AudioClassifierBase.cs (1)

60-95: LGTM!

Numerically stable softmax implementation with max subtraction to prevent overflow. The mapping to class labels is clean and correct.

src/Interfaces/IAudioGenerator.cs (1)

38-209: LGTM!

Comprehensive interface design covering text-to-audio, text-to-music, continuation, and inpainting scenarios. The capability flags (SupportsTextToAudio, etc.) allow implementations to indicate feature availability, with proper NotSupportedException documentation for unsupported methods.

src/Interfaces/ISpeakerVerifier.cs (1)

38-155: LGTM!

Well-designed interface covering enrollment, verification, and profile management workflows. The documentation clearly explains FAR/FRR trade-offs which is valuable for users configuring threshold values.

src/Audio/AudioNeuralNetworkBase.cs (2)

188-197: LGTM!

Dispose correctly cleans up all three ONNX model holders and delegates to the base class.


32-86: LGTM!

Good design establishing audio-specific properties (SampleRate, NumMels) with sensible defaults, and clear ONNX mode detection based on presence of any ONNX model.

src/Audio/Speaker/SpeakerEmbeddingExtractor.cs (2)

39-39: Consider implementing ISpeakerEmbeddingExtractor<T>.

This class provides speaker embedding functionality but doesn't implement the ISpeakerEmbeddingExtractor<T> interface defined in this PR. If this is intentional (to keep it simpler than IFullModel<T, ...>), consider documenting why. Otherwise, implementing the interface would ensure API consistency with ISpeakerVerifier.EmbeddingExtractor.


361-397: LGTM!

Well-structured options class with sensible defaults for speaker embedding extraction. The nullable ModelPath enables graceful fallback to MFCC statistics mode.

src/Interfaces/ITextToSpeech.cs (2)

1-181: Well-designed TTS interface with comprehensive API surface.

The interface provides a cohesive API for text-to-speech with good coverage of advanced features (voice cloning, emotion control, streaming). Documentation is thorough and beginner-friendly.


220-256: LGTM!

The VoiceGender enum and IStreamingSynthesisSession<T> interface are well-designed. The streaming session properly extends IDisposable for resource cleanup.

src/Audio/Speaker/SpeakerVerifier.cs (3)

75-108: LGTM!

Enrollment methods have proper validation and correctly recompute the centroid when embeddings change.


120-186: LGTM!

Verification and identification logic is correct. The Verify method gracefully handles unenrolled speakers with an informative error result rather than throwing.


252-350: LGTM!

Supporting data types are well-structured with appropriate defaults. The threshold values (0.7 for verification, 0.6 for identification) are reasonable for cosine similarity-based speaker recognition.

src/Audio/Speaker/SpeakerDiarizer.cs (6)

39-79: LGTM!

Constructor properly initializes the embedding extractor with options. The class correctly implements IDisposable and disposes the extractor.


81-123: LGTM!

The diarization pipeline is well-structured: segment audio → extract embeddings → cluster → create speaker turns. The Vector<T> overload correctly converts to Tensor<T>.


156-268: LGTM!

The agglomerative clustering implementation using average linkage is correct. The algorithm properly merges clusters above the threshold and relabels to consecutive integers.


345-395: LGTM!

Turn creation logic correctly merges consecutive same-speaker segments and filters by minimum duration. The final turn is properly handled after the loop.


397-426: LGTM!

Disposal pattern is correctly implemented with proper cleanup of the embedding extractor.


428-542: LGTM!

Supporting data types are well-designed. SpeakingTimePerSpeaker as a computed property is a nice touch for convenience. Default options are reasonable for typical diarization scenarios.

src/Interfaces/ISpeechRecognizer.cs (2)

1-150: Well-designed speech recognition interface.

The interface provides a comprehensive API for ASR with language detection, streaming support, and word-level timestamps. Documentation is thorough and beginner-friendly.


152-234: LGTM!

Data types are well-structured. The streaming session interface properly extends IDisposable and provides clear semantics for incremental transcription.

src/Audio/AudioGen/AudioGenModel.cs (5)

43-112: LGTM!

Class structure is well-organized with clear property definitions and proper initialization. The private constructor ensures models are created through factory methods.


126-203: LGTM!

CreateAsync properly handles resource cleanup on failure. The try-catch block ensures all loaded models are disposed if any subsequent load fails.


226-332: LGTM!

Audio generation pipeline is well-structured. The classifier-free guidance implementation follows the standard formula. Unsupported features appropriately throw NotSupportedException.


661-750: LGTM!

Sampling implementation with temperature, top-k, and top-p filtering is correct. Softmax handles numerical stability by subtracting the max value.


794-815: LGTM!

Disposal correctly cleans up text encoder and language model. The audio decoder is stored as OnnxModel in the base class and disposed there.

src/Audio/Whisper/WhisperModel.cs (5)

48-112: LGTM!

Class structure is well-organized with proper initialization of mel spectrogram preprocessor and tokenizer. Whisper-specific parameters (25ms window, 10ms hop) are correctly configured.


144-205: LGTM!

CreateAsync properly handles resource cleanup on failure with try-catch ensuring both encoder and decoder are disposed if an exception occurs.


592-672: LGTM!

Token decoding with greedy strategy is correctly implemented. The autoregressive loop properly handles the end-of-text token to terminate generation.


283-377: LGTM!

Language detection correctly extracts per-language probabilities from decoder logits. The softmax implementation is numerically stable with proper handling of the generic type.


697-716: LGTM!

Disposal pattern is correctly implemented, deferring encoder/decoder cleanup to the base class.

tests/AiDotNet.Tests/Audio/Classification/ClassificationTests.cs (6)

1-10: LGTM!

Imports and test class setup look correct. The namespace follows project conventions.


11-96: LGTM!

Well-structured synthetic audio generators with deterministic seeding for reproducible tests. The signal generation covers diverse audio characteristics (harmonic content, rhythm patterns, formants) suitable for classifier testing.


100-168: LGTM!

Comprehensive test coverage for GenreClassifier including result validation, probability distribution checks, ordering verification, and feature extraction. The tolerance of 0.01 for probability sum is appropriate.


189-272: LGTM!

AudioEventDetector tests appropriately validate event detection, timestamp relationships, confidence bounds, and top-K result limits. The assertion topK.Count <= 5 correctly handles the case where fewer events than K are detected.


276-374: LGTM!

SceneClassifier tests follow the same thorough patterns as GenreClassifier tests, with proper validation of classification results, probability distributions, category mapping, and feature extraction.


378-445: LGTM!

Disposal tests correctly verify that Dispose() doesn't throw and that subsequent method calls on disposed instances throw ObjectDisposedException. This ensures proper resource cleanup semantics.

src/Audio/Classification/SceneClassifier.cs (9)

1-54: LGTM!

Well-structured class with proper field declarations, beginner-friendly XML documentation, and protected NumOps for derived classes. The use of INumericOperations<T> follows project conventions.


54-98: LGTM!

Comprehensive scene taxonomy with sensible category groupings (indoor, outdoor_urban, outdoor_nature, transportation). The GetCategory fallback to "unknown" handles edge cases safely.


109-158: LGTM!

Constructor properly initializes feature extractors with configurable options and conditionally loads the ONNX model. The CreateAsync factory provides a convenient async initialization path with model download support.


165-221: LGTM!

The Classify method correctly handles both model-based and rule-based inference paths, with proper disposal checking and result construction. The manual loop for finding the best probability is appropriate for clarity.


316-409: LGTM!

Spectral feature computations are mathematically sound with proper zero-division guards. The spectral flatness uses an appropriate epsilon floor (1e-10) to avoid log(0).


411-482: LGTM!

Temporal and band energy feature extraction methods are correctly implemented with proper array bounds handling.


484-528: Verify feature normalization matches expected model input.

The hardcoded scaling factors (centroid/100, bandwidth/100, rms10, zcr100) must match the normalization used during model training. Document or make these configurable if the model expects different scaling.


530-653: LGTM!

Rule-based classification provides a reasonable fallback when no ONNX model is loaded. The Gaussian scoring function and disposal pattern are correctly implemented.


656-729: LGTM!

Options class has sensible audio processing defaults. The use of required properties for mandatory data in SceneFeatures and SceneClassificationResult ensures proper initialization.

src/Interfaces/IGenreClassifier.cs (2)

1-42: LGTM!

Excellent beginner-friendly documentation explaining genre classification concepts, use cases, and challenges. The interface properly extends IFullModel<T, Tensor<T>, Tensor<T>>.


43-127: LGTM!

Well-designed interface with comprehensive capabilities: sample rate specification, multi-label support, ONNX mode detection, synchronous/async classification, probability retrieval, top-K predictions, time-tracking, and feature extraction.

src/Audio/Classification/AudioEventDetector.cs (8)

1-54: LGTM!

Proper class structure with protected NumOps field and appropriate field declarations. XML documentation is comprehensive and beginner-friendly.


56-111: LGTM!

CommonEventLabels provides a well-organized taxonomy of audio events covering human sounds, animals, music, environmental, and household categories. Constructor properly initializes extractors with configurable options.


141-187: LGTM!

Windowed detection logic correctly handles overlap and merges events by label. The approach of processing sliding windows and filtering by threshold follows standard audio event detection patterns.


244-272: LGTM!

Window splitting correctly handles the edge case where the audio is shorter than a single window, ensuring at least one window is processed.


274-322: LGTM!

Classification methods correctly handle both ONNX-based inference (with sigmoid for multi-label) and rule-based fallback. Input tensor reshaping for the model is appropriate.


408-488: LGTM!

Event scoring heuristics provide reasonable signal-based detection for common event types. The baseline score of 0.05 ensures no event is completely ruled out in rule-based mode.


490-554: LGTM!

Event merging algorithm correctly groups by label, handles overlapping events with sensible tolerance (0.1s), and takes maximum confidence during merge. Disposal pattern is properly implemented.


557-620: LGTM!

Options class has appropriate defaults for audio event detection (16kHz sample rate, 1s window). The AudioEvent class uses required for Label and provides a useful ToString override.

src/Interfaces/IAudioEventDetector.cs (3)

1-44: LGTM!

Thorough documentation distinguishing event detection from classification, with practical use cases and challenge descriptions. The interface properly extends the base model interface.


45-147: LGTM!

Comprehensive interface design with threshold overloads, async support, type-specific detection, frame-level probabilities, and streaming session support. The dual Detect overloads (default vs custom threshold) provide good flexibility.


149-277: LGTM!

Well-designed data model supporting batch results with statistics, individual events with peak time tracking, and a streaming session interface with event-driven notifications. The GetEventsByType convenience method is helpful.

src/Interfaces/ISceneClassifier.cs (4)

1-41: LGTM!

Excellent documentation clearly differentiating scene classification from event detection, with practical examples and use cases including accessibility applications.


42-120: LGTM!

Interface provides comprehensive scene classification capabilities including minimum duration requirements, scene tracking, and acoustic feature extraction. The TrackSceneChanges method is particularly useful for analyzing recordings with location changes.


122-216: LGTM!

Rich result types with scene categories, acoustic characteristics (reverberation, crowd density, traffic/nature indicators), and sorted predictions. The AcousticCharacteristics<T> type provides valuable environmental context.


218-302: LGTM!

Scene tracking types properly model temporal scene information with segments, transitions, and distribution statistics. The SceneTransition<T> type captures both the timing and confidence of detected scene changes.

src/Audio/Classification/GenreClassifier.cs (6)

1-97: LGTM!

Well-structured implementation following the same patterns as SceneClassifier. The constructor properly initializes MFCC and spectral feature extractors with configurable options.


99-197: LGTM!

CreateAsync factory and Classify/ClassifyAsync/ClassifyBatch methods follow consistent patterns with other classifiers. The batch classification correctly leverages the single-item classifier.


199-259: LGTM!

Feature extraction correctly computes MFCC statistics and extracts spectral features. The fallback to NumOps.Zero for missing rolloff data is appropriate.


366-442: LGTM!

Model-based classification correctly flattens features with normalization and applies numerically stable softmax. The rule-based fallback provides reasonable heuristic classification when no model is available.


444-556: LGTM!

Genre-specific scoring heuristics are well-tuned for typical characteristics of each genre (tempo, spectral content, energy). The Gaussian scoring function and disposal pattern are correctly implemented.


559-638: LGTM!

Options and result classes follow the same patterns as other classifiers with sensible defaults and proper use of required properties for mandatory data.

src/Audio/Fingerprinting/AudioFingerprinterBase.cs (4)

1-65: Well-structured base class with clean abstractions.

The class establishes a solid foundation for fingerprinting implementations with appropriate use of NumOps from MathHelper, consistent property initialization patterns, and clear separation of abstract/virtual members. Documentation is thorough and beginner-friendly.


193-214: Alignment overlap logic is correct.

The offset-based overlap calculation properly handles both positive and negative offsets, ensuring array accesses remain within bounds.


222-243: Hash computation is clean and correct.

Proper binarization with threshold comparison and 32-bit limitation per hash value.


250-263: Frame/time conversion utilities are straightforward and correct.

src/Audio/Fingerprinting/ChromaprintFingerprinter.cs (7)

39-53: Clean constructor with proper delegation to base class and options initialization.


58-77: Dual fingerprint paths are intentional for type-specific chroma extraction.

The overridden Fingerprint(Vector<T>) method uses a separate code path (ComputeFingerprintFromMatrix) to work with Matrix<T> output from the chroma extractor, which is appropriate if ChromaExtractor returns different types for different inputs.


162-188: Gray code computation correctly implements differential encoding with proper bit limiting.

The dual-comparison approach (horizontal and vertical differences) and the 32-bit limit are appropriate for the Chromaprint algorithm.


204-240: Hash similarity with cross-correlation is correctly implemented with proper bounds checking.


348-372: Hash variant generation is correct and clearly implemented.


374-409: Segment detection logic is sound with reasonable gap tolerance.

The hardcoded gap threshold of 3 at line 387 works well for typical use cases. Consider making it configurable via ChromaprintOptions if users need to tune matching sensitivity.


426-457: Configuration options are well-documented with sensible defaults.

src/Audio/Fingerprinting/SpectrogramFingerprinter.cs (7)

39-50: Constructor properly initializes STFT with configurable parameters.


55-81: Fingerprinting pipeline follows the Shazam methodology correctly.

The STFT → magnitude → peaks → hash pipeline is well-structured.


83-102: Magnitude computation is correct and efficiently converts to double for peak detection.


219-227: Hash encoding is correctly implemented with proper bit packing.

The 10+10+12 bit layout for (freq1, freq2, timeDelta) is clearly documented and clamped appropriately.


232-248: Jaccard similarity is appropriate for peak-pair hash sets.

Unlike the bit-level similarity in ChromaprintFingerprinter, the set-based Jaccard similarity here correctly reflects the Shazam-style matching where each hash represents a unique peak pair.


253-318: Match detection with offset histogram is well-implemented.

The algorithm correctly builds position indices, aggregates offsets, and calculates match regions with proper time boundary handling.


334-380: Configuration options provide sensible defaults for Shazam-style fingerprinting.

The target zone range (1-64 frames) and peak detection parameters are appropriate for the algorithm. The PeaksPerSecond naming issue was noted above.

Comment thread src/Audio/Classification/AudioClassifierBase.cs
Comment thread src/Audio/Classification/SceneClassifier.cs Outdated
Comment thread src/Audio/Fingerprinting/SpectrogramFingerprinter.cs
Comment thread src/Audio/Fingerprinting/SpectrogramFingerprinter.cs
Comment thread src/Audio/Speaker/SpeakerEmbeddingExtractor.cs Outdated
Comment thread src/Audio/Speaker/SpeakerRecognitionBase.cs
Comment thread src/Audio/Speaker/SpeakerVerifier.cs Outdated
Comment thread src/Audio/TextToSpeech/TtsModel.cs Outdated
Comment thread src/Audio/Whisper/WhisperModel.cs
Extract all nested classes, enums, and result types from multi-class files
into their own individual files following C# best practices (one class per file):

- Whisper: WhisperModelSize, WhisperResult, WhisperWord, WhisperSegment
- Speaker: SpeakerEmbedding, SpeakerEmbeddingOptions, SpeakerTurn,
  DiarizationResult, SpeakerDiarizerOptions, VerificationResult,
  IdentificationResult, SpeakerMatch, SpeakerVerifierOptions
- Classification: GenreClassifierOptions, GenreFeatures,
  GenreClassificationResult, AudioEventDetectorOptions, AudioEvent,
  SceneClassifierOptions, SceneFeatures, SceneClassificationResult
- MusicAnalysis: BeatTrackingResult, BeatTrackerOptions, ChordSegment,
  ChordRecognizerOptions, KeyDetectionResult, KeyMode, KeyDetectorOptions
- Fingerprinting: ChromaprintOptions, SpectrogramFingerprintOptions
- Features: ChromaOptions, MfccOptions, SpectralFeatureType,
  SpectralFeatureOptions, WindowType
- TextToSpeech: TtsResult, TtsModelType, VocoderType
- AudioGen: AudioGenModelSize, AudioGenResult
- SourceSeparation: SourceSeparationOptions, SeparationResult
- Localization: SoundLocalizerOptions, LocalizationAlgorithm, LocalizationResult

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ooples

ooples commented Dec 29, 2025

Copy link
Copy Markdown
Owner Author

Current Status Update

Completed Work

  • Phase 1 (Interfaces): All 14+ audio AI interfaces created in src/Interfaces/

    • ISpeechRecognizer, ITextToSpeech, IAudioGenerator, ISpeakerEmbeddingExtractor, ISpeakerVerifier, ISpeakerDiarizer, IBeatTracker, IChordRecognizer, IKeyDetector, IGenreClassifier, IAudioEventDetector, ISceneClassifier, IMusicSourceSeparator, ISoundLocalizer
  • Phase 2 (Base Classes): All 5 audio base classes created

    • AudioNeuralNetworkBase extends NeuralNetworkBase
    • SpeakerRecognitionBase, MusicAnalysisBase, AudioClassifierBase, AudioFingerprinterBase
  • Class Extraction: All multi-class files separated into individual class files (C# best practice)

  • Build Status: 0 warnings, 0 errors on net8.0

Critical Issue Identified

The audio AI models are ONNX-only and cannot be trained from scratch.

Current WhisperModel, TtsModel, AudioGenModel implementations:

protected override void InitializeLayers()
{
    // ONNX-only model - no native layers to initialize  // <- WRONG!
}

public override void Train(...)
{
    throw new NotImplementedException("Native training ... not yet implemented.");  // <- WRONG!
}

This defeats the purpose of integrating with AiDotNet. Users should be able to:

  1. Define custom audio model architectures
  2. Add custom layers (transformers, attention, convolutions)
  3. Train from scratch with their own data
  4. Fine-tune models
  5. Use optimizers and loss functions like any other AiDotNet model

Remaining Work (Phase 3)

The audio models need trainable architectures:

  1. WhisperModel: Add encoder-decoder transformer layers
  2. TtsModel: Add acoustic model and vocoder layers
  3. AudioGenModel: Add text encoder and audio decoder layers
  4. Ensure all models work WITHOUT pre-trained ONNX weights
  5. Implement proper Train() methods using the neural network infrastructure

ooples and others added 3 commits December 29, 2025 14:30
…audio models

- Create LanguageModelBackbone enum for type-safe model selection
- Add LanguageModelTokenizerFactory for backbone-appropriate tokenizers
- Update Blip2NeuralNetwork, FlamingoNeuralNetwork, LLaVANeuralNetwork:
  - ONNX mode: Require tokenizer (throw with helpful message)
  - Native mode: Use factory to create appropriate default tokenizer
- Update IBlip2Model, IFlamingoModel, ILLaVAModel to use enum
- Apply pattern to AudioGenModel as example:
  - Tokenizer now required for ONNX mode (T5-based)
  - Replace placeholder tokenization with proper ITokenizer usage

The golden standard pattern:
- ONNX mode: Tokenizer is REQUIRED (user loads pretrained models)
- Native/Training mode: Create appropriate default per backbone

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- AudioGenModel: remove AudioGenOptions dependency, add two constructors
  (ONNX and Native mode), add code regions, fix UpdateParameters
- WhisperModel: remove WhisperOptions dependency, add two constructors,
  add code regions, use DenseLayer instead of Conv1DLayer
- TtsModel: remove TtsOptions dependency, add two constructors,
  add code regions, fix Train method with ToVector conversion

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove dependency on nerfoptions class
- Single constructor with all parameters having defaults
- Add proper code regions for organization
- Fix train method to use _lossfunction and tovector() pattern
- Industry-standard defaults (10 position encoding levels, etc.)
- Comprehensive documentation with for beginners sections

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

♻️ Duplicate comments (5)
src/Audio/Whisper/WhisperModel.cs (1)

578-609: Incorrect duration calculation uses sample value instead of sample count.

Line 606 computes DurationSeconds by reading the last audio sample's value (audio[audio.Length - 1]) instead of using the audio length. This produces incorrect duration values. The correct calculation should divide the number of samples by the sample rate.

🔎 Proposed fix
         return new TranscriptionResult<T>
         {
             Text = text,
             Language = effectiveLanguage,
             Confidence = NumOps.FromDouble(1.0), // TODO: Extract from decoder
-            DurationSeconds = NumOps.ToDouble(audio[audio.Length - 1]) / SampleRate,
+            DurationSeconds = (double)audio.Length / SampleRate,
             Segments = includeTimestamps ? ExtractSegments(tokens, text) : Array.Empty<TranscriptionSegment<T>>()
         };
src/Audio/SourceSeparation/MusicSourceSeparator.cs (2)

292-318: Buffer overflow when kernelSize is even.

The window array is allocated with size kernelSize, but the loop iterates 2 * halfKernel + 1 times. When kernelSize is even (e.g., 4), halfKernel = 2, and the loop runs 5 times, causing an IndexOutOfRangeException on window[count++].

🔎 Proposed fix
 private Tensor<T> MedianFilterTime(Tensor<T> input, int kernelSize)
 {
     int numFrames = input.Shape[0];
     int numBins = input.Shape[1];
     var output = new Tensor<T>([numFrames, numBins]);
     int halfKernel = kernelSize / 2;
+    int windowSize = 2 * halfKernel + 1;

     for (int f = 0; f < numBins; f++)
     {
-        var window = new double[kernelSize];
+        var window = new double[windowSize];

         for (int t = 0; t < numFrames; t++)
         {
             int count = 0;
             for (int k = -halfKernel; k <= halfKernel; k++)
             {
                 int ti = Math.Max(0, Math.Min(numFrames - 1, t + k));
                 window[count++] = _numOps.ToDouble(input[ti, f]);
             }

             Array.Sort(window, 0, count);
             output[t, f] = _numOps.FromDouble(window[count / 2]);
         }
     }

     return output;
 }

320-346: Same buffer overflow issue as MedianFilterTime.

Apply the same fix here—allocate window with size 2 * halfKernel + 1 instead of kernelSize.

🔎 Proposed fix
 private Tensor<T> MedianFilterFrequency(Tensor<T> input, int kernelSize)
 {
     int numFrames = input.Shape[0];
     int numBins = input.Shape[1];
     var output = new Tensor<T>([numFrames, numBins]);
     int halfKernel = kernelSize / 2;
+    int windowSize = 2 * halfKernel + 1;

     for (int t = 0; t < numFrames; t++)
     {
-        var window = new double[kernelSize];
+        var window = new double[windowSize];
src/Audio/Fingerprinting/SpectrogramFingerprinter.cs (2)

148-161: PeaksPerSecond naming issue remains unaddressed.

As noted in a previous review, windowSize is set to PeaksPerSecond, but this value represents a frame window size, not peaks per second. The naming is misleading.


203-217: FrameCount is set to peak count instead of actual frame count.

As noted in a previous review, FrameCount = peaks.Count is inconsistent with the semantic meaning of frame count. This should be the actual number of spectrogram frames for consistency with ChromaprintFingerprinter.

🟠 Major comments (20)
src/Audio/Whisper/WhisperModel.cs-293-359 (1)

293-359: Resource leak if decoder ONNX model loading fails.

At lines 348-349, the encoder ONNX model is created before the decoder. If the decoder constructor throws (e.g., file not found, invalid ONNX), the encoder is never disposed, causing a resource leak.

🔎 Proposed fix
         // Load ONNX models
         var options = onnxOptions ?? new OnnxModelOptions();
-        OnnxEncoder = new OnnxModel<T>(encoderPath, options);
-        OnnxDecoder = new OnnxModel<T>(decoderPath, options);
+        OnnxModel<T>? encoder = null;
+        try
+        {
+            encoder = new OnnxModel<T>(encoderPath, options);
+            OnnxDecoder = new OnnxModel<T>(decoderPath, options);
+            OnnxEncoder = encoder;
+        }
+        catch
+        {
+            encoder?.Dispose();
+            throw;
+        }
src/Audio/Features/SpectralFeatureExtractor.cs-112-116 (1)

112-116: Avoid duplicate centroid computation.

When both Centroid and Bandwidth flags are enabled, ComputeSpectralCentroid is called twice (Line 109 and Line 114). Cache the result to eliminate redundant work.

🔎 Proposed fix
     // Extract features
     var features = new List<double[]>();
+    double[]? centroids = null;

     if (_featureTypes.HasFlag(SpectralFeatureType.Centroid))
     {
-        features.Add(ComputeSpectralCentroid(magnitude, freqBins, numFrames, numFreqs));
+        centroids = ComputeSpectralCentroid(magnitude, freqBins, numFrames, numFreqs);
+        features.Add(centroids);
     }

     if (_featureTypes.HasFlag(SpectralFeatureType.Bandwidth))
     {
-        var centroids = ComputeSpectralCentroid(magnitude, freqBins, numFrames, numFreqs);
+        centroids ??= ComputeSpectralCentroid(magnitude, freqBins, numFrames, numFreqs);
         features.Add(ComputeSpectralBandwidth(magnitude, freqBins, centroids, numFrames, numFreqs));
     }
src/Audio/SourceSeparation/MusicSourceSeparator.cs-444-485 (1)

444-485: Validate mask tensor shape before indexing.

Line 465 assumes the mask tensor has shape [batch, stems, time, freq] with at least 4 dimensions. If the ONNX model returns a different shape (e.g., [stems, time, freq] or [batch, time, freq, stems]), the bounds check may pass incorrectly or the indexing at Line 467 will throw an exception.

🔎 Proposed fix
 private SeparationResult<T> ApplyMasksAndReconstruct(Tensor<Complex<T>> stft, Tensor<T> masks)
 {
+    // Validate mask tensor shape
+    if (masks.Rank != 4)
+        throw new ArgumentException($"Expected 4D mask tensor [batch, stems, time, freq], got rank {masks.Rank}");
+    
+    if (masks.Shape[1] < 4)
+        throw new ArgumentException($"Expected at least 4 stems in mask tensor, got {masks.Shape[1]}");
+    
     // masks expected shape: [batch, stems, time, freq] or [batch, stems, freq, time]
     int numFrames = stft.Shape[0];
     int numBins = stft.Shape[1];
src/Helpers/LayerHelper.cs-4482-4498 (1)

4482-4498: Documentation is misleading given architectural limitations.

The documentation states "This creates a trainable model from scratch" (line 4495), but the implementation cannot create a functional Whisper model due to the architectural issue: encoder-decoder models cannot be expressed as a flat sequential layer list.

Update the documentation once the architectural issue is resolved to accurately describe what the method creates and how it should be used.

src/Helpers/LayerHelper.cs-4631-4631 (1)

4631-4631: Consider softmax activation for vocabulary prediction.

The output layer uses identityActivation, which outputs raw logits. For vocabulary prediction in text generation, you typically want a softmax activation to produce a probability distribution over the vocabulary. Compare with other text generation methods in this class (e.g., line 1202, 1207, 2619).

🔎 Proposed change
 // Output projection to vocabulary
-yield return new DenseLayer<T>(modelDimension, vocabularySize, identityActivation);
+yield return new DenseLayer<T>(modelDimension, vocabularySize, new SoftmaxActivation<T>() as IActivationFunction<T>);

Alternatively, if you prefer to keep logits output (e.g., for certain loss functions), add a clarifying comment explaining why identity activation is used.

src/Audio/Speaker/SpeakerEmbedding.cs-32-52 (1)

32-52: Validate vector dimensions and add null check.

The method takes the minimum length of the two vectors, which allows comparison of embeddings with different dimensions. Speaker embeddings should have fixed dimensions (as configured in SpeakerEmbeddingOptions.EmbeddingDimension), and comparing partial overlaps produces misleading similarity scores.

Additionally, the other parameter lacks a null check.

🔎 Proposed fix
 public double CosineSimilarity(SpeakerEmbedding<T> other)
 {
+    ArgumentNullException.ThrowIfNull(other);
+    
+    if (Vector.Length != other.Vector.Length)
+    {
+        throw new ArgumentException(
+            $"Vector dimensions must match. Expected {Vector.Length}, got {other.Vector.Length}.",
+            nameof(other));
+    }
+
     double dot = 0;
     double norm1 = 0;
     double norm2 = 0;

-    int len = Math.Min(Vector.Length, other.Vector.Length);
-    for (int i = 0; i < len; i++)
+    for (int i = 0; i < Vector.Length; i++)
     {
         double v1 = NumOps.ToDouble(Vector[i]);
         double v2 = NumOps.ToDouble(other.Vector[i]);
         dot += v1 * v2;
         norm1 += v1 * v1;
         norm2 += v2 * v2;
     }

     double denominator = Math.Sqrt(norm1 * norm2);
     if (denominator < 1e-10) return 0;

     return dot / denominator;
 }
src/Audio/Speaker/SpeakerEmbedding.cs-57-69 (1)

57-69: Validate vector dimensions and add null check.

Same issue as CosineSimilarity: the method uses the minimum length, allowing dimension mismatches that produce incorrect distance metrics. Add a null check and dimension validation.

🔎 Proposed fix
 public double EuclideanDistance(SpeakerEmbedding<T> other)
 {
+    ArgumentNullException.ThrowIfNull(other);
+    
+    if (Vector.Length != other.Vector.Length)
+    {
+        throw new ArgumentException(
+            $"Vector dimensions must match. Expected {Vector.Length}, got {other.Vector.Length}.",
+            nameof(other));
+    }
+
     double sumSquared = 0;
-    int len = Math.Min(Vector.Length, other.Vector.Length);

-    for (int i = 0; i < len; i++)
+    for (int i = 0; i < Vector.Length; i++)
     {
         double diff = NumOps.ToDouble(Vector[i]) - NumOps.ToDouble(other.Vector[i]);
         sumSquared += diff * diff;
     }

     return Math.Sqrt(sumSquared);
 }
src/Audio/Speaker/SpeakerDiarizer.cs-156-186 (1)

156-186: Memory and performance concerns for long audio.

The similarity matrix (line 163) is allocated as a full n × n array where n is the number of segments. For long recordings with short hop durations, this can consume significant memory and the subsequent agglomerative clustering has O(n³) complexity.

Example: A 1-hour recording with 0.5s hop produces ~7,200 segments, resulting in a 7200×7200 similarity matrix (~414 MB) and potentially millions of cluster comparison operations.

🔎 Recommended optimizations

Option 1: Add input validation with warnings

 private int[] ClusterEmbeddings(List<SpeakerEmbedding<T>> embeddings)
 {
     if (embeddings.Count == 0)
         return [];
+    
+    if (embeddings.Count > 5000)
+        throw new InvalidOperationException(
+            $"Clustering {embeddings.Count} segments may cause memory/performance issues. " +
+            "Consider increasing WindowDurationSeconds or HopDurationSeconds.");

     // Compute similarity matrix

Option 2: Use sparse clustering for large inputs

  • Switch to k-means or online clustering for segment counts > 1000
  • Compute similarities on-demand rather than storing full matrix
  • Use approximate nearest neighbor techniques (e.g., locality-sensitive hashing)

Option 3: Document limitations
Add to class XML docs:

/// <remarks>
/// <para><b>Performance Note:</b> Memory and computational complexity scale 
/// quadratically with the number of audio segments. For recordings longer than 
/// 10 minutes, increase WindowDurationSeconds and HopDurationSeconds to reduce 
/// segment count.</para>
/// </remarks>
src/Audio/Classification/AudioEventDetector.cs-244-272 (1)

244-272: Last partial window is dropped when complete windows exist (data loss issue).

The loop at line 250 stops when start + windowSamples > audio.Length, but the partial window handling at lines 261-269 only activates if windows.Count == 0. This means if any complete windows exist, the remaining audio samples that don't fill a complete window are silently dropped.

Example: For 1000 samples with windowSamples=512 and hopSamples=256, windows are created at offsets 0, 256, 512 (which requires 1024 samples), but the loop stops. Samples 768-999 (232 samples) are lost.

Consider adding logic after line 258 to handle the last partial window even when complete windows exist, possibly by zero-padding or processing the remaining samples.

🔎 Proposed fix to handle last partial window
         for (int start = 0; start + windowSamples <= audio.Length; start += hopSamples)
         {
             var window = new Tensor<T>([windowSamples]);
             for (int i = 0; i < windowSamples; i++)
             {
                 window[i] = audio[start + i];
             }
             windows.Add(window);
         }
 
+        // Handle last partial window if there are remaining samples
+        int lastStart = windows.Count > 0 ? windows.Count * hopSamples : 0;
+        if (lastStart < audio.Length)
+        {
+            int remainingSamples = audio.Length - lastStart;
+            var window = new Tensor<T>([remainingSamples]);
+            for (int i = 0; i < remainingSamples; i++)
+            {
+                window[i] = audio[lastStart + i];
+            }
+            windows.Add(window);
+        }
+
-        // Handle last partial window
-        if (windows.Count == 0 && audio.Length > 0)
-        {
-            var window = new Tensor<T>([audio.Length]);
-            for (int i = 0; i < audio.Length; i++)
-            {
-                window[i] = audio[i];
-            }
-            windows.Add(window);
-        }
src/Audio/Features/ConstantQTransform.cs-227-262 (1)

227-262: Apply the same input validation as suggested for Transform().

This method has the same validation issues as Transform() (null check, shape validation, minimum length check). Apply the same fixes here to ensure consistent error handling.

src/Audio/Features/ConstantQTransform.cs-83-119 (1)

83-119: Add input validation to prevent invalid configurations.

The constructor accepts parameters without validation, which could lead to runtime errors or nonsensical results. For example:

  • Negative or zero sampleRate, fMin, binsPerOctave, numOctaves, or hopLength would produce invalid calculations
  • Very large binsPerOctave could cause division issues in the Q factor calculation
  • The window length calculation could overflow for extreme parameter combinations
🔎 Proposed fix to add parameter validation
 public ConstantQTransform(
     int sampleRate = 22050,
     double fMin = 32.70,
     int binsPerOctave = 12,
     int numOctaves = 7,
     int hopLength = 512,
     WindowType windowType = WindowType.Hann)
 {
+    if (sampleRate <= 0)
+        throw new ArgumentOutOfRangeException(nameof(sampleRate), "Sample rate must be positive.");
+    if (fMin <= 0)
+        throw new ArgumentOutOfRangeException(nameof(fMin), "Minimum frequency must be positive.");
+    if (binsPerOctave <= 0)
+        throw new ArgumentOutOfRangeException(nameof(binsPerOctave), "Bins per octave must be positive.");
+    if (numOctaves <= 0)
+        throw new ArgumentOutOfRangeException(nameof(numOctaves), "Number of octaves must be positive.");
+    if (hopLength <= 0)
+        throw new ArgumentOutOfRangeException(nameof(hopLength), "Hop length must be positive.");
+
     _numOps = MathHelper.GetNumericOperations<T>();
     _sampleRate = sampleRate;
src/Audio/Features/ConstantQTransform.cs-181-220 (1)

181-220: Add input validation for the audio tensor.

The method lacks validation that could lead to incorrect results or runtime errors:

  1. No null check: audio could be null
  2. No shape validation: Expects 1D tensor but doesn't verify rank
  3. Insufficient length handling: If audio is shorter than the longest window, numFrames calculation becomes negative, then forced to 1, producing incorrect results

The boundary check (frameStart + n) < numSamples prevents crashes but silently produces zero-padded results for too-short audio, which may not be the intended behavior.

🔎 Proposed fix to add validation
 public Tensor<T> Transform(Tensor<T> audio)
 {
+    if (audio == null)
+        throw new ArgumentNullException(nameof(audio));
+    if (audio.Rank != 1)
+        throw new ArgumentException("Audio must be a 1D tensor.", nameof(audio));
+    
     int numSamples = audio.Shape[0];
+    
+    int minRequiredSamples = _windowLengths[_numBins - 1];
+    if (numSamples < minRequiredSamples)
+        throw new ArgumentException(
+            $"Audio too short: need at least {minRequiredSamples} samples, got {numSamples}.",
+            nameof(audio));
+    
     int numFrames = (numSamples - _windowLengths[_numBins - 1]) / _hopLength + 1;
-    numFrames = Math.Max(1, numFrames);
src/Audio/Classification/SceneClassifier.cs-411-420 (1)

411-420: Guard against empty audio in RMS energy computation.

Line 419 divides by audio.Length. If the audio tensor is empty, this throws DivideByZeroException. Add a guard:

-        return Math.Sqrt(sum / audio.Length);
+        return audio.Length > 0 ? Math.Sqrt(sum / audio.Length) : 0;
src/Audio/Classification/SceneClassifier.cs-378-409 (1)

378-409: Guard against zero frames in spectral contrast computation.

Line 408 divides by melSpec.Shape[0] * numBands. If melSpec.Shape[0] (number of frames) is zero, this throws DivideByZeroException. Add a guard:

         return totalContrast / (melSpec.Shape[0] * numBands);
+        return melSpec.Shape[0] > 0 ? totalContrast / (melSpec.Shape[0] * numBands) : 0;
src/Audio/Classification/SceneClassifier.cs-422-433 (1)

422-433: Guard against empty audio in zero crossing rate computation.

Line 432 divides by audio.Length. If the audio tensor is empty, this throws DivideByZeroException. Add a guard:

-        return (double)crossings / audio.Length;
+        return audio.Length > 0 ? (double)crossings / audio.Length : 0;
src/Audio/Classification/SceneClassifier.cs-435-453 (1)

435-453: Guard against empty spectrogram in energy variance computation.

Lines 449-450 call Average() and divide by frameEnergies.Length. If melSpec.Shape[0] is zero, frameEnergies is empty, and Average() throws InvalidOperationException. Add a guard:

+        if (frameEnergies.Length == 0)
+            return 0;
+
         double mean = frameEnergies.Average();
         double variance = frameEnergies.Sum(e => (e - mean) * (e - mean)) / frameEnergies.Length;
src/Audio/Classification/SceneClassifier.cs-250-274 (1)

250-274: Guard against zero frames when computing MFCC statistics.

In addition to the division by zero at line 283 (already flagged), lines 265 and 273 will throw if numFrames == 0. The method should validate that numFrames > 0 at the start:

         int numFrames = mfccs.Shape[0];
         int numCoeffs = mfccs.Shape[1];
+
+        if (numFrames == 0)
+            throw new ArgumentException("Audio must contain at least one MFCC frame.", nameof(audio));

Or return a safe default feature set for empty audio. This prevents cascading divisions by zero.

src/Audio/AudioGen/AudioGenModel.cs-764-783 (1)

764-783: Optimizer parameter is ignored; learning rate is hardcoded.

The constructor accepts an optimizer parameter (line 340, 477) and stores it in _optimizer (lines 403, 507), but UpdateParameters hardcodes the learning rate at 0.001 (line 775) and performs manual gradient descent instead of delegating to the optimizer. This prevents users from configuring learning rates or using different optimization algorithms (Adam, RMSprop, etc.).

🔎 Proposed fix

Delegate parameter updates to the optimizer:

 public override void UpdateParameters(Vector<T> gradients)
 {
     if (!_useNativeMode)
     {
         throw new NotSupportedException("Cannot update parameters in ONNX inference mode. Use the native constructor for training.");
     }
 
-    // Get current parameters
-    var currentParams = GetParameters();
-
-    // Apply gradient descent: params = params - learning_rate * gradients
-    T learningRate = NumOps.FromDouble(0.001); // Default learning rate
-    for (int i = 0; i < currentParams.Length; i++)
-    {
-        currentParams[i] = NumOps.Subtract(currentParams[i], NumOps.Multiply(learningRate, gradients[i]));
-    }
-
-    // Set the updated parameters
-    SetParameters(currentParams);
+    // Delegate to the optimizer
+    var currentParams = GetParameters();
+    var updatedParams = _optimizer.UpdateParameters(currentParams, gradients);
+    SetParameters(updatedParams);
 }

Note: Verify that the optimizer interface supports this usage pattern; adjust as needed for the actual IOptimizer API.

Committable suggestion skipped: line range outside the PR's diff.

src/Audio/AudioGen/AudioGenModel.cs-1016-1028 (1)

1016-1028: Inefficient tensor growth causes O(n²) copying.

Growing currentTokens by copying to a new larger tensor on each iteration (lines 1018-1027) results in O(n²) time complexity. For a 30-second generation (~1500 tokens), this performs 1500 iterations with growing copy operations, creating significant overhead.

🔎 Proposed optimization

Pre-allocate the full tensor and track the current position:

-var currentTokens = new Tensor<T>([1, _numCodebooks, 1]);
+var currentTokens = new Tensor<T>([1, _numCodebooks, numTokens]);
+int currentPos = 0;

 for (int cb = 0; cb < _numCodebooks; cb++)
 {
-    currentTokens[0, cb, 0] = NumOps.Zero;
+    currentTokens[0, cb, currentPos] = NumOps.Zero;
 }
+currentPos++;

 for (int t = 0; t < numTokens; t++)
 {
     Tensor<T> logits;

     if (_useNativeMode)
     {
-        logits = ForwardLanguageModel(textEmbeddings, currentTokens);
+        // Pass only the filled portion
+        var currentView = currentTokens.Slice(0, currentPos);
+        logits = ForwardLanguageModel(textEmbeddings, currentView);
     }
     else
     {
         var inputs = new Dictionary<string, Tensor<T>>
         {
             ["text_embeddings"] = textEmbeddings,
-            ["audio_codes"] = currentTokens
+            ["audio_codes"] = currentTokens.Slice(0, currentPos)
         };
         var outputs = _languageModel!.Run(inputs);
         logits = outputs.Values.First();
     }

     for (int cb = 0; cb < _numCodebooks; cb++)
     {
         int nextToken = SampleFromLogits(logits, cb, random);
         codes[0, cb, t] = NumOps.FromDouble(nextToken);
-
-        if (t < numTokens - 1)
-        {
-            var newTokens = new Tensor<T>([1, _numCodebooks, currentTokens.Shape[2] + 1]);
-            for (int c = 0; c < _numCodebooks; c++)
-            {
-                for (int i = 0; i < currentTokens.Shape[2]; i++)
-                {
-                    newTokens[0, c, i] = currentTokens[0, c, i];
-                }
-                newTokens[0, c, currentTokens.Shape[2]] = codes[0, c, t];
-            }
-            currentTokens = newTokens;
-        }
+        currentTokens[0, cb, currentPos] = codes[0, cb, t];
     }
+    currentPos++;
 }

Note: This assumes Tensor supports slicing; if not, consider using a different data structure or passing currentPos as a length parameter.

Apply the same fix to GenerateAudioCodesWithGuidance at lines 1124-1136.

Committable suggestion skipped: line range outside the PR's diff.

src/Audio/AudioGen/AudioGenModel.cs-888-912 (1)

888-912: Serialization doesn't preserve ONNX mode; deserialization always creates native instances.

CreateNewInstance always uses the native constructor (line 890) regardless of the original mode. When an ONNX-mode model is serialized, _useNativeMode = false is written (line 841), but deserialization reads and discards this value (line 866), and CreateNewInstance reconstructs in native mode. This breaks round-trip serialization for ONNX models, as they cannot be restored to their original ONNX state.

🔎 Proposed fix

Store the mode and paths in instance fields during deserialization, then use them in CreateNewInstance:

+private bool _deserializedUseNativeMode;
+private AudioGenModelSize _deserializedModelSize;
+// ... other deserialized fields
+
 protected override void DeserializeNetworkSpecificData(BinaryReader reader)
 {
-    // Read values to advance stream position (validation done in CreateNewInstance)
-    _ = reader.ReadBoolean();  // useNativeMode
-    _ = reader.ReadInt32();    // modelSize
+    _deserializedUseNativeMode = reader.ReadBoolean();
+    _deserializedModelSize = (AudioGenModelSize)reader.ReadInt32();
     // ... read other fields into instance variables
-    _ = reader.ReadInt32();    // sampleRate
+    _deserializedSampleRate = reader.ReadInt32();
     // ... etc.
 }

 protected override IFullModel<T, Tensor<T>, Tensor<T>> CreateNewInstance()
 {
+    if (!_deserializedUseNativeMode)
+    {
+        // Cannot restore ONNX mode without model paths
+        throw new NotSupportedException(
+            "Cannot deserialize ONNX-mode AudioGen models. " +
+            "ONNX models require file paths that are not preserved in serialization. " +
+            "Use FromFiles or the ONNX constructor to recreate the model.");
+    }
+    
     return new AudioGenModel<T>(
         Architecture,
-        _modelSize,
-        _sampleRate,
+        _deserializedModelSize,
+        _deserializedSampleRate,
         // ... use deserialized values
     );
 }

Alternatively, if ONNX models shouldn't be serializable, document this clearly and throw in SerializeNetworkSpecificData when !_useNativeMode.

Committable suggestion skipped: line range outside the PR's diff.

🟡 Minor comments (7)
src/Audio/SourceSeparation/SourceSeparationOptions.cs-20-20 (1)

20-20: Enforce StemCount constraint.

The documentation states "2, 4, or 5" but no validation prevents invalid values like 0, 1, 3, or 100. Invalid stem counts will cause issues downstream (e.g., Line 489-495 in MusicSourceSeparator.cs falls back to 4 for any invalid value).

🔎 Proposed fix
-    /// <summary>Number of stems to separate (2, 4, or 5). Default: 4.</summary>
-    public int StemCount { get; set; } = 4;
+    private int _stemCount = 4;
+    
+    /// <summary>Number of stems to separate (2, 4, or 5). Default: 4.</summary>
+    public int StemCount
+    {
+        get => _stemCount;
+        set
+        {
+            if (value is not (2 or 4 or 5))
+                throw new ArgumentOutOfRangeException(nameof(value), "StemCount must be 2, 4, or 5.");
+            _stemCount = value;
+        }
+    }
src/Audio/Features/MfccOptions.cs-30-34 (1)

30-34: Validate AppendDeltaDelta constraint.

The documentation states that AppendDeltaDelta requires AppendDelta to be true, but there's no validation enforcing this constraint. If a user sets AppendDeltaDelta = true while AppendDelta = false, it could lead to incorrect feature dimensions or runtime errors.

🔎 Suggested validation approach

Consider adding a validation method or property:

+    /// <summary>
+    /// Validates the options configuration.
+    /// </summary>
+    public void Validate()
+    {
+        if (AppendDeltaDelta && !AppendDelta)
+            throw new InvalidOperationException("AppendDeltaDelta requires AppendDelta to be true.");
+    }

Or validate in the consumer (MfccExtractor constructor):

 public MfccExtractor(MfccOptions? options = null)
     : base(options)
 {
     var mfccOptions = options ?? new MfccOptions();
+    
+    if (mfccOptions.AppendDeltaDelta && !mfccOptions.AppendDelta)
+        throw new ArgumentException("AppendDeltaDelta requires AppendDelta to be true.", nameof(options));

Committable suggestion skipped: line range outside the PR's diff.

src/Audio/Features/MfccExtractor.cs-51-60 (1)

51-60: FeatureDimension calculation assumes valid configuration.

The calculation at line 57 dim = dim / 2 * 3 assumes that AppendDelta was already applied (line 56). This only works correctly when AppendDelta is true. If AppendDeltaDelta is true but AppendDelta is false (an invalid configuration per MfccOptions documentation), this produces an incorrect dimension.

This highlights the importance of validating the constraint documented in MfccOptions (that AppendDeltaDelta requires AppendDelta).

🔎 Suggested fix

Add validation in the constructor to enforce the constraint:

 public MfccExtractor(MfccOptions? options = null)
     : base(options)
 {
     var mfccOptions = options ?? new MfccOptions();
+    
+    if (mfccOptions.AppendDeltaDelta && !mfccOptions.AppendDelta)
+        throw new ArgumentException(
+            "AppendDeltaDelta requires AppendDelta to be true.", 
+            nameof(options));

     _numCoefficients = mfccOptions.NumCoefficients;

Committable suggestion skipped: line range outside the PR's diff.

src/Audio/Classification/GenreClassifier.cs-309-364 (1)

309-364: Consider edge case in BPM calculation.

The tempo estimation uses autocorrelation to find periodicity. Line 363 computes 60.0 / lagSeconds, which could theoretically divide by zero if bestLag is 0 or very small. While bestLag is initialized to minLag (which should be positive for normal sample rates), consider adding a defensive check to avoid potential division by zero in edge cases.

🔎 Suggested defensive check
         // Convert lag to BPM
         double lagSeconds = (double)bestLag * frameSize / _options.SampleRate;
-        return 60.0 / lagSeconds;
+        return lagSeconds > 0 ? 60.0 / lagSeconds : 120.0; // Default to 120 BPM
src/Audio/Features/ConstantQTransform.cs-156-174 (1)

156-174: Handle edge case when window length is 1.

The window formulas use (length - 1) as the denominator, which causes division by zero when length == 1. While this is unlikely in typical CQT usage, it could occur with extreme parameter combinations (very high frequencies, low sample rates).

🔎 Proposed fix to handle length == 1
 private static double[] CreateWindow(int length, WindowType windowType)
 {
     var window = new double[length];
+    
+    if (length == 1)
+    {
+        window[0] = 1.0;
+        return window;
+    }

     for (int n = 0; n < length; n++)
     {
src/Audio/MusicAnalysis/ChordSegment.cs-23-26 (1)

23-26: Duration can be negative if EndTime < StartTime.

The computed Duration property doesn't prevent negative values. Ensure upstream code validates that EndTime >= StartTime when populating this result.

src/Audio/AudioGen/AudioGenModel.cs-603-632 (1)

603-632: Add validation for duration parameter.

The durationSeconds parameter is not validated against _maxDurationSeconds. Passing values exceeding the maximum could lead to excessive resource consumption or unexpected behavior.

🔎 Suggested validation
 public Tensor<T> GenerateAudio(
     string prompt,
     string? negativePrompt = null,
     double durationSeconds = 5.0,
     int numInferenceSteps = 100,
     double guidanceScale = 3.0,
     int? seed = null)
 {
     ThrowIfDisposed();
+    
+    if (durationSeconds <= 0)
+        throw new ArgumentOutOfRangeException(nameof(durationSeconds), "Duration must be positive.");
+    if (durationSeconds > _maxDurationSeconds)
+        throw new ArgumentOutOfRangeException(nameof(durationSeconds), 
+            $"Duration {durationSeconds}s exceeds maximum {_maxDurationSeconds}s.");
 
     int seedUsed = seed ?? _random.Next();
     // ...
 }

Comment thread src/Audio/AudioGen/AudioGenModel.cs
Comment thread src/Audio/Features/SpectralFeatureExtractor.cs
Comment thread src/Audio/MusicAnalysis/BeatTracker.cs
Comment thread src/Audio/MusicAnalysis/BeatTrackingResult.cs Outdated
Comment thread src/Audio/TextToSpeech/TtsModel.cs Outdated
Comment thread src/Helpers/LayerHelper.cs
Comment thread src/Helpers/LayerHelper.cs Outdated
… null checks

argumentnullexception.throwifnull was introduced in .net 6 and is not
available in .net framework 4.7.1. replace with explicit null checks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/Audio/TextToSpeech/TtsModel.cs (1)

321-329: Resource leak if vocoder construction fails.

If the OnnxModel<T> constructor for the vocoder at line 327 throws an exception, the already-created _acousticModel at line 322 will not be disposed, leaking resources.

🔎 Proposed fix with exception safety
         // Initialize preprocessor
         _preprocessor = new TtsPreprocessor();
 
-        // Load acoustic model
-        _acousticModel = new OnnxModel<T>(acousticModelPath, onnxOptions ?? new OnnxModelOptions());
+        OnnxModel<T>? acousticModel = null;
+        OnnxModel<T>? vocoder = null;
+        
+        try
+        {
+            // Load acoustic model
+            acousticModel = new OnnxModel<T>(acousticModelPath, onnxOptions ?? new OnnxModelOptions());
 
-        // Load vocoder or create Griffin-Lim fallback
-        if (vocoderModelPath is not null && vocoderModelPath.Length > 0)
-        {
-            _vocoder = new OnnxModel<T>(vocoderModelPath, onnxOptions ?? new OnnxModelOptions());
-            OnnxModel = _vocoder;
-        }
+            // Load vocoder or create Griffin-Lim fallback
+            if (vocoderModelPath is not null && vocoderModelPath.Length > 0)
+            {
+                vocoder = new OnnxModel<T>(vocoderModelPath, onnxOptions ?? new OnnxModelOptions());
+            }
+            
+            _acousticModel = acousticModel;
+            _vocoder = vocoder;
+            if (_vocoder is not null)
+            {
+                OnnxModel = _vocoder;
+            }
+        }
+        catch
+        {
+            acousticModel?.Dispose();
+            vocoder?.Dispose();
+            throw;
+        }
src/Audio/Whisper/WhisperModel.cs (1)

603-610: Incorrect duration calculation uses sample value instead of sample count.

Line 608 computes DurationSeconds by reading the last audio sample's value (audio[audio.Length - 1]) instead of using the audio length. This produces incorrect duration values.

🔎 Proposed fix
         return new TranscriptionResult<T>
         {
             Text = text,
             Language = effectiveLanguage,
             Confidence = NumOps.FromDouble(1.0), // TODO: Extract from decoder
-            DurationSeconds = NumOps.ToDouble(audio[audio.Length - 1]) / SampleRate,
+            DurationSeconds = (double)audio.Length / SampleRate,
             Segments = includeTimestamps ? ExtractSegments(tokens, text) : Array.Empty<TranscriptionSegment<T>>()
         };
🧹 Nitpick comments (1)
src/Audio/Whisper/WhisperModel.cs (1)

509-525: Extract hardcoded vocabulary size to a constant.

The Whisper vocabulary size 51865 is hardcoded at lines 510 and 525. Extract this to a named constant for maintainability.

🔎 Proposed refactor

Add a constant near the top of the class:

+    /// <summary>
+    /// Whisper model vocabulary size.
+    /// </summary>
+    private const int WhisperVocabSize = 51865;

Then use it in the layer initialization:

         // Decoder: Token embeddings + Encoder output -> Logits
         // Token embedding (Whisper vocabulary is 51865 tokens)
-        _tokenEmbedding = new EmbeddingLayer<T>(51865, _modelDim);
+        _tokenEmbedding = new EmbeddingLayer<T>(WhisperVocabSize, _modelDim);
 
         // Decoder transformer layers
         for (int i = 0; i < _numDecoderLayers; i++)
         {
             var decoderLayer = new TransformerDecoderLayer<T>(
                 embeddingSize: _modelDim,
                 numHeads: _numHeads,
                 feedForwardDim: _ffDim,
                 sequenceLength: _maxTokens,
                 ffnActivation: (IActivationFunction<T>?)null);
             _decoderLayers.Add(decoderLayer);
         }
 
         // Output projection to vocabulary
-        _outputProjection = new DenseLayer<T>(_modelDim, 51865);
+        _outputProjection = new DenseLayer<T>(_modelDim, WhisperVocabSize);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5cb684 and 07b4332.

📒 Files selected for processing (2)
  • src/Audio/TextToSpeech/TtsModel.cs
  • src/Audio/Whisper/WhisperModel.cs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-18T08:49:25.295Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningMask.cs:1-102
Timestamp: 2025-12-18T08:49:25.295Z
Learning: In the AiDotNet repository, the project-level global using includes AiDotNet.Tensors.LinearAlgebra via AiDotNet.csproj. Therefore, Vector<T>, Matrix<T>, and Tensor<T> are available without per-file using directives. Do not flag missing using directives for these types in any C# files within this project. Apply this guideline broadly to all C# files (not just a single file) to avoid false positives. If a file uses a type from a different namespace not covered by the global using, flag as usual.

Applied to files:

  • src/Audio/TextToSpeech/TtsModel.cs
  • src/Audio/Whisper/WhisperModel.cs
📚 Learning: 2025-12-18T08:49:53.103Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningStrategy.cs:1-4
Timestamp: 2025-12-18T08:49:53.103Z
Learning: In this repository, global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*) and common system types. When reviewing C# files, assume these global usings are in effect; avoid adding duplicate using statements for these namespaces and for types like Vector<T>, Matrix<T>, Tensor<T>, etc. If a type is not found, verify the global usings or consider adding a file-scoped using if needed. Prefer relying on global usings to reduce boilerplate.

Applied to files:

  • src/Audio/TextToSpeech/TtsModel.cs
  • src/Audio/Whisper/WhisperModel.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build (Windows)
  • GitHub Check: CodeQL Analysis
🔇 Additional comments (1)
src/Audio/TextToSpeech/TtsModel.cs (1)

986-998: Verify disposal of Griffin-Lim vocoder.

The _griffinLim field is not disposed in the Dispose method. If GriffinLim<T> implements IDisposable or holds unmanaged resources, this could cause a resource leak.

Comment thread src/Audio/Whisper/WhisperModel.cs Outdated
…ttern

- Two-constructor pattern (ONNX inference + native training)
- CNN-based feature extraction with configurable blocks
- Emotion classification with arousal/valence prediction
- Time-series emotion recognition for long recordings
- Uses internal layer lists following SileroVad pattern

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
src/Audio/Classification/AudioEventDetector.cs (4)

660-678: _melSpectrogram is not reinitialized after deserialization.

After deserializing updated option values (SampleRate, NumMels, FftSize, HopLength), the readonly _melSpectrogram field retains the instance created at construction with the original options. This causes a mismatch between the deserialized configuration and actual feature extraction behavior—PreprocessAudio (line 598), Detect (line 293), and other methods will extract features using stale parameters.

🔎 Recommended approaches

Option 1: Block deserialization until proper support is implemented:

 protected override void DeserializeNetworkSpecificData(BinaryReader reader)
 {
+    throw new NotSupportedException(
+        "Deserialization is not currently supported for AudioEventDetector. " +
+        "Please reconstruct the model from the original options and weights.");
+
     // Restore options properties
     _options.SampleRate = reader.ReadInt32();
     ...

Option 2: Make _melSpectrogram mutable and reinitialize:

-    private readonly MelSpectrogram<T>? _melSpectrogram;
+    private MelSpectrogram<T>? _melSpectrogram;

Then at the end of DeserializeNetworkSpecificData:

     ClassLabels = labels;
+    
+    // Reinitialize feature extractor with updated options
+    _melSpectrogram = new MelSpectrogram<T>(
+        sampleRate: _options.SampleRate,
+        nMels: _options.NumMels,
+        nFft: _options.FftSize,
+        hopLength: _options.HopLength,
+        fMin: _options.FMin,
+        fMax: _options.FMax,
+        logMel: true);

743-757: Silent fallback to rules-based classification in error state.

The else branch calling ClassifyWithRules is reached only when both conditions fail:

  • ONNX mode is not properly initialized (IsOnnxMode false or OnnxEncoder null)
  • Native mode is disabled (_useNativeMode is false)

This occurs if the ONNX constructor was used but model loading failed, leaving the detector in an inconsistent state. Silently falling back to heuristic rules without a trained model produces unreliable results and masks initialization failures.

🔎 Proposed fix

Replace the silent fallback with explicit error handling:

     private T[] ClassifyWindow(Tensor<T> melSpec, Tensor<T> audio)
     {
         if (IsOnnxMode && OnnxEncoder is not null)
         {
             return ClassifyWithModel(melSpec);
         }
         else if (_useNativeMode)
         {
             return ClassifyWithNative(melSpec);
         }
         else
         {
-            return ClassifyWithRules(melSpec, audio);
+            throw new InvalidOperationException(
+                "AudioEventDetector is in an invalid state: neither ONNX model nor native layers are available. " +
+                "Ensure the ONNX model loaded successfully or use native training mode.");
         }
     }

Alternatively, if rules-based fallback is intentional for specific scenarios, document when and why this path executes.


1143-1206: CRITICAL: Thread-safety bug—while loop processes shared state outside the lock.

The lock (_lock) at line 1150 closes after adding incoming samples to _buffer (line 1161), but the while loop (lines 1164-1204) that reads _buffer.Count, extracts windows from _buffer, processes them, updates _currentState, adds to _newEvents, and calls _buffer.RemoveRange executes outside the lock.

If FeedAudio is called from multiple threads concurrently (common in real-time audio pipelines) or if GetNewEvents/GetCurrentState are called while FeedAudio is processing, race conditions will corrupt _buffer, _newEvents, and _currentState, leading to exceptions, data loss, or incorrect event detection.

Note: A previous review flagged this exact issue as critical and marked it "Addressed in commits 50dbbdb to 5cc2364," but the bug remains in the current code.

🔎 Proposed fix

Move the closing brace of the lock to encompass the entire processing loop:

         public void FeedAudio(Tensor<T> audioChunk)
         {
             if (_disposed)
             {
                 throw new ObjectDisposedException(nameof(StreamingEventDetectionSession));
             }
 
             lock (_lock)
             {
                 if (_disposed)
                 {
                     throw new ObjectDisposedException(nameof(StreamingEventDetectionSession));
                 }
 
                 // Add to buffer
                 for (int i = 0; i < audioChunk.Length; i++)
                 {
                     _buffer.Add(audioChunk[i]);
                 }
 
-            // Process complete windows
-            while (_buffer.Count >= _windowSamples)
-            {
+                // Process complete windows
+                while (_buffer.Count >= _windowSamples)
+                {
                     // Extract window
                     var window = new Tensor<T>([_windowSamples]);
                     for (int i = 0; i < _windowSamples; i++)
                     {
                         window[i] = _buffer[i];
                     }
 
                     // Process window
                     var melSpec = _detector._melSpectrogram?.Forward(window) ??
                         throw new InvalidOperationException("MelSpectrogram not initialized.");
                     var scores = _detector.ClassifyWindow(melSpec, window);
 
                     // Update state and check for events
                     double thresholdValue = _detector.NumOps.ToDouble(_threshold);
+                    AudioEvent<T>? evtToRaise = null;
                     for (int i = 0; i < scores.Length && i < _detector.ClassLabels.Count; i++)
                     {
                         _currentState[_detector.ClassLabels[i]] = scores[i];
 
                         if (_detector.NumOps.ToDouble(scores[i]) >= thresholdValue)
                         {
                             var evt = new AudioEvent<T>
                             {
                                 EventType = _detector.ClassLabels[i],
                                 Confidence = scores[i],
                                 StartTime = _processedTime,
                                 EndTime = _processedTime + _detector._options.WindowSize,
                                 PeakTime = _processedTime + _detector._options.WindowSize / 2
                             };
 
                             _newEvents.Add(evt);
-                            EventDetected?.Invoke(this, evt);
+                            evtToRaise = evt;
                         }
                     }
 
                     // Advance buffer (with overlap)
                     int hopSamples = (int)(_windowSamples * (1 - _detector._options.WindowOverlap));
                     _buffer.RemoveRange(0, hopSamples);
                     _processedTime += hopSamples / (double)_sampleRate;
+
+                    // Invoke event outside lock to prevent deadlock if subscriber acquires locks
+                    if (evtToRaise is not null)
+                    {
+                        // Release lock before invoking to avoid potential deadlocks
+                        // Note: consider collecting all events and invoking after loop if multiple events per window are expected
+                    }
-                }
+                }
             }
+            
+            // If events need to be raised, do so here outside the lock
         }

Important: If multiple events can be detected per window, collect them in a local list inside the lock and invoke EventDetected for each outside the lock to avoid holding the lock during callbacks.


204-215: Dead code: CreateMfccExtractor is never called.

This method creates an MfccExtractor<T> instance but is never invoked anywhere in the class. A previous review flagged the _mfccExtractor field and this method as unused, and the field appears to have been removed, but the method remains.

🔎 Proposed fix

Remove the unused method:

-    private MfccExtractor<T> CreateMfccExtractor()
-    {
-        return new MfccExtractor<T>(new MfccOptions
-        {
-            SampleRate = _options.SampleRate,
-            NumCoefficients = 13,
-            FftSize = _options.FftSize,
-            HopLength = _options.HopLength,
-            FMin = _options.FMin,
-            FMax = _options.FMax
-        });
-    }
🧹 Nitpick comments (3)
src/Audio/Enhancement/DCCRN.cs (3)

608-639: Consider batch-optimizing the LSTM projection for better performance.

The per-timestep projection correctly addresses dimension compatibility but processes each timestep individually in nested loops. For better performance, consider reshaping to [batch*time, hidden], applying the projection once, then reshaping back to [batch, time, encoderDim].

🔎 Potential optimization approach
     // Project LSTM output back to encoder spatial dimensions
     if (_lstmProjection is not null)
     {
-        // Apply projection per timestep: [batch, time, hidden] -> [batch, time, encoderDim]
-        var projectedData = new T[batchSize * timeFrames * _encoderOutputDim];
-        for (int b = 0; b < batchSize; b++)
-        {
-            for (int t = 0; t < timeFrames; t++)
-            {
-                // Extract timestep data
-                var timestepInput = new Tensor<T>([_lstmHiddenDim]);
-                for (int h = 0; h < _lstmHiddenDim; h++)
-                {
-                    int idx = b * timeFrames * _lstmHiddenDim + t * _lstmHiddenDim + h;
-                    if (idx < reshaped.Length)
-                        timestepInput[h] = reshaped.GetFlat(idx);
-                }
-
-                // Project
-                var projected = _lstmProjection.Forward(timestepInput);
-
-                // Store result
-                for (int e = 0; e < _encoderOutputDim && e < projected.Length; e++)
-                {
-                    int outIdx = b * timeFrames * _encoderOutputDim + t * _encoderOutputDim + e;
-                    if (outIdx < projectedData.Length)
-                        projectedData[outIdx] = projected.GetFlat(e);
-                }
-            }
-        }
-        reshaped = new Tensor<T>(projectedData, [batchSize, timeFrames, _encoderOutputDim]);
+        // Reshape to [batch*time, hidden] for batch projection
+        var flatInput = reshaped.Reshape([batchSize * timeFrames, _lstmHiddenDim]);
+        var flatProjected = _lstmProjection.Forward(flatInput);
+        reshaped = flatProjected.Reshape([batchSize, timeFrames, _encoderOutputDim]);
     }

822-860: Consider making spatial dimension mismatch handling more explicit.

The current implementation warns via Debug.WriteLine when spatial dimensions don't match, then uses Math.Min to avoid crashes. While this prevents failures, the truncation behavior may hide architectural issues during development.

For better debuggability, consider one of:

  1. Make the warning more visible (e.g., using a logging framework if available)
  2. Add a strict validation mode that throws on mismatch during development
  3. Add an architectural validation pass that checks dimension compatibility before the first forward pass

707-779: Consider using FFT library for better STFT performance.

The current implementation uses manual DFT computation (O(N²) per frame), which is significantly slower than FFT-based implementations (O(N log N)). While correct for a reference implementation, real-world usage would benefit from a proper FFT library.

Consider using a FFT library (e.g., MathNet.Numerics.IntegralTransforms or a native binding) for production use, especially when processing longer audio segments.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc2364 and 2f10004.

📒 Files selected for processing (2)
  • src/Audio/Classification/AudioEventDetector.cs
  • src/Audio/Enhancement/DCCRN.cs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-18T08:49:25.295Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningMask.cs:1-102
Timestamp: 2025-12-18T08:49:25.295Z
Learning: In the AiDotNet repository, the project-level global using includes AiDotNet.Tensors.LinearAlgebra via AiDotNet.csproj. Therefore, Vector<T>, Matrix<T>, and Tensor<T> are available without per-file using directives. Do not flag missing using directives for these types in any C# files within this project. Apply this guideline broadly to all C# files (not just a single file) to avoid false positives. If a file uses a type from a different namespace not covered by the global using, flag as usual.

Applied to files:

  • src/Audio/Enhancement/DCCRN.cs
  • src/Audio/Classification/AudioEventDetector.cs
📚 Learning: 2025-12-18T08:49:53.103Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningStrategy.cs:1-4
Timestamp: 2025-12-18T08:49:53.103Z
Learning: In this repository, global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*) and common system types. When reviewing C# files, assume these global usings are in effect; avoid adding duplicate using statements for these namespaces and for types like Vector<T>, Matrix<T>, Tensor<T>, etc. If a type is not found, verify the global usings or consider adding a file-scoped using if needed. Prefer relying on global usings to reduce boilerplate.

Applied to files:

  • src/Audio/Enhancement/DCCRN.cs
  • src/Audio/Classification/AudioEventDetector.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CodeQL Analysis
  • GitHub Check: Build (Windows)
🔇 Additional comments (6)
src/Audio/Enhancement/DCCRN.cs (6)

283-327: LGTM! Parameter validation is thorough.

The constructor now properly validates all critical architecture parameters before initialization, including power-of-2 check for FFT size and valid hop size range. This prevents runtime errors from invalid configurations.


331-411: LGTM! Layer initialization correctly implements the DCCRN architecture.

The initialization properly creates:

  • Encoder stages with Conv + BatchNorm (2 layers each)
  • LSTM layers with appropriate input/output dimensions
  • LSTM projection layer to map back to encoder spatial dimensions (addresses past dimension mismatch issue)
  • Decoder stages with symmetric structure
  • Proper mask estimation layer

578-592: LGTM! Encoder forward pass correctly processes layer pairs.

The loop correctly iterates by 2 to process Conv + BatchNorm pairs, matching the initialization structure. Skip connections are properly cached for decoder use.


644-669: LGTM! Decoder iteration logic correctly matches initialization.

The condition (i > 0) ? 2 : 1 properly processes:

  • 2 layers per stage for iterations i = numStages-1 down to 1
  • 1 layer for the final iteration when i = 0

This matches the initialization pattern where the first (numStages-1) stages have 2 layers each, and the last stage has 1 layer. The decoderLayerIdx correctly consumes layers in the order they were added.


888-926: LGTM! Serialization correctly persists and restores model configuration.

The serialization methods properly:

  • Write all architecture parameters and config to binary format
  • Restore configuration on deserialization
  • Re-initialize native layers to match the restored config (line 922-925)

The approach correctly restores a model to its saved state, including architecture parameters.


1-960: Excellent implementation of dual-mode DCCRN with comprehensive functionality.

This implementation successfully provides:

  • Dual-mode operation (ONNX inference + native training)
  • Proper parameter validation and error handling
  • Complete forward and backward passes for training
  • Serialization support for model persistence
  • Clear documentation with beginner-friendly examples

The architecture correctly implements the DCCRN paper's design with encoder-LSTM-decoder structure, skip connections, and complex mask estimation. All critical issues from past reviews have been properly addressed.

ooples and others added 2 commits December 30, 2025 11:51
- fix audioldm/audiogen fragile layer indexing using layer boundary fields
- fix audiogen o(n^2) tensor copy overhead with pre-allocation
- remove null-forgiving operators and use proper null checks
- upgrade fftsharp from 2.1.0 to 2.2.0
- add documentation for classifywithrules fallback behavior
- improve audioldm tokenizer compatibility documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Security fix: use cryptographically secure random via RandomHelper
instead of directly instantiating System.Random.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (9)
src/Audio/AudioLDM/AudioLDMModel.cs (3)

254-258: Tokenizer compatibility concern is properly addressed.

The previous review flagged potential CLAP/FlanT5 tokenizer incompatibility. The updated comments (lines 254-257) now clearly document that:

  • Native training mode uses T5-style tokenizer (appropriate for training from scratch)
  • ONNX inference mode requires the user to provide the matching tokenizer via constructor parameter (line 150)

This is the correct design. The ONNX constructor enforces tokenizer provision (line 175-176) and documents the requirement (lines 136-137, 295-299).


1377-1390: Deserialization properly restores configuration values.

The previous review flagged that deserialized values were being discarded. The code now correctly assigns the read values to _options properties (lines 1381-1384). The only discarded value is useNativeMode (line 1379), which is appropriate because it's a readonly field set in the constructor. The comments clearly explain this design decision.


307-317: Layer boundaries still use fragile fraction-based logic.

While the code now uses boundary fields (lines 83-89) instead of inline fractions, the boundaries are still calculated using simple division (lines 312-317):

_clapLayerEnd = totalLayers / 3;
_unetLayerStart = _clapLayerEnd;
_unetLayerEnd = 2 * totalLayers / 3;

This breaks when users provide custom layer configurations that don't follow the exact 1/3, 1/3, 1/3 split. ValidateLayerConfiguration only checks that at least 3 layers exist, but doesn't validate the architecture structure.

Recommended approach:

  • Add explicit boundary markers to NeuralNetworkArchitecture<T> (e.g., ClapLayerEnd, UNetLayerEnd)
  • When custom layers are provided, require these markers to be set
  • Update validation to check markers exist and are in valid order
  • Use markers for routing instead of fractions

Also applies to: 83-89, 323-332, 629-640, 653-659, 675-680, 697-703

src/Audio/AudioGen/AudioGenModel.cs (4)

337-354: Parameter validation is comprehensive.

The constructors now properly validate all configuration parameters including temperature, topK, topP, guidanceScale, channels, sampleRate, durationSeconds, and maxDurationSeconds. The validation is consistent between both constructors and throws appropriate exceptions with clear messages.

Also applies to: 383-416

Also applies to: 479-497


126-131: Thread-safety properly implemented for random generation.

The previous review flagged _random as not thread-safe for concurrent generation. The code now includes _randomLock (line 131) and uses it to protect access to _random.Next() (lines 643-646). This ensures safe concurrent calls to generation methods.

Also applies to: 404-406, 528-530

Also applies to: 643-646


1017-1070: Pre-allocation eliminates O(n²) overhead.

The previous review flagged O(n²) tensor copy overhead during autoregressive generation. The code now:

  1. Pre-allocates currentTokens buffer to full capacity (lines 1017-1020)
  2. Tracks length with currentLength variable (line 1020)
  3. Creates lightweight views for each forward pass (lines 1031-1038)
  4. Directly assigns new tokens without reallocation (lines 1066-1069)

This reduces complexity from O(n²) to O(n), significantly improving generation speed for longer durations.

Also applies to: 1113-1173


1319-1464: Native decoder implementation is complete and functional.

The previous review flagged the native decoder as a critical placeholder that produced silence. The current implementation (lines 1329-1464) provides a complete EnCodec-style decoder with:

  1. Codebook aggregation (lines 1346-1364): Converts discrete codes to continuous embeddings with proper residual VQ weighting (line 1362: weight = 1.0 / Math.Pow(2, cb))
  2. Upsampling (lines 1369-1419): Interpolates between tokens using both linear and cosine curves for smoother output
  3. Click prevention (lines 1422-1461): Fades out remaining samples to avoid artifacts
  4. Multi-channel support (lines 1407-1418): Handles both mono and stereo output

The implementation is well-documented and produces actual audio instead of silence. This fully resolves the critical issue.

src/Audio/Classification/AudioEventDetector.cs (1)

204-215: Dead code: CreateMfccExtractor is never called.

The CreateMfccExtractor method is defined but never invoked anywhere in the class. This appears to be leftover code from when the _mfccExtractor field existed (which was removed in earlier commits).

🔎 Proposed fix
-    private MfccExtractor<T> CreateMfccExtractor()
-    {
-        return new MfccExtractor<T>(new MfccOptions
-        {
-            SampleRate = _options.SampleRate,
-            NumCoefficients = 13,
-            FftSize = _options.FftSize,
-            HopLength = _options.HopLength,
-            FMin = _options.FMin,
-            FMax = _options.FMax
-        });
-    }
-
src/Audio/Classification/SceneClassifier.cs (1)

603-617: FeatureCount still doesn't reflect actual input dimensions.

The past review noted that FeatureCount should represent input features, not output classes. The fix changed from ClassLabels.Count to _options.NumMfccs, but the actual input feature count is NumMfccs * 3 + 7 + 6 as computed in CreateMinimalArchitecture (line 190).

🔎 Proposed fix
     public override ModelMetadata<T> GetModelMetadata()
     {
+        int inputFeatures = _options.NumMfccs * 3 + 7 + 6;
         var metadata = new ModelMetadata<T>
         {
             Name = _useNativeMode ? "SceneClassifier-Native" : "SceneClassifier-ONNX",
             Description = "Acoustic scene classification model (DCASE-style)",
             ModelType = ModelType.NeuralNetwork,
-            FeatureCount = _options.NumMfccs,  // Input feature count, not output class count
+            FeatureCount = inputFeatures,
             Complexity = 1
         };
🧹 Nitpick comments (2)
src/Audio/Classification/SceneClassifier.cs (2)

114-116: Consider deferring optimizer creation in ONNX mode.

The _optimizer is instantiated in both ONNX constructors (lines 116, 167) but Train() throws InvalidOperationException when not in native mode. This wastes memory for inference-only instances.

🔎 Proposed fix

Use lazy initialization or defer creation to the training path:

-    private readonly IGradientBasedOptimizer<T, Tensor<T>, Tensor<T>> _optimizer;
+    private IGradientBasedOptimizer<T, Tensor<T>, Tensor<T>>? _optimizer;

Then in Train():

     public override void Train(Tensor<T> input, Tensor<T> expected)
     {
         if (!_useNativeMode)
         {
             throw new InvalidOperationException(...);
         }
 
+        _optimizer ??= new AdamWOptimizer<T, Tensor<T>, Tensor<T>>(this);
         SetTrainingMode(true);

687-746: Bounds checks are redundant after the early return guard.

Lines 716, 726, and 738 check t < mfccs.Shape[0] && c < mfccs.Shape[1], but this is always true after the early return at line 688 ensures numFrames > 0 && numCoeffs > 0. These can be safely removed to simplify the code.

🔎 Proposed simplification
         for (int c = 0; c < numCoeffs; c++)
         {
             double sum = 0;
             for (int t = 0; t < numFrames; t++)
             {
-                if (t < mfccs.Shape[0] && c < mfccs.Shape[1])
-                {
-                    sum += NumOps.ToDouble(mfccs[t, c]);
-                }
+                sum += NumOps.ToDouble(mfccs[t, c]);
             }
             mfccMean[c] = sum / numFrames;

             double sumSq = 0;
             for (int t = 0; t < numFrames; t++)
             {
-                if (t < mfccs.Shape[0] && c < mfccs.Shape[1])
-                {
-                    double diff = NumOps.ToDouble(mfccs[t, c]) - mfccMean[c];
-                    sumSq += diff * diff;
-                }
+                double diff = NumOps.ToDouble(mfccs[t, c]) - mfccMean[c];
+                sumSq += diff * diff;
             }
             mfccStd[c] = Math.Sqrt(sumSq / numFrames);

             // Delta (first derivative)
             double sumDelta = 0;
-            for (int t = 1; t < numFrames && t < mfccs.Shape[0]; t++)
+            for (int t = 1; t < numFrames; t++)
             {
-                if (c < mfccs.Shape[1])
-                {
-                    double prev = NumOps.ToDouble(mfccs[t - 1, c]);
-                    double curr = NumOps.ToDouble(mfccs[t, c]);
-                    sumDelta += Math.Abs(curr - prev);
-                }
+                double prev = NumOps.ToDouble(mfccs[t - 1, c]);
+                double curr = NumOps.ToDouble(mfccs[t, c]);
+                sumDelta += Math.Abs(curr - prev);
             }
             mfccDelta[c] = numFrames > 1 ? sumDelta / (numFrames - 1) : 0.0;
         }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f10004 and d06e6e8.

📒 Files selected for processing (5)
  • src/AiDotNet.csproj
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/AudioLDM/AudioLDMModel.cs
  • src/Audio/Classification/AudioEventDetector.cs
  • src/Audio/Classification/SceneClassifier.cs
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-18T08:49:30.125Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningMask.cs:1-102
Timestamp: 2025-12-18T08:49:30.125Z
Learning: The AiDotNet project uses project-level global usings (configured in AiDotNet.csproj with `<Using Include=AiDotNet.Tensors.LinearAlgebra />`), making Vector<T>, Matrix<T>, and Tensor<T> available in all files without explicit per-file using directives. Do not flag missing using directives for these types in this project.

Applied to files:

  • src/AiDotNet.csproj
📚 Learning: 2025-12-18T08:50:00.720Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningStrategy.cs:1-4
Timestamp: 2025-12-18T08:50:00.720Z
Learning: The AiDotNet project uses global using directives in src/AiDotNet.csproj via <Using Include="..." /> for AiDotNet.Tensors.LinearAlgebra, AiDotNet.Tensors.Engines, AiDotNet.Tensors.Interfaces, AiDotNet.Tensors.NumericOperations, AiDotNet.Tensors.Helpers, AiDotNet.Autodiff, System.Text, and AiDotNet.Helpers. Types like Vector<T>, Matrix<T>, Tensor<T>, and related linear algebra types are available project-wide without per-file using statements.

Applied to files:

  • src/AiDotNet.csproj
📚 Learning: 2025-12-18T08:49:25.295Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningMask.cs:1-102
Timestamp: 2025-12-18T08:49:25.295Z
Learning: In the AiDotNet repository, the project-level global using includes AiDotNet.Tensors.LinearAlgebra via AiDotNet.csproj. Therefore, Vector<T>, Matrix<T>, and Tensor<T> are available without per-file using directives. Do not flag missing using directives for these types in any C# files within this project. Apply this guideline broadly to all C# files (not just a single file) to avoid false positives. If a file uses a type from a different namespace not covered by the global using, flag as usual.

Applied to files:

  • src/Audio/AudioLDM/AudioLDMModel.cs
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/Classification/SceneClassifier.cs
  • src/Audio/Classification/AudioEventDetector.cs
📚 Learning: 2025-12-18T08:49:53.103Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningStrategy.cs:1-4
Timestamp: 2025-12-18T08:49:53.103Z
Learning: In this repository, global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*) and common system types. When reviewing C# files, assume these global usings are in effect; avoid adding duplicate using statements for these namespaces and for types like Vector<T>, Matrix<T>, Tensor<T>, etc. If a type is not found, verify the global usings or consider adding a file-scoped using if needed. Prefer relying on global usings to reduce boilerplate.

Applied to files:

  • src/Audio/AudioLDM/AudioLDMModel.cs
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/Classification/SceneClassifier.cs
  • src/Audio/Classification/AudioEventDetector.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build (Windows)
  • GitHub Check: CodeQL Analysis
🔇 Additional comments (10)
src/AiDotNet.csproj (1)

66-66: Excellent—upgrade to FftSharp 2.2.0 implemented.

This addresses the previous recommendation to upgrade from 2.1.0 to 2.2.0. The successful build with zero warnings confirms compatibility with both net8.0 and net471 targets, and the dependency appropriately supports the FFT-based audio processing features introduced in this PR.

src/Audio/Classification/SceneClassifier.cs (9)

44-44: LGTM!

Class declaration follows the interface → base class → concrete class pattern as required by the project architecture. XML documentation is comprehensive with examples for both ONNX and training modes.


46-102: Well-structured static scene data.

The StandardScenes array and SceneCategories dictionary provide a comprehensive DCASE-style taxonomy. The readonly modifiers are appropriately applied, and _useNativeMode correctly lacks readonly to support serialization round-trips.


224-244: LGTM!

The async factory method follows the established pattern for model download and initialization. Mutating options.ModelPath after download allows callers to inspect the resolved path.


369-392: Good parameter validation addresses infinite loop risk.

The validation at lines 374-390 and the Math.Max(1, ...) guard at line 392 properly prevent the infinite loop scenario flagged in the past review.


484-504: LGTM!

The predict method correctly prioritizes ONNX inference, falls back to native layers, and finally to rule-based classification. The fallback cascade is appropriate for a model that may not always have weights loaded.


622-661: LGTM!

Serialization and deserialization are symmetric, with all options and class labels properly persisted. The _useNativeMode field is correctly restored (addressing the past review concern).


932-985: Division-by-zero guards properly implemented.

ComputeRmsEnergy, ComputeZeroCrossingRate, and ComputeEnergyVariance all include appropriate guards for empty input, addressing the past review concerns. The other spectral methods also properly guard their divisions.


1020-1040: LGTM!

The rule-based fallback appropriately returns a uniform distribution when no neural network is available. The comment at line 1027 clearly documents this is a fallback mechanism.


1066-1077: Check if feature extractors need disposal.

OnnxEncoder is correctly disposed, but verify whether _melSpectrogram and _mfccExtractor implement IDisposable. If they hold unmanaged resources, they should also be disposed in the Dispose method.

Comment thread src/Audio/AudioGen/AudioGenModel.cs Outdated
Comment thread src/Audio/Classification/AudioEventDetector.cs Outdated
ooples and others added 2 commits December 30, 2025 12:06
- audiogen: use layer type detection for boundaries instead of fractions
- audiogen: calculate exact boundaries for default layers based on structure
- audioeventdetector: fix potential deadlock by invoking events outside lock

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add parameterless constructors to SpeakerEmbeddingExtractor and SpeakerVerifier
- Add SpeakerEmbeddingExtractor(SpeakerEmbeddingOptions) constructor
- Add Turns and Duration properties to DiarizationResult<T> for legacy API
- Add AllProbabilities, TopPredictions, Features to GenreClassificationResult<T>
- Add AllProbabilities, TopPredictions, Features to SceneClassificationResult<T>
- Add EventLabels property to AudioEventDetector
- Add Scenes and ClassifyCategory to SceneClassifier
- Implement IEnumerable<AudioEvent<T>> on AudioEventResult<T>
- Fix test: use EventType instead of Label, Count instead of Length

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/Audio/Classification/SceneClassifier.cs (1)

623-637: Incorrect FeatureCount in model metadata (regression).

Line 630 sets FeatureCount = _options.NumMfccs, but this is only one component of the input features. The correct input feature count is computed at line 190 as numMfccs * 3 + 7 + 6 (MFCC mean/std/delta + 7 spectral features + 6 band energies), which matches the feature tensor construction at line 800.

This was previously flagged and appears to be a regression. The metadata should accurately reflect the model's input dimensions for proper introspection and validation.

🔎 Proposed fix
     public override ModelMetadata<T> GetModelMetadata()
     {
+        int inputFeatures = _options.NumMfccs * 3 + 7 + 6;
         var metadata = new ModelMetadata<T>
         {
             Name = _useNativeMode ? "SceneClassifier-Native" : "SceneClassifier-ONNX",
             Description = "Acoustic scene classification model (DCASE-style)",
             ModelType = ModelType.NeuralNetwork,
-            FeatureCount = _options.NumMfccs,  // Input feature count, not output class count
+            FeatureCount = inputFeatures,
             Complexity = 1
         };
src/Audio/Classification/AudioEventDetector.cs (1)

204-215: Remove unused CreateMfccExtractor method.

This method is never called and appears to be dead code left over after the _mfccExtractor field was removed in previous commits.

🔎 Proposed fix
-    private MfccExtractor<T> CreateMfccExtractor()
-    {
-        return new MfccExtractor<T>(new MfccOptions
-        {
-            SampleRate = _options.SampleRate,
-            NumCoefficients = 13,
-            FftSize = _options.FftSize,
-            HopLength = _options.HopLength,
-            FMin = _options.FMin,
-            FMax = _options.FMax
-        });
-    }

Also remove orphaned comments at lines 136 and 174:

-        // Create feature extractors
-
         // Optimizer not used in ONNX mode but required by interface
-        // Create feature extractors
-
         InitializeLayers();
🧹 Nitpick comments (5)
src/Audio/Classification/SceneClassifier.cs (1)

731-766: Optional: Remove redundant bounds checks after early return guard.

The bounds checks at lines 736-739, 746-750, and 756-764 verify t < mfccs.Shape[0] and c < mfccs.Shape[1], but these are always true because:

  1. Lines 708-725 provide an early return when numFrames == 0 or numCoeffs == 0
  2. Lines 704-705 set numFrames = mfccs.Shape[0] and numCoeffs = mfccs.Shape[1]
  3. The loops at lines 731 and 734 use these exact bounds

These redundant checks were likely added defensively, but they add visual noise without providing safety. The early return guard at lines 708-725 (correctly addressing past review feedback) already ensures the loops are safe.

🔎 Simplified code (remove redundant checks)
         for (int c = 0; c < numCoeffs; c++)
         {
             double sum = 0;
             for (int t = 0; t < numFrames; t++)
             {
-                if (t < mfccs.Shape[0] && c < mfccs.Shape[1])
-                {
-                    sum += NumOps.ToDouble(mfccs[t, c]);
-                }
+                sum += NumOps.ToDouble(mfccs[t, c]);
             }
             mfccMean[c] = sum / numFrames;
 
             double sumSq = 0;
             for (int t = 0; t < numFrames; t++)
             {
-                if (t < mfccs.Shape[0] && c < mfccs.Shape[1])
-                {
-                    double diff = NumOps.ToDouble(mfccs[t, c]) - mfccMean[c];
-                    sumSq += diff * diff;
-                }
+                double diff = NumOps.ToDouble(mfccs[t, c]) - mfccMean[c];
+                sumSq += diff * diff;
             }
             mfccStd[c] = Math.Sqrt(sumSq / numFrames);
 
             // Delta (first derivative)
             double sumDelta = 0;
             for (int t = 1; t < numFrames && t < mfccs.Shape[0]; t++)
+            for (int t = 1; t < numFrames; t++)
             {
-                if (c < mfccs.Shape[1])
-                {
-                    double prev = NumOps.ToDouble(mfccs[t - 1, c]);
-                    double curr = NumOps.ToDouble(mfccs[t, c]);
-                    sumDelta += Math.Abs(curr - prev);
-                }
+                double prev = NumOps.ToDouble(mfccs[t - 1, c]);
+                double curr = NumOps.ToDouble(mfccs[t, c]);
+                sumDelta += Math.Abs(curr - prev);
             }
             mfccDelta[c] = numFrames > 1 ? sumDelta / (numFrames - 1) : 0.0;
         }
src/Audio/Classification/AudioEventDetector.cs (2)

558-559: Consider defensive null check for LossFunction.

The Train method uses LossFunction.CalculateLoss and LossFunction.CalculateDerivative without verifying that LossFunction is not null. While the base class likely initializes this property, adding a defensive check would prevent potential NullReferenceException if the model is constructed in an unexpected state.

🔎 Suggested approach
         // Forward pass
         var output = Predict(input);
 
+        if (LossFunction is null)
+        {
+            throw new InvalidOperationException("LossFunction not initialized.");
+        }
+
         // Compute loss and gradients
         var loss = LossFunction.CalculateLoss(output.ToVector(), expected.ToVector());
         var gradient = LossFunction.CalculateDerivative(output.ToVector(), expected.ToVector());

829-867: Well-documented fallback, but consider explicit error.

The extensive XML documentation (lines 829-846) now clearly explains when the rule-based fallback is used. However, given that this path is only reached during "incomplete initialization" (effectively an error state), consider throwing an InvalidOperationException instead of silently falling back to heuristics, which would make debugging easier.

💡 Alternative: Throw instead of silent fallback
     else if (_useNativeMode)
     {
         return ClassifyWithNative(melSpec);
     }
     else
     {
-        return ClassifyWithRules(melSpec, audio);
+        throw new InvalidOperationException(
+            "Detector is in an invalid state: neither ONNX mode nor native mode is active. " +
+            "This indicates incomplete initialization. Please ensure the model is properly constructed.");
     }

This change would require updating tests that rely on the rule-based fallback. If the fallback is intentionally used for scenarios beyond error recovery, the current implementation with comprehensive documentation is acceptable.

src/Audio/AudioGen/AudioGenModel.cs (2)

607-642: Layer boundary fallback still uses fraction-based heuristic.

Lines 639-640 fall back to layers.Count / 3 when no TransformerDecoderLayer is detected. This heuristic may fail for custom architectures that don't follow the expected structure.

Consider either:

  1. Requiring users to specify explicit boundary indices in the Architecture configuration when using custom layers
  2. Throwing an exception instead of falling back to a potentially incorrect guess
  3. Adding a warning log when the fallback is used

The smart detection (lines 617-627) is good, but the fallback may produce silent failures.


1047-1116: O(n²) tensor copy overhead remains in autoregressive loop.

Despite pre-allocating currentTokens (lines 1060-1063), lines 1074-1081 still create a new inputTokens tensor and copy data on every iteration. For long durations (e.g., 30 seconds ≈ 1500 tokens), this results in ~1.1 million copy operations.

The pre-allocation avoids memory reallocation but doesn't eliminate the copy overhead. To truly achieve O(n) complexity, the framework would need to support zero-copy tensor slicing/views.

Current impact: Noticeable slowdown for durations > 10 seconds, but acceptable for typical use cases (5-10 seconds).

🔎 Potential optimization if tensor views become available

If the Tensor API supports slicing without copy:

-    var inputTokens = new Tensor<T>([1, _numCodebooks, currentLength]);
-    for (int cb = 0; cb < _numCodebooks; cb++)
-    {
-        for (int i = 0; i < currentLength; i++)
-        {
-            inputTokens[0, cb, i] = currentTokens[0, cb, i];
-        }
-    }
+    // Zero-copy slice: currentTokens[0.., 0.., 0..currentLength]
+    var inputTokens = currentTokens.Slice([0, 0, 0], [1, _numCodebooks, currentLength]);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d06e6e8 and 4470684.

📒 Files selected for processing (11)
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/Classification/AudioEventDetector.cs
  • src/Audio/Classification/SceneClassifier.cs
  • src/Audio/Speaker/SpeakerEmbeddingExtractor.cs
  • src/Audio/Speaker/SpeakerVerifier.cs
  • src/Interfaces/IAudioEventDetector.cs
  • src/Interfaces/IGenreClassifier.cs
  • src/Interfaces/ISceneClassifier.cs
  • src/Interfaces/ISpeakerDiarizer.cs
  • src/ReinforcementLearning/Environments/DeterministicBanditEnvironment.cs
  • tests/AiDotNet.Tests/Audio/Classification/ClassificationTests.cs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-18T08:49:25.295Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningMask.cs:1-102
Timestamp: 2025-12-18T08:49:25.295Z
Learning: In the AiDotNet repository, the project-level global using includes AiDotNet.Tensors.LinearAlgebra via AiDotNet.csproj. Therefore, Vector<T>, Matrix<T>, and Tensor<T> are available without per-file using directives. Do not flag missing using directives for these types in any C# files within this project. Apply this guideline broadly to all C# files (not just a single file) to avoid false positives. If a file uses a type from a different namespace not covered by the global using, flag as usual.

Applied to files:

  • src/Audio/Classification/SceneClassifier.cs
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/Classification/AudioEventDetector.cs
📚 Learning: 2025-12-18T08:49:53.103Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningStrategy.cs:1-4
Timestamp: 2025-12-18T08:49:53.103Z
Learning: In this repository, global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*) and common system types. When reviewing C# files, assume these global usings are in effect; avoid adding duplicate using statements for these namespaces and for types like Vector<T>, Matrix<T>, Tensor<T>, etc. If a type is not found, verify the global usings or consider adding a file-scoped using if needed. Prefer relying on global usings to reduce boilerplate.

Applied to files:

  • src/Audio/Classification/SceneClassifier.cs
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/Classification/AudioEventDetector.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CodeQL Analysis
  • GitHub Check: Build (Windows)
🔇 Additional comments (10)
src/Audio/Classification/AudioEventDetector.cs (3)

707-756: LGTM: Comprehensive window handling.

The windowing logic now properly handles edge cases including partial windows, very short audio, and padding. The 10% threshold (line 733) ensures very small tail segments are discarded while significant audio is preserved and zero-padded for consistent processing.


684-692: LGTM: MelSpectrogram properly reinitialized after deserialization.

The method now correctly reinitializes _melSpectrogram with the deserialized options, ensuring feature extraction matches the restored configuration. This addresses the issue from previous reviews.


1140-1288: LGTM: Excellent thread-safe streaming session implementation.

The StreamingEventDetectionSession demonstrates proper concurrent programming practices:

  • All shared state (_buffer, _newEvents, _currentState) is protected by _lock
  • Events are collected inside the lock but invoked outside (lines 1183-1185, 1247-1254), preventing potential deadlocks
  • volatile bool _disposed (line 1150) ensures memory visibility across threads
  • Double-checked disposal pattern (lines 1189-1192) prevents race conditions
  • All public methods properly synchronize access

This implementation addresses all thread-safety concerns from previous reviews.

src/Audio/AudioGen/AudioGenModel.cs (7)

1-61: AI summary lists methods not present in the code.

The AI summary claims CreateAsync and FromFiles static factory methods are present, but these methods are not implemented in this file. This creates confusion about the actual public API surface.

Additionally, past review comments reference a FromFiles method with resource leak issues, but that method no longer exists in the code, making those comments obsolete.


125-131: Thread-safe random generation correctly implemented.

The _randomLock object protects access to the shared _random field (see line 686-689), resolving the thread-safety concern from previous reviews.


302-417: ONNX constructor correctly handles resource cleanup and validation.

The try-catch block (lines 383-416) ensures that partially-loaded ONNX models are disposed if instantiation fails, preventing resource leaks. Parameter validation (lines 336-354) is comprehensive and throws clear exceptions for invalid ranges.


1029-1045: Tokenization correctly uses ITokenizer interface.

The method properly delegates to _tokenizer.Encode() rather than implementing manual character-based tokenization, making the past review comment about digit handling obsolete.


1522-1535: IDisposable pattern correctly implemented.

The disposal logic properly disposes ONNX models and avoids double-disposal of _audioDecoder (noted in line 1530 comment, which is stored in base class at line 394).


935-955: Deserialization discards all values without restoring model state.

Lines 937-954 read 17 serialized values but immediately discard them via underscore assignments. The comment states "validation done in CreateNewInstance," but CreateNewInstance (lines 960-984) uses current instance fields (e.g., _modelSize at line 964), not deserialized values.

Since the configuration fields are readonly, they cannot be updated during deserialization. This raises a concern: either the design intentionally separates serialization concerns (weights vs. configuration), or the deserialized values should be used to reconstruct the model properly.

Verify whether this matches the base class serialization contract and how other neural network models in the codebase handle this pattern.


524-524: Verify LanguageModelBackbone.FlanT5 availability and factory implementation.

Line 524 creates a default tokenizer using LanguageModelBackbone.FlanT5. Ensure that:

  1. The LanguageModelBackbone enum and FlanT5 value exist in the codebase
  2. LanguageModelTokenizerFactory.CreateForBackbone is implemented and handles this enum value
  3. The resulting tokenizer is compatible with AudioGen's text encoder expectations

Comment thread src/Audio/AudioGen/AudioGenModel.cs
Comment thread src/Audio/Classification/AudioEventDetector.cs
- Add parameter validation for numCodebooks, codebookSize, and
  maxTextLength in AudioGenModel constructor
- Fix AudioEventDetector serialization to preserve ONNX inference mode
- Make _useNativeMode non-readonly to allow restoration during deserialize
- Serialize/deserialize model path and mode flag for proper round-trip

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/Audio/Classification/AudioEventDetector.cs (1)

204-215: Dead code: CreateMfccExtractor is never called.

The CreateMfccExtractor method is defined but never invoked anywhere in the class. This was likely leftover after the _mfccExtractor field was removed in previous fixes.

🔎 Proposed fix
-    private MfccExtractor<T> CreateMfccExtractor()
-    {
-        return new MfccExtractor<T>(new MfccOptions
-        {
-            SampleRate = _options.SampleRate,
-            NumCoefficients = 13,
-            FftSize = _options.FftSize,
-            HopLength = _options.HopLength,
-            FMin = _options.FMin,
-            FMax = _options.FMax
-        });
-    }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4470684 and 0d14e87.

📒 Files selected for processing (2)
  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/Classification/AudioEventDetector.cs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-18T08:49:25.295Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningMask.cs:1-102
Timestamp: 2025-12-18T08:49:25.295Z
Learning: In the AiDotNet repository, the project-level global using includes AiDotNet.Tensors.LinearAlgebra via AiDotNet.csproj. Therefore, Vector<T>, Matrix<T>, and Tensor<T> are available without per-file using directives. Do not flag missing using directives for these types in any C# files within this project. Apply this guideline broadly to all C# files (not just a single file) to avoid false positives. If a file uses a type from a different namespace not covered by the global using, flag as usual.

Applied to files:

  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/Classification/AudioEventDetector.cs
📚 Learning: 2025-12-18T08:49:53.103Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningStrategy.cs:1-4
Timestamp: 2025-12-18T08:49:53.103Z
Learning: In this repository, global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*) and common system types. When reviewing C# files, assume these global usings are in effect; avoid adding duplicate using statements for these namespaces and for types like Vector<T>, Matrix<T>, Tensor<T>, etc. If a type is not found, verify the global usings or consider adding a file-scoped using if needed. Prefer relying on global usings to reduce boilerplate.

Applied to files:

  • src/Audio/AudioGen/AudioGenModel.cs
  • src/Audio/Classification/AudioEventDetector.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build (Windows)
  • GitHub Check: CodeQL Analysis
🔇 Additional comments (14)
src/Audio/Classification/AudioEventDetector.cs (4)

267-417: LGTM!

The detection API methods are well-structured and handle both synchronous and asynchronous scenarios correctly. Proper disposal checks and null reference guards are in place.


420-492: LGTM!

Legacy API methods provide clean backward compatibility with proper conversion between generic and non-generic types.


494-720: LGTM!

Core implementation properly handles ONNX and native modes. Serialization/deserialization correctly preserves and restores mode state, including reinitialization of _melSpectrogram and OnnxEncoder when needed.


1155-1306: LGTM!

The StreamingEventDetectionSession implementation correctly addresses all previous thread-safety concerns. The locking strategy properly protects shared state while avoiding deadlocks by raising events outside the lock.

src/Audio/AudioGen/AudioGenModel.cs (10)

16-61: Excellent documentation for accessibility.

The XML documentation is comprehensive, beginner-friendly, and clearly explains the differences between TTS and AudioGen, use cases, and limitations.


123-131: Thread-safe random access properly implemented.

The addition of _randomLock and its usage in generation methods (e.g., lines 692-695) correctly addresses the thread-safety concern for concurrent access to _random.


302-417: Constructor properly validates and handles errors.

Parameter validation (lines 337-354) and exception handling with cleanup (lines 410-416) are correctly implemented, ensuring robust initialization.


419-539: Native constructor validation is complete.

All parameters including numCodebooks, codebookSize, and maxTextLength are properly validated (lines 498-503), addressing previous concerns about divide-by-zero risks.


543-660: Layer boundary detection improved.

The layer initialization now attempts to detect boundaries by identifying TransformerDecoderLayer instances (lines 625-633), with a fraction-based fallback. This is a significant improvement over purely fraction-based assumptions.


675-715: Generation logic is clean and thread-safe.

The seed generation uses proper locking (lines 692-696), and the overall generation flow (encode text → generate codes → decode audio) is clear and correct.


1053-1225: Autoregressive generation optimized properly.

The pre-allocation strategy (lines 1066-1069, 1162-1164) with view creation (lines 1079-1087, 1173-1181) eliminates the O(n²) overhead, reducing complexity from quadratic to linear.


1368-1513: Native decoder implementation is complete.

The DecodeAudioNative method now provides a functional EnCodec-style decoder with codebook aggregation, upsampling, interpolation, and smoothing—replacing the previous placeholder implementation.


1528-1541: Resource disposal implemented correctly.

The Dispose method properly releases all ONNX model resources and calls the base class disposal.


163-179: Past review comment references code not in current file.

A previous review flagged the FromFiles method for not cleaning up on partial failure. However, this method is not present in the current version of this file—it may have been moved, removed, or refactored.

Comment thread src/Audio/Classification/AudioEventDetector.cs
- Fix CS8604 null reference warning by using pattern matching for
  modelPath in deserialization
- Document the ClassifyWithRules fallback branch for edge cases when
  ONNX model file is missing after deserialization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/Audio/Classification/AudioEventDetector.cs (2)

204-215: Remove unused CreateMfccExtractor method.

The CreateMfccExtractor method is never called. Additionally, the comments at lines 135 and 174 ("Create feature extractors") suggest extractors should be initialized, but no code follows these comments.

🔎 Proposed fix
-    private MfccExtractor<T> CreateMfccExtractor()
-    {
-        return new MfccExtractor<T>(new MfccOptions
-        {
-            SampleRate = _options.SampleRate,
-            NumCoefficients = 13,
-            FftSize = _options.FftSize,
-            HopLength = _options.HopLength,
-            FMin = _options.FMin,
-            FMax = _options.FMax
-        });
-    }

Also remove or complete the "Create feature extractors" comments at lines 135 and 174.


1055-1099: Consider making event merge tolerance configurable.

Line 1073 uses a hardcoded 0.1-second tolerance to determine if events should be merged. This fixed value may not be appropriate for all window configurations. For example, if TimeResolution is 0.5 seconds, a 0.1s tolerance might be too restrictive, while for a 0.01s resolution, it might merge events that should remain separate.

💡 Suggested approaches

Option 1: Add a configurable merge tolerance to AudioEventDetectorOptions:

public class AudioEventDetectorOptions
{
    // ... existing properties ...
    public double MergeTolerance { get; set; } = 0.1;
}

Then use it in MergeEvents:

-                if (next.StartTime <= currentEvent.EndTime + 0.1)
+                if (next.StartTime <= currentEvent.EndTime + _options.MergeTolerance)

Option 2: Make it proportional to TimeResolution:

+        double mergeTolerance = TimeResolution * 0.5; // 50% of time resolution
+        
         for (int i = 1; i < sortedEvents.Count; i++)
         {
             var next = sortedEvents[i];

             // Check if events overlap or are adjacent
-            if (next.StartTime <= currentEvent.EndTime + 0.1)
+            if (next.StartTime <= currentEvent.EndTime + mergeTolerance)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d14e87 and 2d66b2d.

📒 Files selected for processing (1)
  • src/Audio/Classification/AudioEventDetector.cs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-18T08:49:25.295Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningMask.cs:1-102
Timestamp: 2025-12-18T08:49:25.295Z
Learning: In the AiDotNet repository, the project-level global using includes AiDotNet.Tensors.LinearAlgebra via AiDotNet.csproj. Therefore, Vector<T>, Matrix<T>, and Tensor<T> are available without per-file using directives. Do not flag missing using directives for these types in any C# files within this project. Apply this guideline broadly to all C# files (not just a single file) to avoid false positives. If a file uses a type from a different namespace not covered by the global using, flag as usual.

Applied to files:

  • src/Audio/Classification/AudioEventDetector.cs
📚 Learning: 2025-12-18T08:49:53.103Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningStrategy.cs:1-4
Timestamp: 2025-12-18T08:49:53.103Z
Learning: In this repository, global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*) and common system types. When reviewing C# files, assume these global usings are in effect; avoid adding duplicate using statements for these namespaces and for types like Vector<T>, Matrix<T>, Tensor<T>, etc. If a type is not found, verify the global usings or consider adding a file-scoped using if needed. Prefer relying on global usings to reduce boilerplate.

Applied to files:

  • src/Audio/Classification/AudioEventDetector.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CodeQL Analysis
  • GitHub Check: Build (Windows)

Comment thread src/Audio/Classification/AudioEventDetector.cs
Update XML documentation to accurately describe when the rule-based
fallback is invoked:
- Remove incorrect claim about legacy constructor usage
- Clarify that this path is reached after deserialization when ONNX
  model file is missing
- Add note explaining why normal constructor paths don't reach this

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/Audio/Classification/AudioEventDetector.cs (1)

204-215: Remove unused CreateMfccExtractor method.

The CreateMfccExtractor method is never called anywhere in the class. All feature extraction is handled by _melSpectrogram (initialized in constructors at lines 120-127 and 165-172). This method appears to be leftover from an earlier design.

🔎 Proposed fix
-    private MfccExtractor<T> CreateMfccExtractor()
-    {
-        return new MfccExtractor<T>(new MfccOptions
-        {
-            SampleRate = _options.SampleRate,
-            NumCoefficients = 13,
-            FftSize = _options.FftSize,
-            HopLength = _options.HopLength,
-            FMin = _options.FMin,
-            FMax = _options.FMax
-        });
-    }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d66b2d and 313db3d.

📒 Files selected for processing (1)
  • src/Audio/Classification/AudioEventDetector.cs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-18T08:49:25.295Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningMask.cs:1-102
Timestamp: 2025-12-18T08:49:25.295Z
Learning: In the AiDotNet repository, the project-level global using includes AiDotNet.Tensors.LinearAlgebra via AiDotNet.csproj. Therefore, Vector<T>, Matrix<T>, and Tensor<T> are available without per-file using directives. Do not flag missing using directives for these types in any C# files within this project. Apply this guideline broadly to all C# files (not just a single file) to avoid false positives. If a file uses a type from a different namespace not covered by the global using, flag as usual.

Applied to files:

  • src/Audio/Classification/AudioEventDetector.cs
📚 Learning: 2025-12-18T08:49:53.103Z
Learnt from: ooples
Repo: ooples/AiDotNet PR: 444
File: src/Interfaces/IPruningStrategy.cs:1-4
Timestamp: 2025-12-18T08:49:53.103Z
Learning: In this repository, global using directives are declared in AiDotNet.csproj for core namespaces (AiDotNet.Tensors.* and AiDotNet.*) and common system types. When reviewing C# files, assume these global usings are in effect; avoid adding duplicate using statements for these namespaces and for types like Vector<T>, Matrix<T>, Tensor<T>, etc. If a type is not found, verify the global usings or consider adding a file-scoped using if needed. Prefer relying on global usings to reduce boilerplate.

Applied to files:

  • src/Audio/Classification/AudioEventDetector.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build (Windows)

@ooples ooples mentioned this pull request Dec 30, 2025
5 of 9 tasks
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.5% Coverage on New Code (required ≥ 80%)
10.0% Duplication on New Code (required ≤ 3%)
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Feature work item

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ONNX Export and ONNX Runtime (CPU/DirectML) Execution

3 participants