mirror of
https://github.com/pschatzmann/arduino-audio-tools.git
synced 2024-09-22 10:57:31 +00:00
fft
This commit is contained in:
parent
c549170c04
commit
e4f6767846
@ -1,8 +1,7 @@
|
||||
/**
|
||||
* @file streams-generator-i2s.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief
|
||||
* @file streams-generator-fft.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief generate sound and analyze tone with fft to determine the musical note
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
@ -18,16 +17,29 @@ GeneratedSoundStream<int16_t> sound(sineWave); // Stream generated from sine w
|
||||
FFTStream<int16_t,float> out;
|
||||
StreamCopy copier(out, sound); // copies sound into i2s
|
||||
|
||||
void processFFTResult(FFTArray<float>&array){
|
||||
|
||||
void processFFTResult(FFTStream<int16_t,float> &fft, FFTArray<float> &values){
|
||||
for(int j=0;j<values.size();j++){
|
||||
Serial.print("fft -> j: ");
|
||||
Serial.print(j);
|
||||
Serial.print(", freq: ");
|
||||
Serial.print(fft.toFrequency(j));
|
||||
Serial.print(", real: ");
|
||||
Serial.print(values[j].real());
|
||||
Serial.print(", img: ");
|
||||
Serial.print(values[j].imag());
|
||||
Serial.print(", distance: ");
|
||||
Serial.print(fft.amplitude(values, j));
|
||||
Serial.println();
|
||||
}
|
||||
Serial.println("-----------------------------------------------------");
|
||||
}
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
out.setCallback(processFFTResult);
|
||||
out.begin(channels, 1000);
|
||||
sineWave.begin(channels, sample_rate, N_B4);
|
||||
out.setCallback(processFFTResult);
|
||||
out.begin(sineWave.audioInfo());
|
||||
}
|
||||
|
||||
// Arduino loop - copy sound to out
|
||||
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file streams-generator-fft.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief generate sound and analyze tone with fft to determine the musical note
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
#include "AudioTools.h"
|
||||
#include "AudioTools/FFTStream.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
uint16_t sample_rate=44100;
|
||||
uint8_t channels = 1; // The stream will have 2 channels
|
||||
SineWaveGenerator<int16_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000
|
||||
GeneratedSoundStream<int16_t> sound(sineWave); // Stream generated from sine wave
|
||||
FFTStream<int16_t,float> out(2000);
|
||||
StreamCopy copier(out, sound); // copies sound into fft
|
||||
|
||||
void processFFTResult(FFTStream<int16_t,float> &fft, FFTArray<float> &values){
|
||||
int diff;
|
||||
Serial.print("fft -> note: ");
|
||||
Serial.print(fft.note(values, diff));
|
||||
Serial.print(" - diff: ");
|
||||
Serial.println(diff);
|
||||
}
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
sineWave.begin(channels, sample_rate, N_B4);
|
||||
out.setCallback(processFFTResult);
|
||||
out.begin(sineWave.audioInfo());
|
||||
}
|
||||
|
||||
// Arduino loop - copy sound to out
|
||||
void loop() {
|
||||
copier.copy();
|
||||
}
|
@ -29,7 +29,9 @@ template <class NT> class FFTBase {
|
||||
public:
|
||||
/// forward fft
|
||||
virtual FFTArray<NT> &calculateArray(NT array[], int len) {
|
||||
FFTArray<NT> complex_array(len);
|
||||
if (complex_array.size() != len) {
|
||||
complex_array.resize(len);
|
||||
}
|
||||
for (int j = 0; j < len; ++j) {
|
||||
complex_array[j] = array[j];
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
#include "AudioTools/FFT.h"
|
||||
#include "AudioTools/Streams.h"
|
||||
#include "AudioTools/MusicalNotes.h"
|
||||
#include <cmath>
|
||||
#include <cfloat>
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
@ -8,53 +11,119 @@ namespace audio_tools {
|
||||
* Audio Ouput Stream to perform FFT
|
||||
* T defines the audio data (e.g. int16_t) and U the data type which is used for
|
||||
* the fft (e.g. float)
|
||||
*
|
||||
*/
|
||||
template <class T, class U> class FFTStream : public BufferedStream {
|
||||
template <class T, class U> class FFTStream : public BufferedStream, public AudioBaseInfoDependent {
|
||||
public:
|
||||
// Default constructor
|
||||
FFTStream(int channels=1, int samplesForFFT = 1024): BufferedStream(DEFAULT_BUFFER_SIZE) {
|
||||
begin(channels, samplesForFFT);
|
||||
}
|
||||
|
||||
void begin(int channels=1, int samplesForFFT=1024){
|
||||
FFTStream(int samplesForFFT = 1024): BufferedStream(DEFAULT_BUFFER_SIZE) {
|
||||
max_samples = samplesForFFT;
|
||||
array.resize(max_samples);
|
||||
current_samples = 0;
|
||||
this->channels = channels;
|
||||
}
|
||||
|
||||
void begin(AudioBaseInfo info){
|
||||
current_samples = 0;
|
||||
this->info = info;
|
||||
}
|
||||
|
||||
void begin(){
|
||||
current_samples = 0;
|
||||
}
|
||||
|
||||
/// Determines the frequency resolution of the FFTArray: Sample Frequency / Number of data points
|
||||
uint32_t frequencyResolution(){
|
||||
return info.sample_rate / max_samples;
|
||||
}
|
||||
|
||||
/// Provides the mininum frequency in the FFTArray
|
||||
uint32_t minFrequency() {
|
||||
return frequencyResolution();
|
||||
}
|
||||
|
||||
/// Provides the maximum frequency in the FFTArray
|
||||
uint32_t maxFrequency() {
|
||||
return info.sample_rate;
|
||||
}
|
||||
|
||||
/// Determines the frequency at the indicated index of the FFTArray
|
||||
uint32_t toFrequency(int idx){
|
||||
return minFrequency() + (idx * frequencyResolution());
|
||||
}
|
||||
|
||||
/// Determines the amplitude at the indicated index
|
||||
U amplitude(FFTArray<U> &stream, int idx){
|
||||
return sqrt((stream[idx].real() * stream[idx].real()) + (stream[idx].imag() * stream[idx].imag()));
|
||||
}
|
||||
|
||||
/// Determines the index with the max amplitude
|
||||
int16_t maxAmplitudeIdx(FFTArray<U> &stream){
|
||||
int idx = -1;
|
||||
U max = 0;
|
||||
for (int j=0;j<current_samples;j++){
|
||||
U tmp = amplitude(stream, j);
|
||||
if (std::isfinite(tmp) && tmp>max){
|
||||
idx = j;
|
||||
max = tmp;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/// Determines the note from the value at max amplitude
|
||||
const char* note(FFTArray<U> &array, int &diff){
|
||||
int16_t idx = maxAmplitudeIdx(array);
|
||||
int16_t frequency = toFrequency(idx);
|
||||
return notes.note(frequency, diff);
|
||||
}
|
||||
|
||||
|
||||
/// Defines the Audio Info
|
||||
virtual void setAudioInfo(AudioBaseInfo info) {
|
||||
this->info = info;
|
||||
};
|
||||
|
||||
AudioBaseInfo audioInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
// defines the callback which processes the fft result
|
||||
void setCallback(void (*cb)(FFTArray<U> &data)) { this->cb = cb; }
|
||||
void setCallback(void (*cb)(FFTStream<T,U> &stream, FFTArray<U> &data)) {
|
||||
this->cb = cb;
|
||||
}
|
||||
|
||||
protected:
|
||||
FFT<U> fft;
|
||||
FFTArray<U> array;
|
||||
void (*cb)(FFTArray<U> &data);
|
||||
void (*cb)(FFTStream<T,U> &stream, FFTArray<U> &data);
|
||||
int max_samples = 0;
|
||||
int current_samples = 0;
|
||||
int channels = 1;
|
||||
AudioBaseInfo info;
|
||||
MusicalNotes notes;
|
||||
|
||||
|
||||
/// write data to FFT
|
||||
virtual size_t writeExt(const uint8_t *data, size_t len) {
|
||||
int size = len / sizeof(T);
|
||||
T *ptr = (T*)data;
|
||||
for (int j = 0; j < size; j+=channels) {
|
||||
if (channels==1){
|
||||
for (int j = 0; j < size; j+= info.channels) {
|
||||
if (info.channels==1){
|
||||
array[current_samples++] = (U)ptr[j];;
|
||||
} else {
|
||||
U total = 0;
|
||||
for (int i=0;i<channels;i++){
|
||||
for (int i=0;i<info.channels;i++){
|
||||
total += (U)ptr[j+i];
|
||||
}
|
||||
array[current_samples++] = total / channels;
|
||||
array[current_samples++] = total / info.channels;
|
||||
}
|
||||
// if array is full we calculate the fft
|
||||
if (current_samples == max_samples) {
|
||||
fft.calculate(array);
|
||||
cb(array);
|
||||
cb(*this, array);
|
||||
current_samples = 0;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/// not supported
|
||||
|
@ -154,6 +154,38 @@ public:
|
||||
return mainFrequency(mainNote, level);
|
||||
}
|
||||
|
||||
/// Returns true if the frequency is audible (in the range of 20 Hz to 20 kHz)
|
||||
bool isAudible(uint16_t frequency){
|
||||
return frequency >= 20 && frequency<=20000;
|
||||
}
|
||||
|
||||
/// Determines the closes note for a frequency. We also return the frequency difference
|
||||
const char* note(int frequency, int &diff){
|
||||
uint16_t* all_notes = (uint16_t*) notes;
|
||||
const int note_count = 12*9;
|
||||
|
||||
// find closest note
|
||||
int min_diff = frequency;
|
||||
int min_pos = 0;
|
||||
for (int j=0; j<note_count; j++){
|
||||
int tmp = abs(frequency - all_notes[j]);
|
||||
if (tmp<min_diff){
|
||||
min_diff = tmp;
|
||||
min_pos = j;
|
||||
}
|
||||
}
|
||||
|
||||
int noteFrequency = all_notes[min_pos];
|
||||
diff = frequency - noteFrequency;
|
||||
return notes_str[min_pos];
|
||||
}
|
||||
|
||||
/// Determines the closes note for a frequency
|
||||
const char* note(int frequency){
|
||||
int diff;
|
||||
return note(frequency, diff);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
uint16_t notes[9][12] = {
|
||||
@ -168,6 +200,18 @@ protected:
|
||||
{N_C8, N_CS8, N_D8, N_DS8, N_E8, N_F8, N_FS8, N_G8, N_GS8, N_A8, N_AS8, N_B8}
|
||||
};
|
||||
|
||||
const char *notes_str[9*12]= {
|
||||
"C0","CS0","D0","DS0","E0","F0","FS0","G0","GS0","A0","AS0","B0",
|
||||
"C1","CS1","D1","DS1","E1","F1","FS1","G1","GS1","A1","AS1","B1",
|
||||
"C2","CS2","D2","DS2","E2","F2","FS2","G2","GS2","A2","AS2","B2",
|
||||
"C3","CS3","D3","DS3","E3","F3","FS3","G3","GS3","A3","AS3","B3",
|
||||
"C4","CS4","D4","DS4","E4","F4","FS4","G4","GS4","A4","AS4","B4",
|
||||
"C5","CS5","D5","DS5","E5","F5","FS5","G5","GS5","A5","AS5","B5",
|
||||
"C6","CS6","D6","DS6","E6","F6","FS6","G6","GS6","A6","AS6","B6",
|
||||
"C7","CS7","D7","DS7","E7","F7","FS7","G7","GS7","A7","AS7","B7",
|
||||
"C8","CS8","D8","DS8","E8","F8","FS8","G8","GS8","A8","AS8","B8"
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
@ -46,7 +46,7 @@ class SoundGenerator {
|
||||
|
||||
/// Provides the data as byte array with the requested number of channels
|
||||
virtual size_t readBytes( uint8_t *buffer, size_t lengthBytes){
|
||||
LOGD("readBytes: %d", lengthBytes);
|
||||
LOGD("readBytes: %d", (int)lengthBytes);
|
||||
size_t result = 0;
|
||||
int ch = channels();
|
||||
int frame_size = sizeof(T) * ch;
|
||||
@ -98,6 +98,7 @@ class SoundGenerator {
|
||||
return output_channels;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
bool active = false;
|
||||
bool activeWarningIssued = false;
|
||||
@ -114,10 +115,10 @@ class SoundGenerator {
|
||||
*
|
||||
*/
|
||||
template <class T>
|
||||
class SineWaveGenerator : public SoundGenerator<T> {
|
||||
class SineWaveGenerator : public SoundGenerator<T>, public AudioBaseInfoDependent {
|
||||
public:
|
||||
// the scale defines the max value which is generated
|
||||
SineWaveGenerator(float amplitude = 32767.0, float phase = 0) {
|
||||
SineWaveGenerator(float amplitude = 32767.0, float phase = 0.0) {
|
||||
LOGD("SineWaveGenerator");
|
||||
m_amplitude = amplitude;
|
||||
m_phase = phase;
|
||||
@ -126,6 +127,11 @@ class SineWaveGenerator : public SoundGenerator<T> {
|
||||
void begin() {
|
||||
begin(1, 44100, 0);
|
||||
}
|
||||
|
||||
void begin(AudioBaseInfo info, uint16_t frequency=0){
|
||||
begin(info.channels, info.sample_rate, frequency);
|
||||
}
|
||||
|
||||
void begin(uint16_t sample_rate, uint16_t frequency=0){
|
||||
begin(1, sample_rate, frequency);
|
||||
}
|
||||
@ -140,6 +146,15 @@ class SineWaveGenerator : public SoundGenerator<T> {
|
||||
logStatus();
|
||||
}
|
||||
|
||||
/// provides the AudioBaseInfo
|
||||
AudioBaseInfo audioInfo() {
|
||||
AudioBaseInfo baseInfo;
|
||||
baseInfo.sample_rate = m_sample_rate;
|
||||
baseInfo.channels = SoundGenerator<T>::channels();
|
||||
baseInfo.bits_per_sample = sizeof(T)*8;
|
||||
return baseInfo;
|
||||
}
|
||||
|
||||
/// Defines the frequency - after the processing has been started
|
||||
void setFrequency(uint16_t frequency) {
|
||||
this->m_frequency = frequency;
|
||||
@ -154,12 +169,11 @@ class SineWaveGenerator : public SoundGenerator<T> {
|
||||
}
|
||||
|
||||
protected:
|
||||
uint16_t m_sample_rate;
|
||||
|
||||
uint16_t m_sample_rate = 0;
|
||||
float m_frequency = 0;
|
||||
float m_time = 0.0;
|
||||
float m_amplitude = 1.0;
|
||||
float m_deltaTime = 1.0 / m_sample_rate;
|
||||
float m_deltaTime = 0.0;
|
||||
float m_phase = 0.0;
|
||||
float double_Pi = PI * 2.0;
|
||||
|
||||
|
@ -29,6 +29,7 @@ if(NOT arduino_emulator_POPULATED)
|
||||
endif()
|
||||
|
||||
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/fft ${CMAKE_CURRENT_BINARY_DIR}/fft)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/mp3-mini ${CMAKE_CURRENT_BINARY_DIR}/mp3-mini)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/mp3-helix ${CMAKE_CURRENT_BINARY_DIR}/mp3-helix)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/aac-helix ${CMAKE_CURRENT_BINARY_DIR}/aac-helix)
|
||||
|
19
tests/fft/CMakeLists.txt
Normal file
19
tests/fft/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
# set the project name
|
||||
project(fft)
|
||||
set (CMAKE_CXX_STANDARD 11)
|
||||
set (DCMAKE_CXX_FLAGS "-Werror")
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
|
||||
set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
|
||||
endif()
|
||||
|
||||
# build sketch as executable
|
||||
add_executable (fft fft.cpp)
|
||||
# use main() from arduino_emulator
|
||||
target_compile_definitions(fft PUBLIC -DARDUINO -DEXIT_ON_STOP)
|
||||
|
||||
# specify libraries
|
||||
target_link_libraries(fft arduino_emulator arduino-audio-tools)
|
||||
|
70
tests/fft/fft.cpp
Normal file
70
tests/fft/fft.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @file streams-generator-fft.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief generate sound and analyze tone with fft to determine the musical note
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "AudioTools.h"
|
||||
#include "AudioTools/FFTStream.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
uint16_t sample_rate=8000;
|
||||
uint8_t channels = 1; // The stream will have 2 channels
|
||||
SineWaveGenerator<int16_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000
|
||||
GeneratedSoundStream<int16_t> sound(sineWave); // Stream generated from sine wave
|
||||
FFTStream<int16_t,float> out(1024);
|
||||
StreamCopy copier(out, sound); // copies sound into i2s
|
||||
|
||||
void processFFTResult(FFTStream<int16_t,float> &fft, FFTArray<float> &values){
|
||||
static MusicalNotes notes;
|
||||
int diff;
|
||||
|
||||
for(int j=0;j<values.size();j++){
|
||||
Serial.print("fft -> j: ");
|
||||
Serial.print(j);
|
||||
Serial.print(", freq: ");
|
||||
Serial.print(fft.toFrequency(j));
|
||||
Serial.print(", real: ");
|
||||
Serial.print(values[j].real());
|
||||
Serial.print(", img: ");
|
||||
Serial.print(values[j].imag());
|
||||
Serial.print(", distance: ");
|
||||
Serial.print(fft.amplitude(values, j));
|
||||
Serial.print("-> note: ");
|
||||
const char* note = notes.note(fft.toFrequency(j), diff);
|
||||
Serial.print(note);
|
||||
Serial.print(" / diff: ");
|
||||
Serial.print(diff);
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
Serial.print("=> max index: ");
|
||||
Serial.println(fft.maxAmplitudeIdx(values));
|
||||
Serial.print("=> note: ");
|
||||
Serial.println(fft.note(values, diff));
|
||||
Serial.print(" / diff: ");
|
||||
Serial.print(diff);
|
||||
|
||||
Serial.println("-----------------------------------------------------");
|
||||
}
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
sineWave.begin(channels, sample_rate, N_B4);
|
||||
out.setCallback(processFFTResult);
|
||||
out.begin(sineWave.audioInfo());
|
||||
}
|
||||
|
||||
// Arduino loop - copy sound to out
|
||||
void loop() {
|
||||
copier.copy();
|
||||
}
|
||||
|
||||
int main(){
|
||||
setup();
|
||||
while(true) loop();
|
||||
}
|
Loading…
Reference in New Issue
Block a user