src/third_party/teensy_audio_notefreq/README.md
Pulled from: https://github.com/PaulStoffregen/Audio/tree/master (analyze_notefreq.cpp and analyze_notefreq.h)
fl::third_partyThe Teensy Audio Library's analyze_notefreq component provides real-time fundamental frequency detection using the Yin algorithm, designed specifically for musical note detection and tuning applications. This component is MIT licensed and authored by Colin Duffy (2015).
AudioAnalyzeNoteFrequency// Initialize frequency detection with threshold and buffer configuration
bool begin(float threshold = 0.15);
// Set detection uncertainty threshold (0.0 to 1.0)
void threshold(float p);
// Check if a valid frequency has been detected
bool available(void);
// Get detected frequency in Hertz
float read(void);
// Get confidence of frequency detection (0.0 to 1.0)
float probability(void);
Buffer Size: Default 24 audio blocks
Threshold: Detection uncertainty parameter
The Yin algorithm is a well-established pitch detection algorithm that:
Advantages:
FastLED mandates that all third-party libraries be wrapped in the fl::third_party namespace to:
// Before: Global namespace collision risk
class AudioAnalyzeNoteFrequency { /* ... */ };
// After: Safely namespaced
namespace fl {
namespace third_party {
namespace teensy_audio {
class AudioAnalyzeNoteFrequency { /* ... */ };
}
}
}
The fl::third_party::teensy_audio namespace makes it immediately clear:
fl::third_party::teensy_audio is from Teensy Audio Library// Third-party code stays isolated
namespace fl::third_party::teensy_audio {
class AudioAnalyzeNoteFrequency { /* Original Teensy Audio API */ };
}
// FastLED provides clean wrappers
namespace fl {
class NoteFrequencyAnalyzer { // Clean C++ API following FastLED conventions
static float detectFrequency(const AnalyzerConfig& config,
fl::span<const float> audio_samples,
float* confidence = nullptr);
private:
fl::third_party::teensy_audio::AudioAnalyzeNoteFrequency analyzer_;
};
}
src/third_party/teensy_audio_notefreq/
├── README.md (this file)
├── LICENSE.txt (MIT license from original)
├── analyze_notefreq.h (wrapped in fl::third_party::teensy_audio)
├── analyze_notefreq.cpp (wrapped in fl::third_party::teensy_audio)
└── AudioStream.h (minimal stub for Teensy Audio dependency)
// analyze_notefreq.h
namespace fl {
namespace third_party {
namespace teensy_audio {
class AudioAnalyzeNoteFrequency {
// Original API preserved
};
}
}
}
The original code depends on Teensy Audio's AudioStream class for audio block management. Options:
Option A: Minimal Stub (Recommended)
AudioStream.h stub with required interfacesOption B: Direct Port
AudioStream dependency entirelynamespace fl {
struct NoteFrequencyConfig {
// Detection parameters
float threshold = 0.15f; // Uncertainty threshold (0.0 to 1.0)
float minFrequency = 29.14f; // Minimum detectable frequency (Hz)
float maxFrequency = 5000.0f; // Maximum detectable frequency (Hz)
// Buffer configuration
uint32_t sampleRate = 44100; // Audio sample rate
uint32_t bufferSize = 3072; // Analysis buffer size (samples)
// Output options
bool requireHighConfidence = false; // Only return high-confidence results
float minConfidence = 0.5f; // Minimum confidence threshold
};
}
namespace fl {
class NoteFrequencyAnalyzer {
public:
// Static detection method
static bool detectFrequency(const NoteFrequencyConfig& config,
fl::span<const float> audio_samples,
float* frequency,
float* confidence = nullptr,
fl::string* error_message = nullptr);
// Platform support detection
static bool isSupported();
// Stateful analyzer for streaming audio
class StreamingAnalyzer {
public:
explicit StreamingAnalyzer(const NoteFrequencyConfig& config);
// Process audio samples incrementally
void processSamples(fl::span<const float> samples);
// Check if frequency is available
bool available() const;
// Get detected frequency
float frequency() const;
// Get detection confidence
float confidence() const;
private:
fl::third_party::teensy_audio::AudioAnalyzeNoteFrequency analyzer_;
NoteFrequencyConfig config_;
};
};
}
fl/audio/note_frequency.cpp)namespace fl {
class NoteFrequencyAnalyzerImpl {
public:
NoteFrequencyAnalyzerImpl(const NoteFrequencyConfig& config)
: config_(config) {
analyzer_.begin(config.threshold);
}
bool detect(fl::span<const float> samples, float* freq, float* conf) {
// Feed samples to analyzer
feedSamples(samples);
// Check if frequency detected
if (!analyzer_.available()) {
return false;
}
// Get results
float detected_freq = analyzer_.read();
float detected_conf = analyzer_.probability();
// Apply configuration filters
if (detected_freq < config_.minFrequency ||
detected_freq > config_.maxFrequency) {
return false;
}
if (config_.requireHighConfidence &&
detected_conf < config_.minConfidence) {
return false;
}
// Return results
if (freq) *freq = detected_freq;
if (conf) *conf = detected_conf;
return true;
}
private:
void feedSamples(fl::span<const float> samples);
fl::third_party::teensy_audio::AudioAnalyzeNoteFrequency analyzer_;
NoteFrequencyConfig config_;
};
}
fl::scoped_array for temporary buffers// Teensy Audio uses float samples in range [-1.0, 1.0]
// FastLED may use int16_t or other formats
void convertSamples(fl::span<const int16_t> input,
fl::span<float> output) {
for (size_t i = 0; i < input.size(); ++i) {
output[i] = input[i] / 32768.0f;
}
}
Problem: Original code inherits from AudioStream class for Teensy Audio framework integration.
Solutions:
AudioStream base class with required virtual methodsRecommendation: Use Minimal Stub for fastest integration, preserving original code quality.
Problem: Teensy Audio uses specific audio block structure (128 samples per block, reference counting).
Solutions:
Recommendation: Emulate blocks in stub for minimal code changes.
Problem: Original code may have ARM-specific optimizations or Teensy hardware assumptions.
Solutions:
#ifdef guards for platform-specific optimizationsRecommendation: Test on multiple platforms early, add portable fallbacks as needed.
Problem: Musical note detection requires consistent low-latency processing.
Solutions:
tests/audio_note_frequency.cpp)TEST_CASE("NoteFrequency initialization") {
fl::NoteFrequencyConfig config;
fl::NoteFrequencyAnalyzer::StreamingAnalyzer analyzer(config);
CHECK(analyzer.confidence() == 0.0f);
}
TEST_CASE("NoteFrequency known frequency detection") {
// Generate pure 440Hz sine wave (A4)
std::vector<float> samples = generateSineWave(440.0f, 1.0f, 44100, 4096);
float freq, conf;
bool detected = fl::NoteFrequencyAnalyzer::detectFrequency(
fl::NoteFrequencyConfig{}, samples, &freq, &conf);
CHECK(detected);
CHECK(freq == Approx(440.0f).epsilon(0.01)); // Within 1%
CHECK(conf > 0.8f); // High confidence
}
TEST_CASE("NoteFrequency musical note range") {
// Test standard guitar tuning: E2, A2, D3, G3, B3, E4
float notes[] = {82.41f, 110.0f, 146.83f, 196.0f, 246.94f, 329.63f};
for (float expected : notes) {
auto samples = generateSineWave(expected, 1.0f, 44100, 4096);
float detected;
bool success = fl::NoteFrequencyAnalyzer::detectFrequency(
fl::NoteFrequencyConfig{}, samples, &detected);
CHECK(success);
CHECK(detected == Approx(expected).epsilon(0.02));
}
}
TEST_CASE("NoteFrequency low SNR handling") {
// Test with noisy signal
auto signal = generateSineWave(440.0f, 0.5f, 44100, 4096);
auto noise = generateWhiteNoise(0.3f, 4096);
for (size_t i = 0; i < signal.size(); ++i) {
signal[i] += noise[i];
}
float freq, conf;
bool detected = fl::NoteFrequencyAnalyzer::detectFrequency(
fl::NoteFrequencyConfig{}, signal, &freq, &conf);
// Should still detect but with lower confidence
if (detected) {
CHECK(freq == Approx(440.0f).epsilon(0.05));
CHECK(conf < 0.9f); // Lower confidence expected
}
}
TEST_CASE("NoteFrequency harmonic-rich signal") {
// Test with square wave (odd harmonics)
auto samples = generateSquareWave(440.0f, 1.0f, 44100, 4096);
float freq;
bool detected = fl::NoteFrequencyAnalyzer::detectFrequency(
fl::NoteFrequencyConfig{}, samples, &freq);
CHECK(detected);
CHECK(freq == Approx(440.0f).epsilon(0.02)); // Should find fundamental
}
# src/third_party/teensy_audio_notefreq/CMakeLists.txt
add_library(teensy_audio_notefreq
analyze_notefreq.cpp
# AudioStream.cpp (if needed)
)
target_include_directories(teensy_audio_notefreq
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
)
# Add to FastLED build
target_link_libraries(FastLED PRIVATE teensy_audio_notefreq)
// fl/audio/note_frequency.h
namespace fl {
inline bool NoteFrequencyAnalyzer::isSupported() {
#if defined(ESP32) || defined(ARDUINO) || defined(__linux__) || defined(_WIN32)
return true; // Algorithm is portable
#else
return false;
#endif
}
}
fl/audio/note_frequency.h)/// @brief Real-time musical note frequency detection using the Yin algorithm
///
/// This analyzer detects the fundamental frequency of musical notes in audio
/// signals, optimized for guitar/bass tuning and pitch detection applications.
///
/// Based on the Teensy Audio Library's analyze_notefreq component by Colin Duffy.
/// Uses the Yin algorithm for high-quality pitch estimation resistant to octave errors.
///
/// @note Requires buffering audio samples for analysis. Minimum detectable frequency
/// is determined by buffer size: f_min ≈ sample_rate / buffer_size
///
/// Example usage:
/// @code
/// fl::NoteFrequencyConfig config;
/// config.threshold = 0.15f;
/// config.sampleRate = 44100;
///
/// float frequency, confidence;
/// if (fl::NoteFrequencyAnalyzer::detectFrequency(config, audio_samples,
/// &frequency, &confidence)) {
/// printf("Detected %.2f Hz (%.0f%% confidence)\n", frequency, confidence * 100);
/// }
/// @endcode
class NoteFrequencyAnalyzer { /* ... */ };
Add to FastLED documentation:
src/third_party/teensy_audio_notefreq/fl::third_party::teensy_audio namespaceAudioStream stubNoteFrequencyConfig structureNoteFrequencyAnalyzer static APIStreamingAnalyzer for stateful processing✅ Detects fundamental frequency of musical notes (E2-E6: 82Hz-1318Hz) ✅ Accuracy within 2% of true frequency for clean signals ✅ Processes audio in real-time on ESP32/Teensy platforms ✅ Provides confidence metric for detection quality ✅ Handles harmonic-rich signals (guitar, bass, voice)
✅ All code in fl::third_party::teensy_audio namespace
✅ Clean FastLED API following project conventions
✅ Comprehensive error handling and validation
✅ Full test coverage with unit and integration tests
✅ Documented API with usage examples
✅ Processing latency < 100ms for typical configurations ✅ Memory footprint < 20KB for analyzer instance ✅ No dynamic allocation in real-time processing path ✅ Consistent performance across FastLED-supported platforms
Original Work:
MIT License Summary: Permission is hereby granted, free of charge, to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, subject to including the copyright notice and permission notice in all copies.
Integration into FastLED:
fl::third_party::teensy_audio namespaceLICENSE.txtsrc/third_party/TJpg_Decoder/README.md (reference implementation)CLAUDE.md (project-wide integration rules)fl::third_party::* pattern for all external librariesThis integration document serves as a blueprint for incorporating Teensy Audio's note frequency detection into FastLED, following established third-party integration patterns and maintaining code quality standards.