diff --git a/sandbox/streams-generator-fft/streams-generator-fft.ino b/sandbox/streams-generator-fft/streams-generator-fft.ino index 3679bfc0c..413a27597 100644 --- a/sandbox/streams-generator-fft/streams-generator-fft.ino +++ b/sandbox/streams-generator-fft/streams-generator-fft.ino @@ -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 sound(sineWave); // Stream generated from sine w FFTStream out; StreamCopy copier(out, sound); // copies sound into i2s -void processFFTResult(FFTArray&array){ - +void processFFTResult(FFTStream &fft, FFTArray &values){ + for(int j=0;j 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 diff --git a/sandbox/streams-generator-fft_note/streams-generator-fft_note.ino b/sandbox/streams-generator-fft_note/streams-generator-fft_note.ino new file mode 100644 index 000000000..f0ca78139 --- /dev/null +++ b/sandbox/streams-generator-fft_note/streams-generator-fft_note.ino @@ -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 sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000 +GeneratedSoundStream sound(sineWave); // Stream generated from sine wave +FFTStream out(2000); +StreamCopy copier(out, sound); // copies sound into fft + +void processFFTResult(FFTStream &fft, FFTArray &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(); +} diff --git a/src/AudioTools/FFT.h b/src/AudioTools/FFT.h index a58217aba..a195cf56e 100644 --- a/src/AudioTools/FFT.h +++ b/src/AudioTools/FFT.h @@ -29,7 +29,9 @@ template class FFTBase { public: /// forward fft virtual FFTArray &calculateArray(NT array[], int len) { - FFTArray complex_array(len); + if (complex_array.size() != len) { + complex_array.resize(len); + } for (int j = 0; j < len; ++j) { complex_array[j] = array[j]; } diff --git a/src/AudioTools/FFTStream.h b/src/AudioTools/FFTStream.h index 395bc23c3..5dbf21532 100644 --- a/src/AudioTools/FFTStream.h +++ b/src/AudioTools/FFTStream.h @@ -1,6 +1,9 @@ #pragma once #include "AudioTools/FFT.h" #include "AudioTools/Streams.h" +#include "AudioTools/MusicalNotes.h" +#include +#include 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 FFTStream : public BufferedStream { +template 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 &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 &stream){ + int idx = -1; + U max = 0; + for (int j=0;jmax){ + idx = j; + max = tmp; + } + } + return idx; + } + + /// Determines the note from the value at max amplitude + const char* note(FFTArray &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 &data)) { this->cb = cb; } + void setCallback(void (*cb)(FFTStream &stream, FFTArray &data)) { + this->cb = cb; + } protected: FFT fft; FFTArray array; - void (*cb)(FFTArray &data); + void (*cb)(FFTStream &stream, FFTArray &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= 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 -class SineWaveGenerator : public SoundGenerator { +class SineWaveGenerator : public SoundGenerator, 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 { 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 { logStatus(); } + /// provides the AudioBaseInfo + AudioBaseInfo audioInfo() { + AudioBaseInfo baseInfo; + baseInfo.sample_rate = m_sample_rate; + baseInfo.channels = SoundGenerator::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 { } 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; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b8df3e056..1429733f5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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) diff --git a/tests/fft/CMakeLists.txt b/tests/fft/CMakeLists.txt new file mode 100644 index 000000000..a039d6763 --- /dev/null +++ b/tests/fft/CMakeLists.txt @@ -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) + diff --git a/tests/fft/fft.cpp b/tests/fft/fft.cpp new file mode 100644 index 000000000..c9335e176 --- /dev/null +++ b/tests/fft/fft.cpp @@ -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 sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000 +GeneratedSoundStream sound(sineWave); // Stream generated from sine wave +FFTStream out(1024); +StreamCopy copier(out, sound); // copies sound into i2s + +void processFFTResult(FFTStream &fft, FFTArray &values){ + static MusicalNotes notes; + int diff; + + for(int j=0;j 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(); +} \ No newline at end of file