FastLED 3.9.15
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_NOEXCEPT : rootNote(0), isMinor(false), confidence(0.0f), timestamp(0), duration(0) {}
54
55 Key(u8 root, bool minor, float conf, u32 time)
56 : 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;
60
61 // Get quality name ("maj" or "min")
62 const char* getQuality() const { 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;
66
67 // Check if key is valid
68 bool isValid() const { return confidence > 0.0f; }
69
70 // Compare keys
71 bool operator==(const Key& other) const {
72 return rootNote == other.rootNote && isMinor == other.isMinor;
73 }
74
75 bool operator!=(const Key& other) const {
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) override;
88 void fireCallbacks() override;
89 bool needsFFT() const override { return true; }
90 const char* getName() const override { return "KeyDetector"; }
91 void reset() 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 { return mCurrentKey; }
100 bool hasKey() const { return mCurrentKey.isValid(); }
101
102 // Configuration
103 void setConfidenceThreshold(float threshold) { mConfidenceThreshold = threshold; }
104 void setMinDuration(u32 ms) { mMinKeyDuration = ms; }
105 void setAveragingFrames(int frames) { 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(); // Pre-compute profile statistics once
138 void extractChroma(const fft::Bins& fft, float* chroma);
139 void normalizeChroma(float* chroma);
140 void updateChromaHistory(const float* chroma);
141 void getAveragedChroma(float* chroma);
142 Key detectKey(const float* chroma, u32 timestamp);
143 float correlateWithProfile(const float* chroma, const float* profile, int rootNote);
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
shared_ptr< const fft::Bins > mRetainedFFT
Definition key.h:134
void normalizeChroma(float *chroma)
Definition key.cpp.hpp:247
bool needsFFT() const override
Definition key.h:89
void setConfidenceThreshold(float threshold)
Definition key.h:103
float correlateWithProfile(const float *chroma, const float *profile, int rootNote)
Definition key.cpp.hpp:335
static const float MAJOR_PROFILE[12]
Definition key.h:125
~KeyDetector() FL_NOEXCEPT override
vector< float > mChromaHistory[12]
Definition key.h:120
function_list< void()> onKeyEnd
Definition key.h:96
void extractChroma(const fft::Bins &fft, float *chroma)
Definition key.cpp.hpp:209
void setAveragingFrames(int frames)
Definition key.h:105
function_list< void(const Key &key)> onKeyChange
Definition key.h:95
static const float MINOR_PROFILE[12]
Definition key.h:126
void updateChromaHistory(const float *chroma)
Definition key.cpp.hpp:264
const Key & getCurrentKey() const
Definition key.h:99
function_list< void(const Key &key)> onKey
Definition key.h:94
void update(shared_ptr< Context > context) override
Definition key.cpp.hpp:118
Key detectKey(const float *chroma, u32 timestamp)
Definition key.cpp.hpp:303
const char * getName() const override
Definition key.h:90
void setMinDuration(u32 ms)
Definition key.h:104
void getAveragedChroma(float *chroma)
Definition key.cpp.hpp:280
unsigned char u8
Definition stdint.h:131
fl::u64 time() FL_NOEXCEPT
Alias for millis64() - returns 64-bit millisecond time.
Definition chrono.h:346
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
Key(u8 root, bool minor, float conf, u32 time)
Definition key.h:55
const char * getRootName() const
Definition key.cpp.hpp:60
bool operator!=(const Key &other) const
Definition key.h:75
const char * getQuality() const
Definition key.h:62
Key() FL_NOEXCEPT
Definition key.h:53
bool operator==(const Key &other) const
Definition key.h:71
void getKeyName(char *buffer, size_t bufferSize) const
Definition key.cpp.hpp:65
bool isValid() const
Definition key.h:68