FastLED 3.10.8
Loading...
Searching...
No Matches
key.h
Go to the documentation of this file.
1// KeyDetector - Musical key detection using chroma features and key profiles
2//
3// Detects the musical key (tonal center) of the audio using Krumhansl-Schmuckler
4// key-finding algorithm. Analyzes pitch class profiles (chroma) and correlates
5// them with theoretical key profiles for major and minor keys.
6//
7// Features:
8// - Detects 24 possible keys (12 major + 12 minor)
9// - Uses chroma features from FFT analysis
10// - Krumhansl-Schmuckler key profiles for accurate detection
11// - Temporal averaging for stable key estimation
12// - Confidence scoring based on correlation strength
13// - Event callbacks for key changes
14//
15// Usage:
16// KeyDetector detector;
17// detector.onKeyChange([](const Key& key) {
18// Serial.print("Key: ");
19// Serial.print(key.getKeyName());
20// Serial.print(" (confidence: ");
21// Serial.print(key.confidence);
22// Serial.println(")");
23// });
24//
25// void loop() {
26// Sample sample = audio.next();
27// shared_ptr<Context> context = make_shared<Context>(sample);
28// detector.update(context);
29// }
30
31#pragma once
32
34#include "fl/stl/function.h"
35#include "fl/stl/vector.h"
36#include "fl/stl/int.h"
37#include "fl/stl/noexcept.h"
38
39namespace fl {
40namespace audio {
41namespace detector {
42
43// Forward declaration
44
45// Key structure representing detected musical key
46struct Key {
47 u8 rootNote; // 0-11 (C=0, C#=1, D=2, ..., B=11)
48 bool isMinor; // true = minor key, false = major key
49 float confidence; // 0.0-1.0 correlation with key profile
50 u32 timestamp; // Detection timestamp (ms)
51 u32 duration; // How long this key has been active (ms)
52
53 Key() FL_NO_EXCEPT : rootNote(0), isMinor(false), confidence(0.0f), timestamp(0), duration(0) {}
54
55 Key(u8 root, bool minor, float conf, u32 time)
56 FL_NO_EXCEPT : rootNote(root), isMinor(minor), confidence(conf), timestamp(time), duration(0) {}
57
58 // Get key name (e.g., "C", "F#", "Bb")
59 const char* getRootName() const FL_NO_EXCEPT;
60
61 // Get quality name ("maj" or "min")
62 const char* getQuality() const FL_NO_EXCEPT { return isMinor ? "min" : "maj"; }
63
64 // Get full key name (e.g., "C maj", "F# min")
65 void getKeyName(char* buffer, size_t bufferSize) const FL_NO_EXCEPT;
66
67 // Check if key is valid
68 bool isValid() const FL_NO_EXCEPT { return confidence > 0.0f; }
69
70 // Compare keys
71 bool operator==(const Key& other) const FL_NO_EXCEPT {
72 return rootNote == other.rootNote && isMinor == other.isMinor;
73 }
74
75 bool operator!=(const Key& other) const FL_NO_EXCEPT {
76 return !(*this == other);
77 }
78};
79
80// KeyDetector - Detects musical key using chroma analysis
81class KeyDetector : public Detector {
82public:
85
86 // Detector interface
87 void update(shared_ptr<Context> context) FL_NO_EXCEPT override;
88 void fireCallbacks() FL_NO_EXCEPT override;
89 bool needsFFT() const FL_NO_EXCEPT override { return true; }
90 const char* getName() const FL_NO_EXCEPT override { return "KeyDetector"; }
91 void reset() FL_NO_EXCEPT override;
92
93 // Event callbacks (multiple listeners supported)
94 function_list<void(const Key& key)> onKey; // Every frame with current key
95 function_list<void(const Key& key)> onKeyChange; // When key changes
96 function_list<void()> onKeyEnd; // When key ends (confidence drops)
97
98 // State access
99 const Key& getCurrentKey() const FL_NO_EXCEPT { return mCurrentKey; }
100 bool hasKey() const FL_NO_EXCEPT { return mCurrentKey.isValid(); }
101
102 // Configuration
103 void setConfidenceThreshold(float threshold) FL_NO_EXCEPT { mConfidenceThreshold = threshold; }
105 void setAveragingFrames(int frames) FL_NO_EXCEPT { mAveragingFrames = frames; }
106
107private:
108 // Current state
113
114 // Configuration
115 float mConfidenceThreshold; // Minimum confidence to detect key (default: 0.65)
116 u32 mMinKeyDuration; // Minimum duration for stable key (default: 2000ms)
117 int mAveragingFrames; // Number of frames to average (default: 8)
118
119 // Chroma history for temporal averaging
120 vector<float> mChromaHistory[12]; // History for each pitch class
123
124 // Key profiles (Krumhansl-Schmuckler)
125 static const float MAJOR_PROFILE[12];
126 static const float MINOR_PROFILE[12];
127
128 // Pre-computed profile statistics (computed once at init, reused 24x per frame)
129 float mMajorProfileMean = 0.0f;
131 float mMinorProfileMean = 0.0f;
133
135
136 // Helper methods
137 void initializeProfileStats() FL_NO_EXCEPT; // Pre-compute profile statistics once
138 void extractChroma(const fft::Bins& fft, float* chroma) FL_NO_EXCEPT;
139 void normalizeChroma(float* chroma) FL_NO_EXCEPT;
140 void updateChromaHistory(const float* chroma) FL_NO_EXCEPT;
141 void getAveragedChroma(float* chroma) FL_NO_EXCEPT;
142 Key detectKey(const float* chroma, u32 timestamp) FL_NO_EXCEPT;
143 float correlateWithProfile(const float* chroma, const float* profile, int rootNote) FL_NO_EXCEPT;
144
145 // Pending callback flags
146 bool mFireKeyChange = false;
147 bool mFireKeyEnd = false;
148 bool mFireKey = false;
149};
150
151} // namespace detector
152} // namespace audio
153} // namespace fl
const Key & getCurrentKey() const FL_NO_EXCEPT
Definition key.h:99
bool needsFFT() const FL_NO_EXCEPT override
Definition key.h:89
shared_ptr< const fft::Bins > mRetainedFFT
Definition key.h:134
void reset() FL_NO_EXCEPT override
Definition key.cpp.hpp:189
void updateChromaHistory(const float *chroma) FL_NO_EXCEPT
Definition key.cpp.hpp:262
bool hasKey() const FL_NO_EXCEPT
Definition key.h:100
~KeyDetector() FL_NO_EXCEPT override
void setAveragingFrames(int frames) FL_NO_EXCEPT
Definition key.h:105
static const float MAJOR_PROFILE[12]
Definition key.h:125
vector< float > mChromaHistory[12]
Definition key.h:120
void normalizeChroma(float *chroma) FL_NO_EXCEPT
Definition key.cpp.hpp:245
void fireCallbacks() FL_NO_EXCEPT override
Definition key.cpp.hpp:372
void extractChroma(const fft::Bins &fft, float *chroma) FL_NO_EXCEPT
Definition key.cpp.hpp:207
void getAveragedChroma(float *chroma) FL_NO_EXCEPT
Definition key.cpp.hpp:278
function_list< void()> onKeyEnd
Definition key.h:96
Key detectKey(const float *chroma, u32 timestamp) FL_NO_EXCEPT
Definition key.cpp.hpp:301
function_list< void(const Key &key)> onKeyChange
Definition key.h:95
static const float MINOR_PROFILE[12]
Definition key.h:126
void initializeProfileStats() FL_NO_EXCEPT
Definition key.cpp.hpp:98
float correlateWithProfile(const float *chroma, const float *profile, int rootNote) FL_NO_EXCEPT
Definition key.cpp.hpp:333
function_list< void(const Key &key)> onKey
Definition key.h:94
const char * getName() const FL_NO_EXCEPT override
Definition key.h:90
void setConfidenceThreshold(float threshold) FL_NO_EXCEPT
Definition key.h:103
void update(shared_ptr< Context > context) FL_NO_EXCEPT override
Definition key.cpp.hpp:118
void setMinDuration(u32 ms) FL_NO_EXCEPT
Definition key.h:104
unsigned char u8
Definition stdint.h:131
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
fl::u64 time() FL_NO_EXCEPT
Alias for millis64() - returns 64-bit millisecond time.
Definition chrono.h:346
Base definition for an LED controller.
Definition crgb.hpp:179
const char * getRootName() const FL_NO_EXCEPT
Definition key.cpp.hpp:60
void getKeyName(char *buffer, size_t bufferSize) const FL_NO_EXCEPT
Definition key.cpp.hpp:65
const char * getQuality() const FL_NO_EXCEPT
Definition key.h:62
Key() FL_NO_EXCEPT
Definition key.h:53
bool operator!=(const Key &other) const FL_NO_EXCEPT
Definition key.h:75
bool operator==(const Key &other) const FL_NO_EXCEPT
Definition key.h:71
Key(u8 root, bool minor, float conf, u32 time) FL_NO_EXCEPT
Definition key.h:55
bool isValid() const FL_NO_EXCEPT
Definition key.h:68