Pulled from: https://github.com/PaulStoffregen/Audio/tree/master (analyze_notefreq.cpp and analyze_notefreq.h)
Current Status
- ⏸️ Library Staged: Awaiting third-party code integration
- ⏸️ Namespace Wrapping: To be wrapped in
fl::third_party
- ⏸️ C++ Conversion: Already C++ compatible
- ⏸️ FastLED Integration: Bridge implementation pending
- ⏸️ Audio System Integration: Integration with FastLED audio analysis pending
Overview
The 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).
Key Features
- Yin Algorithm Implementation: High-quality fundamental frequency estimation
- Musical Note Detection: Optimized for guitar/bass tuning and note analysis
- Real-time Processing: Designed for embedded audio processing with low overhead
- Configurable Accuracy: Adjustable uncertainty threshold and buffer size
- Low-frequency Capable: Default configuration detects down to ~29.14 Hz
Original API
Class: AudioAnalyzeNoteFrequency
Public Methods
bool begin(float threshold = 0.15);
void threshold(float p);
bool available(void);
float read(void);
float probability(void);
Configuration Parameters
- Buffer Size: Default 24 audio blocks
- Each block is typically 128 samples
- Total buffer: ~3072 samples
- Minimum detectable frequency: ~(sample_rate / buffer_size)
- Threshold: Detection uncertainty parameter
- Range: 0.0 (most sensitive) to 1.0 (least sensitive)
- Default: 0.15 (good balance for most applications)
- Lower values detect quieter/more ambiguous signals
Algorithm Details
The Yin algorithm is a well-established pitch detection algorithm that:
- Computes the autocorrelation function using cumulative mean normalized difference
- Identifies the first local minimum below the threshold
- Applies parabolic interpolation for sub-sample accuracy
- Returns frequency estimate with confidence measure
Advantages:
- Resistant to octave errors (common in autocorrelation methods)
- Works well with harmonic musical signals
- Provides confidence metric for quality assessment
- Computationally efficient for embedded systems
FastLED Third-Party Namespace Architecture
FastLED mandates that all third-party libraries be wrapped in the fl::third_party namespace to:
1. Prevent Global Namespace Pollution
class AudioAnalyzeNoteFrequency { };
namespace third_party {
namespace teensy_audio {
class AudioAnalyzeNoteFrequency { };
}
}
}
Base definition for an LED controller.
2. Clear Ownership and Licensing Boundaries
The fl::third_party::teensy_audio namespace makes it immediately clear:
- What code is external: Everything in
fl::third_party::teensy_audio is from Teensy Audio Library
- Licensing considerations: MIT licensed third-party code
- Maintenance responsibility: Upstream maintained by PJRC (Paul Stoffregen)
- API stability: Third-party APIs may change independently of FastLED
3. Controlled Integration Points
namespace fl::third_party::teensy_audio {
class AudioAnalyzeNoteFrequency { };
}
class NoteFrequencyAnalyzer {
static float detectFrequency(const AnalyzerConfig& config,
float* confidence = nullptr);
private:
fl::third_party::teensy_audio::AudioAnalyzeNoteFrequency analyzer_;
};
}
Proposed FastLED Integration
Phase 1: Library Staging and Namespace Wrapping
1.1 File Structure
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)
1.2 Namespace Wrapping
namespace third_party {
namespace teensy_audio {
class AudioAnalyzeNoteFrequency {
};
}
}
}
1.3 Dependency Handling
The original code depends on Teensy Audio's AudioStream class for audio block management. Options:
Option A: Minimal Stub (Recommended)
- Create minimal
AudioStream.h stub with required interfaces
- Replace Teensy-specific audio block management with FastLED equivalents
- Maintain same buffering semantics
Option B: Direct Port
- Extract and adapt only the core Yin algorithm implementation
- Remove
AudioStream dependency entirely
- Implement buffer management directly in FastLED wrapper
Phase 2: FastLED API Wrapper
2.1 Configuration Structure
struct NoteFrequencyConfig {
float threshold = 0.15f;
float minFrequency = 29.14f;
float maxFrequency = 5000.0f;
uint32_t sampleRate = 44100;
uint32_t bufferSize = 3072;
bool requireHighConfidence = false;
float minConfidence = 0.5f;
};
}
2.2 Clean FastLED API
class NoteFrequencyAnalyzer {
public:
static bool detectFrequency(const NoteFrequencyConfig& config,
float* frequency,
float* confidence = nullptr,
static bool isSupported();
class StreamingAnalyzer {
public:
explicit StreamingAnalyzer(const NoteFrequencyConfig& config);
bool available() const;
float frequency() const;
float confidence() const;
private:
fl::third_party::teensy_audio::AudioAnalyzeNoteFrequency analyzer_;
NoteFrequencyConfig config_;
};
};
}
Phase 3: Integration Implementation
3.1 Bridge Implementation (fl/audio/note_frequency.cpp)
class NoteFrequencyAnalyzerImpl {
public:
NoteFrequencyAnalyzerImpl(const NoteFrequencyConfig& config)
: config_(config) {
analyzer_.begin(config.threshold);
}
feedSamples(samples);
if (!analyzer_.available()) {
return false;
}
float detected_freq = analyzer_.read();
float detected_conf = analyzer_.probability();
if (detected_freq < config_.minFrequency ||
detected_freq > config_.maxFrequency) {
return false;
}
if (config_.requireHighConfidence &&
detected_conf < config_.minConfidence) {
return false;
}
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_;
};
}
3.2 Memory Management
- Use
fl::scoped_array for temporary buffers
- Replace any dynamic allocation with FastLED memory patterns
- Ensure proper cleanup in destructors
3.3 Type Conversions
for (
size_t i = 0; i < input.
size(); ++i) {
output[i] = input[i] / 32768.0f;
}
}
constexpr fl::size size() const FL_NOEXCEPT
Integration Challenges and Solutions
Challenge 1: AudioStream Dependency
Problem: Original code inherits from AudioStream class for Teensy Audio framework integration.
Solutions:
- Minimal Stub Approach: Create lightweight
AudioStream base class with required virtual methods
- Direct Algorithm Port: Extract Yin algorithm core, remove streaming framework dependency
- Adapter Pattern: Wrap analyzer and translate between FastLED and Teensy Audio semantics
Recommendation: Use Minimal Stub for fastest integration, preserving original code quality.
Challenge 2: Audio Block Management
Problem: Teensy Audio uses specific audio block structure (128 samples per block, reference counting).
Solutions:
- Emulate Blocks: Create compatible block structure in stub
- Buffer Conversion: Convert FastLED audio buffers to block format on-the-fly
- Algorithm Refactor: Modify to accept arbitrary buffer sizes
Recommendation: Emulate blocks in stub for minimal code changes.
Challenge 3: Platform Dependencies
Problem: Original code may have ARM-specific optimizations or Teensy hardware assumptions.
Solutions:
- Portable Fallbacks: Ensure C++ implementation works on all platforms
- Feature Detection: Use
#ifdef guards for platform-specific optimizations
- FastLED Portability: Leverage existing FastLED cross-platform patterns
Recommendation: Test on multiple platforms early, add portable fallbacks as needed.
Challenge 4: Real-time Requirements
Problem: Musical note detection requires consistent low-latency processing.
Solutions:
- Performance Profiling: Benchmark on target embedded platforms
- Buffer Size Tuning: Allow configuration of latency vs. accuracy trade-off
- Optimization: Consider fixed-point math for microcontrollers if needed
Testing Strategy
Unit Tests (tests/audio_note_frequency.cpp)
Phase 1: Basic Functionality
TEST_CASE("NoteFrequency initialization") {
fl::NoteFrequencyConfig config;
fl::NoteFrequencyAnalyzer::StreamingAnalyzer analyzer(config);
CHECK(analyzer.confidence() == 0.0f);
}
TEST_CASE("NoteFrequency known frequency detection") {
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));
CHECK(conf > 0.8f);
}
Phase 2: Musical Note Range
TEST_CASE("NoteFrequency musical note range") {
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));
}
}
Phase 3: Edge Cases
TEST_CASE("NoteFrequency low SNR handling") {
auto signal = generateSineWave(440.0f, 0.5f, 44100, 4096);
auto noise = generateWhiteNoise(0.3f, 4096);
for (size_t i = 0; i < signal.size(); ++i) {
}
float freq, conf;
bool detected = fl::NoteFrequencyAnalyzer::detectFrequency(
fl::NoteFrequencyConfig{}, signal, &freq, &conf);
if (detected) {
CHECK(freq == Approx(440.0f).epsilon(0.05));
CHECK(conf < 0.9f);
}
}
TEST_CASE("NoteFrequency harmonic-rich signal") {
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));
}
uint8_t noise[NUM_LAYERS][WIDTH][HEIGHT]
Test Data Requirements
- Pure Tones: Sine waves at musical note frequencies
- Harmonic Signals: Square, sawtooth, triangle waves
- Noisy Signals: Signal + white/pink noise at various SNR levels
- Edge Cases: Sub-threshold signals, octave ambiguity tests
Integration Tests
- Streaming Analysis: Feed continuous audio, verify state updates
- Configuration Impact: Test different threshold/buffer size settings
- Performance: Benchmark processing time on embedded targets
Build System Integration
CMake Configuration
# 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)
Platform Support
inline bool NoteFrequencyAnalyzer::isSupported() {
#if defined(ESP32) || defined(ARDUINO) || defined(__linux__) || defined(_WIN32)
return true;
#else
return false;
#endif
}
}
Documentation Requirements
API Documentation (fl/audio/note_frequency.h)
class NoteFrequencyAnalyzer { };
User Guide Section
Add to FastLED documentation:
- Audio Analysis Overview: Introduction to pitch detection
- Configuration Guide: Choosing threshold, buffer size for application
- Performance Considerations: Latency vs. accuracy trade-offs
- Example Applications: Tuner, pitch-to-MIDI, reactive lighting
Timeline and Milestones
Milestone 1: Library Integration (Est. 1-2 days)
- Copy source files to
src/third_party/teensy_audio_notefreq/
- Wrap in
fl::third_party::teensy_audio namespace
- Create minimal
AudioStream stub
- Verify compilation on multiple platforms
Milestone 2: FastLED API Wrapper (Est. 2-3 days)
- Implement
NoteFrequencyConfig structure
- Create
NoteFrequencyAnalyzer static API
- Implement
StreamingAnalyzer for stateful processing
- Add type conversion utilities (int16_t ↔ float)
- Implement error handling and validation
Milestone 3: Testing and Validation (Est. 2-3 days)
- Write unit tests for basic functionality
- Create test signal generators (sine, square, noise)
- Add musical note range tests
- Implement edge case tests (noise, harmonics)
- Performance benchmarking on ESP32/Arduino
Milestone 4: Documentation and Examples (Est. 1-2 days)
- API documentation in headers
- Create example sketches (tuner, reactive lighting)
- Update FastLED documentation index
Success Criteria
Functional Requirements
✅ 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)
Code Quality
✅ 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
Performance
✅ 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
License and Attribution
Original Work:
- Library: Teensy Audio Library
- Component: analyze_notefreq (Note Frequency Detection)
- Author: Colin Duffy
- Copyright: © 2015 Colin Duffy
- License: MIT License
- Source: https://github.com/PaulStoffregen/Audio
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:
- Third-party code wrapped in
fl::third_party::teensy_audio namespace
- Original license preserved in
LICENSE.txt
- FastLED wrapper code follows FastLED license (MIT)
- Clear attribution maintained in documentation
References
Yin Algorithm
- Paper: "YIN, a fundamental frequency estimator for speech and music" by Alain de Cheveigné and Hideki Kawahara (2002)
- Algorithm: Autocorrelation-based pitch detection with cumulative mean normalized difference function
- Advantages: Resistant to octave errors, works well with harmonic signals
Teensy Audio Library
FastLED Integration Patterns
This 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.