diff --git a/firmware/.gitignore b/firmware/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/firmware/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/firmware/.vscode/extensions.json b/firmware/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/firmware/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/firmware/include/README b/firmware/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/firmware/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/firmware/include/app.h b/firmware/include/app.h new file mode 100644 index 0000000..d8b9b58 --- /dev/null +++ b/firmware/include/app.h @@ -0,0 +1,10 @@ +#ifndef ZAUBERSTAB_APP_H +#define ZAUBERSTAB_APP_H + +struct App { + void (*loop)(void); + void (*setup)(void); +}; + +struct App +#endif \ No newline at end of file diff --git a/firmware/include/zauberstab.h b/firmware/include/zauberstab.h new file mode 100644 index 0000000..57161fc --- /dev/null +++ b/firmware/include/zauberstab.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#define LED_PIN 12 +#define NUM_LEDS 144 +#define MIC_PIN 15 + +extern CRGB leds[NUM_LEDS]; + +int zauberstab_init(); +int32_t get_sample(); \ No newline at end of file diff --git a/firmware/lib/README b/firmware/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/firmware/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/firmware/platformio.ini b/firmware/platformio.ini new file mode 100644 index 0000000..735ad79 --- /dev/null +++ b/firmware/platformio.ini @@ -0,0 +1,23 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32doit-devkit-v1] +platform = espressif32 +board = esp32doit-devkit-v1 +framework = arduino +lib_deps = fastled/FastLED @ ^3.5.0 + +monitor_port = COM5 +monitor_speed = 115200 + +upload_protocol = esptool +upload_port = COM5 + + diff --git a/firmware/sound_ripple/sound_ripple.cpp b/firmware/sound_ripple/sound_ripple.cpp new file mode 100644 index 0000000..4a0933c --- /dev/null +++ b/firmware/sound_ripple/sound_ripple.cpp @@ -0,0 +1,125 @@ +/* soundmems_ripple + * + * By: Andrew Tuline + * + * Date: August, 2015 + * + * Updated: January, 2020 + * + * Create a ripple from a calculated peak from a sampled microphone + * + * Note: If you are using a microphone powered by the 3.3V signal, such as the Sparkfun MEMS microphone, then connect 3.3V to the AREF pin. + * + */ + + +//#define FASTLED_ALLOW_INTERRUPTS 0 // Used for ESP8266. +#include // FastLED library. +#include "zauberstab.h" +// Fixed definitions cannot change on the fly. +#define LED_DT LED_PIN // Data pin to connect to the strip. +#define LED_CK 11 // Clock pin for WS2801 or APA102. +#define COLOR_ORDER GRB // It's GRB for WS2812 and BGR for APA102. +#define LED_TYPE WS2812 // Using APA102, WS2812, WS2801. Don't forget to modify LEDS.addLeds to suit. + +uint8_t squelch = 7; // Anything below this is background noise, so we'll make it '0'. +int sample; // Current sample. +float sampleAvg = 0; // Smoothed Average. +float micLev = 0; // Used to convert returned value to have '0' as minimum. +uint8_t maxVol = 11; // Reasonable value for constant volume for 'peak detector', as it won't always trigger. +bool samplePeak = 0; // Boolean flag for peak. Responding routine must reset this flag. + +int max_bright = 255; + +CRGB leds[NUM_LEDS]; +// Ripple variables +uint8_t colour; // Ripple colour is randomized. +int center = 0; // Center of the current ripple. +int step = -1; // -1 is the initializing step. +uint8_t myfade = 255; // Starting brightness. +#define maxsteps 16 // Case statement wouldn't allow a variable. + + + +void setup() { + + //analogReference(EXTERNAL); // 3.3V reference for analog input. + + Serial.begin(115200); + + LEDS.addLeds(leds, NUM_LEDS); // Use this for WS2812B +// LEDS.addLeds(leds, NUM_LEDS); // Use this for WS2801 or APA102 + + FastLED.setBrightness(max_bright); + //FastLED.setMaxPowerInVoltsAndMilliamps(5, 500); + +} // setup() + + +void getSample() { + + int16_t micIn; // Current sample starts with negative values and large values, which is why it's 16 bit signed. + static long peakTime; + + micIn = analogRead(MIC_PIN); // Poor man's analog Read. + micLev = ((micLev * 31) + micIn) / 32; // Smooth it out over the last 32 samples for automatic centering. + micIn -= micLev; // Let's center it to 0 now. + micIn = abs(micIn); // And get the absolute value of each sample. + sample = (micIn <= squelch) ? 0 : (sample + micIn) / 2; // Using a ternary operator, the resultant sample is either 0 or it's a bit smoothed out with the last sample. + sampleAvg = ((sampleAvg * 31) + sample) / 32; // Smooth it out over the last 32 samples. + + if (sample > (sampleAvg+maxVol) && millis() > (peakTime + 50)) { // Poor man's beat detection by seeing if sample > Average + some value. + samplePeak = 1; // Then we got a peak, else we don't. Display routines need to reset the samplepeak value in case they miss the trigger. + peakTime=millis(); + } + +} // getSample() + + + +void ripple() { + + if (samplePeak == 1) {step = -1; samplePeak = 0; } // If we have a peak, let's reset our ripple. + + fadeToBlackBy(leds, NUM_LEDS, 64); // 8 bit, 1 = slow, 255 = fast + + switch (step) { + + case -1: // Initialize ripple variables. + center = random(NUM_LEDS); + colour = random8(); // More peaks/s = higher the hue colour. + step = 0; + break; + + case 0: + leds[center] = CHSV(colour, 255, 255); // Display the first pixel of the ripple. + step ++; + break; + + case maxsteps: // At the end of the ripples. + // step = -1; + break; + + default: // Middle of the ripples. + leds[(center + step + NUM_LEDS) % NUM_LEDS] += CHSV(colour, 255, myfade/step*2); // Simple wrap. + leds[(center - step + NUM_LEDS) % NUM_LEDS] += CHSV(colour, 255, myfade/step*2); + step ++; // Next step. + break; + } // switch step + +} // ripple() + + +void loop() { + + getSample(); + + EVERY_N_MILLISECONDS(20) { + ripple(); + } + + FastLED.show(); + +} // loop() + + diff --git a/firmware/sound_wave/sound_wave.ino b/firmware/sound_wave/sound_wave.ino new file mode 100644 index 0000000..09d0a37 --- /dev/null +++ b/firmware/sound_wave/sound_wave.ino @@ -0,0 +1,163 @@ + +/* sound_wave + * + * By: Andrew Tuline + * + * Date: February, 2017 + * + * Basic code to read from the Sparkfun INMP401 microphone, and create waves based on sampled input. This does NOT include sensitivity adjustment. + * + * My hardware setup: + * + * Arduino Nano & Addressable LED strips + * - Powered by USB power bank + * - APA102 or WS2812 data connected to pin 12. + * - APA102 clock connected to pin 11. + * - 5V on APA102 or WS2812 connected to 5V on Nano (good for short strips). + * - Gnd to Gnd on Nano. + * + * + * Sparkfun MEMS microphone + * - Vcc on microphone is connected to 3.3V on Nano. + * - AREF on Nano connected to 3.3V on Nano. + * - Mic out connected to A5. + * - Gnd to Gnd on Nano. + * + * Note: If you are using a microphone powered by the 3.3V signal, such as the Sparkfun MEMS microphone, then connect 3.3V to the AREF pin. + * + */ + +//#define FASTLED_ALLOW_INTERRUPTS 0 // Used for ESP8266. +#include // FastLED library. +#include "zauberstab.h" + +uint8_t squelch = 7; // Anything below this is background noise, so we'll make it '0'. +int sample; // Current sample. +float sampleAvg = 0; // Smoothed Average. +float micLev = 0; // Used to convert returned value to have '0' as minimum. +uint8_t maxVol = 11; // Reasonable value for constant volume for 'peak detector', as it won't always trigger. +bool samplePeak = 0; // Boolean flag for peak. Responding routine must reset this flag. + +int sampleAgc, multAgc; +uint8_t targetAgc = 60; // This is our setPoint at 20% of max for the adjusted output. + + +// Fixed definitions cannot change on the fly. +#define LED_DT LED_PIN // Data pin to connect to the strip. +#define LED_CK 11 // Clock pin for WS2801 or APA102. +#define COLOR_ORDER GRB // It's GRB for WS2812 and BGR for APA102. +#define LED_TYPE WS2812 // Using APA102, WS2812, WS2801. Don't forget to modify LEDS.addLeds to suit. + + +struct CRGB leds[NUM_LEDS]; // Initialize our LED array. + +int max_bright = 255; + +CRGBPalette16 currentPalette = OceanColors_p; +CRGBPalette16 targetPalette = OceanColors_p; +TBlendType currentBlending = LINEARBLEND; // NOBLEND or LINEARBLEND + + + +void setup() { + //analogReference(EXTERNAL); // 3.3V reference for analog input. + + Serial.begin(115200); // Initialize serial port for debugging. + delay(1000); // Soft startup to ease the flow of electrons. + + LEDS.addLeds(leds, NUM_LEDS); // Use this for WS2812B +// LEDS.addLeds(leds, NUM_LEDS); // Use this for WS2801 or APA102 + + FastLED.setBrightness(max_bright); + FastLED.setMaxPowerInVoltsAndMilliamps(5, 500); // FastLED Power management set at 5V, 500mA. + +} // setup() + + +void getSample() { + + int16_t micIn; // Current sample starts with negative values and large values, which is why it's 16 bit signed. + static long peakTime; + + micIn = analogRead(MIC_PIN)>>2; // Poor man's analog Read. + micLev = ((micLev * 31) + micIn) / 32; // Smooth it out over the last 32 samples for automatic centering. + micIn -= micLev; // Let's center it to 0 now. + micIn = abs(micIn); // And get the absolute value of each sample. + sample = (micIn <= squelch) ? 0 : (sample + micIn) / 2; // Using a ternary operator, the resultant sample is either 0 or it's a bit smoothed out with the last sample. + sampleAvg = ((sampleAvg * 31) + sample) / 32; // Smooth it out over the last 32 samples. + + if (sample > (sampleAvg+maxVol) && millis() > (peakTime + 50)) { // Poor man's beat detection by seeing if sample > Average + some value. + samplePeak = 1; // Then we got a peak, else we don't. Display routines need to reset the samplepeak value in case they miss the trigger. + peakTime=millis(); + } + +} // getSample() + + +void agcAvg() { // A simple averaging multiplier to automatically adjust sound sensitivity. + + multAgc = (sampleAvg < 1) ? targetAgc : targetAgc / sampleAvg; // Make the multiplier so that sampleAvg * multiplier = setpoint + sampleAgc = sample * multAgc; + if (sampleAgc > 255) sampleAgc = 255; + +//------------ Oscilloscope output --------------------------- + Serial.print(targetAgc); Serial.print(" "); + Serial.print(multAgc); Serial.print(" "); + Serial.print(sampleAgc); Serial.print(" "); + + Serial.print(micLev); Serial.print(" "); + Serial.print(sample); Serial.println(" "); +// Serial.print(sampleAvg); Serial.print(" "); +// Serial.print(samplePeak); Serial.print(" "); samplePeak = 0; +// Serial.print(100); Serial.print(" "); +// Serial.print(0); Serial.print(" "); +// Serial.println(" "); + +} // agcAvg() + + + +void sndwave() { + + leds[NUM_LEDS/2] = ColorFromPalette(currentPalette, sampleAgc, sampleAgc, currentBlending); // Put the sample into the center + + for (int i = NUM_LEDS - 1; i > NUM_LEDS/2; i--) { //move to the left // Copy to the left, and let the fade do the rest. + leds[i] = leds[i - 1]; + } + + for (int i = 0; i < NUM_LEDS/2; i++) { // move to the right // Copy to the right, and let the fade to the rest. + leds[i] = leds[i + 1]; + } + +} // sndwave() + + +void loop() { + + + EVERY_N_SECONDS(5) { // Change the palette every 5 seconds. + for (int i = 0; i < 16; i++) { + targetPalette[i] = CHSV(random8(), 255, 255); + } + } + + EVERY_N_MILLISECONDS(100) { // AWESOME palette blending capability once they do change. + uint8_t maxChanges = 24; + nblendPaletteTowardPalette(currentPalette, targetPalette, maxChanges); + } + + + EVERY_N_MILLIS_I(thistimer,20) { // For fun, let's make the animation have a variable rate. + uint8_t timeval = beatsin8(10,20,50); // Use a sinewave for the line below. Could also use peak/beat detection. + thistimer.setPeriod(timeval); // Allows you to change how often this routine runs. + fadeToBlackBy(leds, NUM_LEDS, 16); // 1 = slow, 255 = fast fade. Depending on the faderate, the LED's further away will fade out. + getSample(); + agcAvg(); + sndwave(); + } + + FastLED.show(); + +} // loop() + + diff --git a/firmware/src/vumeter.cpp b/firmware/src/vumeter.cpp new file mode 100644 index 0000000..c8b17ca --- /dev/null +++ b/firmware/src/vumeter.cpp @@ -0,0 +1,54 @@ +#include "zauberstab.h" + +unsigned long last_sample_time; +static int sample_counter = 0; +unsigned int top_led_pos = 0; +float rms_avg = 0; +float vu_filt = 0.0f; +float vu_filt_slow = 0.0f; + +void setup() +{ + zauberstab_init(); + Serial.begin(115200); + FastLED.setBrightness(100); +} + +void loop() { + if (micros()-last_sample_time >= 500){ + last_sample_time = micros(); + int32_t sample = get_sample(); + float in = sample*sample; + sample_counter++; + rms_avg += (in - rms_avg)/(sample_counter + 1); + } + + EVERY_N_MILLIS(10){ + + float vu = 20 * log10f(rms_avg); + vu_filt = (vu-vu_filt) * 0.8 + vu_filt; + vu_filt_slow = (vu_filt-vu_filt_slow) * 0.01 + vu_filt_slow; + //Serial.println(vu); + int max_led = vu_filt; + int top_led = vu_filt_slow; + + max_led = max_led > 0xFF ? 0xFF : max_led; + + if (top_led < max_led){ + vu_filt_slow = vu_filt; + top_led = max_led; + } + + fill_solid(leds, NUM_LEDS, CRGB::Black); + for(int i = 0; i < max_led; i++) { + int idx = map(i, 0, NUM_LEDS, 0, 0xFF); + leds[i] = ColorFromPalette(RainbowColors_p, idx); + } + + leds[top_led] = CRGB::White; + + FastLED.show(); + rms_avg = 0.0f; + sample_counter = 0; + } +} \ No newline at end of file diff --git a/firmware/src/zauberstab.cpp b/firmware/src/zauberstab.cpp new file mode 100644 index 0000000..f5f3313 --- /dev/null +++ b/firmware/src/zauberstab.cpp @@ -0,0 +1,31 @@ +#include "zauberstab.h" + +CRGB leds[NUM_LEDS]; +static int16_t mic_offset = 0; + +static uint16_t read_mic() { + return analogRead(MIC_PIN); +} + +static uint16_t get_mic_offset() { + float average = 0.f; + uint32_t num_samples = 0; + + for(num_samples = 0; num_samples < 10000; num_samples++) { + uint16_t sample = read_mic(); + average += ((float) sample - average) / (num_samples + 1); + } + + return (uint16_t)average; +} + +int zauberstab_init() { + FastLED.addLeds(leds, NUM_LEDS); + //FastLED.setMaxPowerInVoltsAndMilliamps(5, 300); + mic_offset = get_mic_offset(); + return 0; +} + +int32_t get_sample() { + return read_mic() - mic_offset; +} \ No newline at end of file diff --git a/firmware/temp/alex_beat_detect.cpp b/firmware/temp/alex_beat_detect.cpp new file mode 100644 index 0000000..1e8806b --- /dev/null +++ b/firmware/temp/alex_beat_detect.cpp @@ -0,0 +1,204 @@ +#include "zauberstab.h" +#include "app.h" + +#define SAMPLING_FREQUENCY_BP 40 // number of energy chunks per second +#define SAMPLING_FREQUENCY_CONTROL 1 // check number of times per second if the current band pass is the best one +#define Q 20. // quality factor of band pass filters +#define PI 3.1415926535897932384626433832795 +#define n_BP 30 //number of band pass filters + +static unsigned long sampling_period_bp = 1000000L / SAMPLING_FREQUENCY_BP; +static unsigned long sampling_period_control = 1000000L / SAMPLING_FREQUENCY_CONTROL; +static double energy = 0; +static unsigned long last_us_bp = 0L; +static unsigned long last_us_control = 0L; + +static float a0[n_BP]; +static float a1[n_BP]; +static float a2[n_BP]; +static float b0[n_BP]; +//static float b1[n_BP]; +static float b2[n_BP]; + +static float a[n_BP]; +static float w0[n_BP]; + +static float yy1[n_BP]; +static float yy2[n_BP]; +static float yy3[n_BP]; +static float yy4[n_BP]; +static float yy5[n_BP]; +static float yy6[n_BP]; + +static float u1[n_BP]; +static float u2[n_BP]; +static float y[n_BP]; +static float y_fil[n_BP]; + +static float angle; +static float angle2; + +static double energy_fil = 800.; + +static float pos_target = NUM_LEDS / 2; +static float pos_target_filtered = NUM_LEDS / 2; + +static float microphone_offset = 675; +static long initial_time; + +static int active = 15; +static int candidate = 15; +static int rounds = 0; + +static int get_value(int pos, float pos0) +{ + if (abs(pos0 - pos) > 20) + { + return 0; + } + else + { + return (40 - abs(pos0 - pos) * 2); + } +} + +static void set_filter() +{ + for (int i = 0; i < n_BP; i++) + { + float frequency = 1.75 + i * (2.4 - 1.75) / n_BP; + w0[i] = 2. * PI * frequency / SAMPLING_FREQUENCY_BP; + a[i] = sin(w0[i] / (2. * Q)); + b0[i] = a[i]; + //b1[i] = 0; + b2[i] = -a[i]; + a0[i] = 1. + a[i]; + a1[i] = -2. * cos(w0[i]); + a2[i] = 1. - a[i]; + } +} + +static void abeat_setup() +{ + zauberstab_init(); + Serial.begin(115200); + set_filter(); + initial_time = micros(); +} +static void abeat_loop() +{ + + int sample = int(analogRead(MIC_PIN) - microphone_offset); + energy += abs(sample) * abs(sample); + + if (micros() - last_us_bp > sampling_period_bp) + { + + Serial.println(sample); + + microphone_offset += (analogRead(MIC_PIN) - microphone_offset) * 0.001; + + //Serial.println(microphone_offset); + + last_us_bp += sampling_period_bp; + energy_fil += (energy - energy_fil) * 0.01; + //Serial.println(energy); + for (int i = 0; i < n_BP; i++) + { + y[i] = (b0[i] / a0[i]) * energy + 0. + (b2[i] / a0[i]) * u2[i] - (a1[i] / a0[i]) * yy1[i] - (a2[i] / a0[i]) * yy2[i]; + u2[i] = u1[i]; + u1[i] = energy; + yy6[i] = yy5[i]; + yy5[i] = yy4[i]; + yy4[i] = yy3[i]; + yy3[i] = yy2[i]; + yy2[i] = yy1[i]; + yy1[i] = y[i]; + y_fil[i] += (abs(y[i]) - y_fil[i]) * 0.005; //linie der scheitelpunkte + } + + float delays = constrain(SAMPLING_FREQUENCY_BP * 0.25 / (1.75 + active * (2.4 - 1.75) / n_BP), 4., 6.); + + float delayed = 0; + if (delays > 5) + { + delayed = yy5[active] * (1 - delays + 5) + yy6[active] * (delays - 5); + } + else + { + delayed = yy4[active] * (1 - delays + 4) + yy5[active] * (delays - 4); + } + + angle = atan2(delayed, y[active]); + + if (PI < abs(angle - angle2) && abs(angle - angle2) < 3 * PI) + { + angle2 = angle + 2 * PI; + } + else + { + angle2 = angle; + } + + pos_target = map(angle2, -PI, 3 * PI, -0.3 * NUM_LEDS, NUM_LEDS * 1.5); + + if (pos_target > pos_target_filtered) + { + pos_target_filtered += (pos_target - pos_target_filtered) * 0.35; + } + else + { + pos_target_filtered = pos_target; + } + + // Serial.print(y_fil[active]); + // Serial.print(","); + // Serial.println(y[active]); + + energy = 0; + + for (int i = 0; i < NUM_LEDS; i++) + { + int brightness = get_value(i, pos_target_filtered); + //leds[i].setRGB(brightness, brightness, brightness); + + //leds[i].setHSV(160, (rounds == 6) ? 0xFF : 0, brightness); + leds[i] = CRGB::White; + } + FastLED.show(); + } + + if (micros() - last_us_control > sampling_period_control) + { + last_us_control += sampling_period_control; + int argmax = -1; + float valuemax = 0; + for (int i = 0; i < n_BP; i++) + { + if (y_fil[i] > valuemax) + { + valuemax = y_fil[i]; + argmax = i; + } + } + + if (argmax > -1) + { + if (argmax == candidate) + { + rounds++; + } + else + { + rounds = 0; + candidate = argmax; + } + if (rounds > 6) + { + rounds = 0; + active = candidate; + } + } + } +} + diff --git a/firmware/temp/beat_detect.cpp b/firmware/temp/beat_detect.cpp new file mode 100644 index 0000000..b47b0ab --- /dev/null +++ b/firmware/temp/beat_detect.cpp @@ -0,0 +1,203 @@ +#include "zauberstab.h" + +#define SAMPLING_FREQUENCY_BP 40 // number of energy chunks per second +#define SAMPLING_FREQUENCY_CONTROL 1 // check number of times per second if the current band pass is the best one +#define Q 20. // quality factor of band pass filters +#define PI 3.1415926535897932384626433832795 +#define n_BP 30 //number of band pass filters + +static unsigned long sampling_period_bp = 1000000L / SAMPLING_FREQUENCY_BP; +static unsigned long sampling_period_control = 1000000L / SAMPLING_FREQUENCY_CONTROL; +static double energy = 0; +static unsigned long last_us_bp = 0L; +static unsigned long last_us_control = 0L; + +static float a0[n_BP]; +static float a1[n_BP]; +static float a2[n_BP]; +static float b0[n_BP]; +//static float b1[n_BP]; +static float b2[n_BP]; + +static float a[n_BP]; +static float w0[n_BP]; + +static float yy1[n_BP]; +static float yy2[n_BP]; +static float yy3[n_BP]; +static float yy4[n_BP]; +static float yy5[n_BP]; +static float yy6[n_BP]; + +static float u1[n_BP]; +static float u2[n_BP]; +static float y[n_BP]; +static float y_fil[n_BP]; + +static float angle; +static float angle2; + +static double energy_fil = 800.; + +static float pos_target = NUM_LEDS / 2; +static float pos_target_filtered = NUM_LEDS / 2; + +static float microphone_offset = 675; +static long initial_time; + +static int active = 15; +static int candidate = 15; +static int rounds = 0; + +static int get_value(int pos, float pos0) +{ + if (abs(pos0 - pos) > 20) + { + return 0; + } + else + { + return (40 - abs(pos0 - pos) * 2); + } +} + +static void set_filter() +{ + for (int i = 0; i < n_BP; i++) + { + float frequency = 1.75 + i * (2.4 - 1.75) / n_BP; + w0[i] = 2. * PI * frequency / SAMPLING_FREQUENCY_BP; + a[i] = sin(w0[i] / (2. * Q)); + b0[i] = a[i]; + //b1[i] = 0; + b2[i] = -a[i]; + a0[i] = 1. + a[i]; + a1[i] = -2. * cos(w0[i]); + a2[i] = 1. - a[i]; + } +} + +void setup() +{ + zauberstab_init(); + Serial.begin(115200); + set_filter(); + initial_time = micros(); +} +void loop() +{ + + int sample = int(analogRead(MIC_PIN) - microphone_offset); + energy += abs(sample) * abs(sample); + + if (micros() - last_us_bp > sampling_period_bp) + { + + Serial.println(sample); + + microphone_offset += (analogRead(MIC_PIN) - microphone_offset) * 0.001; + + //Serial.println(microphone_offset); + + last_us_bp += sampling_period_bp; + energy_fil += (energy - energy_fil) * 0.01; + //Serial.println(energy); + for (int i = 0; i < n_BP; i++) + { + y[i] = (b0[i] / a0[i]) * energy + 0. + (b2[i] / a0[i]) * u2[i] - (a1[i] / a0[i]) * yy1[i] - (a2[i] / a0[i]) * yy2[i]; + u2[i] = u1[i]; + u1[i] = energy; + yy6[i] = yy5[i]; + yy5[i] = yy4[i]; + yy4[i] = yy3[i]; + yy3[i] = yy2[i]; + yy2[i] = yy1[i]; + yy1[i] = y[i]; + y_fil[i] += (abs(y[i]) - y_fil[i]) * 0.005; //linie der scheitelpunkte + } + + float delays = constrain(SAMPLING_FREQUENCY_BP * 0.25 / (1.75 + active * (2.4 - 1.75) / n_BP), 4., 6.); + + float delayed = 0; + if (delays > 5) + { + delayed = yy5[active] * (1 - delays + 5) + yy6[active] * (delays - 5); + } + else + { + delayed = yy4[active] * (1 - delays + 4) + yy5[active] * (delays - 4); + } + + angle = atan2(delayed, y[active]); + + if (PI < abs(angle - angle2) && abs(angle - angle2) < 3 * PI) + { + angle2 = angle + 2 * PI; + } + else + { + angle2 = angle; + } + + pos_target = map(angle2, -PI, 3 * PI, -0.3 * NUM_LEDS, NUM_LEDS * 1.5); + + if (pos_target > pos_target_filtered) + { + pos_target_filtered += (pos_target - pos_target_filtered) * 0.35; + } + else + { + pos_target_filtered = pos_target; + } + + // Serial.print(y_fil[active]); + // Serial.print(","); + // Serial.println(y[active]); + + energy = 0; + + for (int i = 0; i < NUM_LEDS; i++) + { + int brightness = get_value(i, pos_target_filtered); + if (brightness >= 1) { + brightness = 10; + } + //leds[i].setRGB(brightness, brightness, brightness); + leds[i].setHSV(160, (rounds == 6) ? 0xFF : 0, brightness); + } + FastLED.show(); + } + + if (micros() - last_us_control > sampling_period_control) + { + last_us_control += sampling_period_control; + int argmax = -1; + float valuemax = 0; + for (int i = 0; i < n_BP; i++) + { + if (y_fil[i] > valuemax) + { + valuemax = y_fil[i]; + argmax = i; + } + } + + if (argmax > -1) + { + if (argmax == candidate) + { + rounds++; + } + else + { + rounds = 0; + candidate = argmax; + } + if (rounds > 6) + { + rounds = 0; + active = candidate; + } + } + } +} diff --git a/firmware/temp/main.cpp b/firmware/temp/main.cpp new file mode 100644 index 0000000..520efd6 --- /dev/null +++ b/firmware/temp/main.cpp @@ -0,0 +1,9 @@ +#include "zauberstab.h" + +void setup() { + zauberstab_init(); +} + +void loop() { + +} \ No newline at end of file diff --git a/firmware/test/README b/firmware/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/firmware/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html