funny that I stopped wasting my time with teensies becos of SNR issues
now I don’t see how settings array values to zero at runtime would reduce the size of the storage requirement
funny that I stopped wasting my time with teensies becos of SNR issues
now I don’t see how settings array values to zero at runtime would reduce the size of the storage requirement
The way the CMSIS constant tables are set up for real numbers is different from complex. Maybe it’s non-intuitive, but it is true. Complex FFT of size 2048 easily fits in Daisy flash, the only down side is some wasted RAM for the 0 imaginary component.
SNR: sound/noise ratio is 50% on teensies
so they fool people with an amazing audio library
yet you have to interface the teensy with a ADC breakout board just to sample audio
and I2S is quiet hard to debug
The main thing I prefer about Daisy is the builtin codec, and patch_sm is very handy for eurorack modules.
On the other hand, I2S might be tricky, but in my experience with Teensy, using several different I2S devices, I2S just worked, with no debugging of I2S.
Anyway, both systems have their pros and cons.
here’s an example which displays the frequency of audio input, using cfft of size 2048:
// Using FFT size of 2048 needs 16K of sample buffer, and only gets a resolution of
// 47Hz.
#include "daisysp.h"
#include "../../kxmx_bluemchen/src/kxmx_bluemchen.h"
#include <string.h>
#include "arm_math.h"
#include "arm_const_structs.h"
using namespace kxmx;
using namespace daisy;
using namespace daisysp;
Bluemchen hw;
// OLED is 64x32
void UpdateOled(float fl)
{
hw.display.Fill(false);
std::string str = std::to_string(static_cast<int>(fl));
char *cstr = &str[0];
hw.display.SetCursor(0, 0);
// Font_16x26 4 digits
// Font_11x18 5 digits BEST for this
// Font_6x8 10 digits
hw.display.WriteString(cstr, Font_11x18, true);
hw.display.Update();
}
#define FFT_SIZE 2048
#define FFT_BUF_SIZE (2*FFT_SIZE)
float fft_buf[FFT_BUF_SIZE];
float output[FFT_SIZE];
// global, so do_fft() knows when to run
int fft_buf_index = 0;
void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size)
{
for(size_t i = 0; i < size; i++)
{
if (fft_buf_index < FFT_BUF_SIZE) {
fft_buf[fft_buf_index++] = in[0][i];
fft_buf[fft_buf_index++] = 0.f;
}
out[0][i] = in[0][i];
out[1][i] = in[1][i];
}
}
float do_fft(float *input, float *output)
{
float32_t maxValue;
uint32_t index = 0;
int Fmax = hw.AudioSampleRate() / 2; // 24000
uint32_t Nbins = FFT_SIZE / 2;
/* Process the data through the CFFT/CIFFT module */
/* this must match FFT_SIZE, and 2048 is as high as Daisy can fit */
arm_cfft_f32(&arm_cfft_sR_f32_len2048, input, 0 /*ifftFlag*/, 1/*doBitReverse*/);
/* Process the data through the Complex Magnitude Module for
calculating the magnitude at each bin */
arm_cmplx_mag_f32(input, output, FFT_SIZE /*fftSize*/);
/* Calculates maxValue and returns corresponding BIN value */
arm_max_f32(output, FFT_SIZE /*fftSize*/, &maxValue, &index);
/*
to calculate the freq of the selected bin
N (Bins) = FFT Size/2
FR = Fmax/N(Bins)
*/
// error test
if (index >= Nbins)
return -1.f;
// good result
return (float) index * ((float)Fmax / Nbins);
}
int main(void)
{
hw.Init();
hw.StartAdc();
hw.StartAudio(AudioCallback);
while (1)
{
float f;
static float old_f = -1.f;
if (fft_buf_index == FFT_BUF_SIZE) {
// do FFT
f = do_fft(fft_buf, output);
// start collecting samples again
fft_buf_index = 0;
} else {
continue;
}
// if frequency value has changed, update display
if ((f > 0.f) && (f != old_f)) {
UpdateOled(f);
old_f = f;
}
}
}
Hello --as a way to finally get FFT to work on the daisy I copied your code to try (and switched it to the patch)
I put (i think) all the relevant files in the makefile (see this post)
and i changed the name of that one file to a lower case S. (so the asm file would work)
BUT now I am getting these new errors
/Users/cricketbee/desktop/daisyexamples/patch/blank/blank.cpp:71: undefined reference to arm_cmplx_mag_f32' /Applications/ARM/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: /Users/cricketbee/desktop/daisyexamples/patch/blank/blank.cpp:74: undefined reference to
arm_max_f32’
I have those in my makefile
TARGET = Blank
CPP_SOURCES = blank.cpp
OPT = -Os
LIBDAISY_DIR = …/…/libDaisy
DAISYSP_DIR = …/…/DaisySP
ASM_SOURCES =
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_bitreversal2.s
C_SOURCES =
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_rfft_fast_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_rfft_fast_init_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/CommonTables/arm_common_tables.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/CommonTables/arm_const_structs.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_cfft_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_bitreversal.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_cfft_radix8_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_cfft_radix4_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_cfft_radix4_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/ComplexMathFunctions/arm_cmplx_mag_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/ComplexMathFunctions/arm_max_f32.c
C_INCLUDES +=
-I$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Include
SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core
include $(SYSTEM_FILES_DIR)/Makefile
thanks for any help
Here’s what I have in the Makefile for the CMSIS stuff:
C_SOURCES =
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_cfft_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_cfft_radix8_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_bitreversal.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/ComplexMathFunctions/arm_cmplx_mag_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/CommonTables/arm_const_structs.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/CommonTables/arm_common_tables.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/StatisticsFunctions/arm_max_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_rfft_fast_init_f32.c
$(LIBDAISY_DIR)/Drivers/CMSIS/DSP/Source/TransformFunctions/arm_rfft_fast_f32.c
as a way to finally get FFT to work on the daisy
please create your own post, it is not good practice to highjack other people threads, especially if you encounter compilation issues that concerns your setup
I am getting spammed on my post for topics that do not concern me
thx for your comprehension
if people were doing the same, a whole site would need to be dedicated to one single thread
oh sorry about that. I actually thought the opposite was true especially since there are so few threads about FFT on this forum. I thought it would be good to put it here so that you would see it as I was hoping you might have had some sucess with your FFT project. And since I was responding to not being able to getting the code running that TelePlayer posted in this thread (see above) It seemed to make the most sense to post it here.
Do you think it would be better to make a new thread for his code?
thanks for your input
For anyone who’s trying to use Émilie Gillet’s shy_fft.h
header these days, here are some tips (learned from much trial and error):
template <typename T>
, I can only get them to work with single-precision floats. If anyone has managed to get double-precision arithmetic working, please let me know! (Or, if someone sees why the implementation obviously doesn’t support it, that’d be interesting too…)ShyFFT<T, N>
instance must be initialized (by calling member function Init
) before anything will work.Direct
and Inverse
methods of the class. You only need to supply pointers to the buffers of size N where samples will be read from and written to.Inverse
with Direct
will give you back what you started with, multiplied by N, the FFT size. Multiplying a signal by 2048 is 66 dB of gain… beware headphone users.Direct
as {real[0], real[1], ..., real[N / 2 - 1], imag[0], imag[1], ..., imag[N / 2 - 1]}
. The input to Inverse
expects the same format. (By comparison, the CMSIS real FFT function packs frequency-domain data like {real[0], imag[0], ..., real[N / 2 - 1], imag[N / 2 - 1]}
.)I’m not as proficient as many here clearly are at programming, so +1 for a simple and clearly annotated FFT example please. I would pay good money tbh.
Woah, point number 5 is exactly what I’d been trying to figure out for some time, so I really appreciate the writeup!
I’d been working on a very “simple” wrapper class for shy_fft to make it easier to do things with (mostly tested with converting to input > fft > ifft > output, and came up wit hthis (again, it’s very poorly implemented and haven’t picked it back up in a while).
I did some preliminary work to try and get (I)FFT into Heavy, but it needs some DSP experts to finalize and get working:
There’s some issues for how these objects are typically used inside Pd, which may not make this particularly useful though.
Here’s a working C++ example for the Seed!
Thanks so much.
I’m only accustomed to using Arduino, so I have no idea what any of this makefile stuff is about.
Took me awhile to understand how C++ build systems work, but the gist is the “ordinary” way to create a C++ executable is to write some code in source.cpp
, point your terminal to the directory where your source file lives, and run g++ source.cpp
. That command g++
invokes the GNU C++ compiler, which eats source.cpp
and by default spits out an executable file called a.out
. If you want to modify some parameters of the compilation – for example, you want the name of the executable to be different, or you want to include other files that live in a different folder – you might instead run:
g++ source.cpp -o myexecutable -I /some/other/dir/
There are a ton of options (or “flags”) you can pass to the compiler, which will modify where it looks for files, what it does with those files, what kinds of optimizations it performs on your code, etc. As your project becomes more complex you often have to use lots and lots of flags. A Makefile is a human-readable way of preparing those flags – it’s a bit like a scripting language that sets up and performs the compilation. Makefiles can include other Makefiles, branch according to conditions, define and modify variables, etc.
The Makefile in the example above gives a name to the output executable, lists the C++ source files, indicates the location of the DaisySP and libDaisy directories, sets the C++ standard used by the compiler (GNU C++17) and optimization level, and then includes the template Makefile supplied by Electrosmith in libDaisy/core/Makefile
, where all the actual compilation commands live.
A Makefile is so much more than a convenient way to put together compiler options.
Thanks for explaining this so thoroughly. As I understand it, this is something like what Arduino handles for the user?
Here’s the follow up question: do I have to (bite off a big learning curve and) advance from using Arduino if I want to be able to use libDaisy, or is there an Arduino way to do it?
I already have a busy career as a mechanical engineer, so I’m not really trying to learn too much for this one small project.
Yeah, exactly – the Arduino IDE gives you a GUI for downloading libraries, and then behind the scenes generates all the build code to compile / link your application to those libraries.
Since the Arduino language is a simplified version of C++, moving from Arduino / DaisyDuino to C++ / libDaisy shouldn’t actually be too difficult. I’d highly encourage looking through the projects in DaisyExamples. You’ll find most of the makefiles are identical (other than some straightforward modifications); the example projects also follow a template analogous to the Arduino sketch format, where there’s some initialization, the audio callback gets started, and then there’s some main loop that deals with processing non-audio information.
Happy to help if you get stuck!
Much appreciated, I’ll put on my big person pants and give it a go.