This commit is contained in:
Phil Schatzmann 2021-09-03 19:56:31 +02:00
parent c549170c04
commit e4f6767846
9 changed files with 300 additions and 30 deletions

View File

@ -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

View File

@ -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();
}

View File

@ -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];
}

View File

@ -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

View File

@ -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"
};
};

View File

@ -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;

View File

@ -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
View 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
View 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();
}