From 353492653aef5824bb681b597c15f9c71c5557c4 Mon Sep 17 00:00:00 2001 From: connornishijima Date: Sun, 29 Jan 2023 19:03:48 -0700 Subject: [PATCH] 3.1.0 Upload --- SENSORY_BRIDGE_FIRMWARE/GDFT.h | 68 ++++--- .../SENSORY_BRIDGE_FIRMWARE.ino | 99 +++++----- SENSORY_BRIDGE_FIRMWARE/bridge_fs.h | 47 ++--- SENSORY_BRIDGE_FIRMWARE/constants.h | 15 +- SENSORY_BRIDGE_FIRMWARE/globals.h | 21 ++- SENSORY_BRIDGE_FIRMWARE/knobs.h | 10 - SENSORY_BRIDGE_FIRMWARE/led_utilities.h | 73 +++++++- SENSORY_BRIDGE_FIRMWARE/lightshow_modes.h | 172 +++++++++++------- SENSORY_BRIDGE_FIRMWARE/noise_cal.h | 10 +- SENSORY_BRIDGE_FIRMWARE/serial_menu.h | 61 +++++-- SENSORY_BRIDGE_FIRMWARE/system.h | 17 +- SENSORY_BRIDGE_FIRMWARE/utilities.h | 23 +-- 12 files changed, 385 insertions(+), 231 deletions(-) diff --git a/SENSORY_BRIDGE_FIRMWARE/GDFT.h b/SENSORY_BRIDGE_FIRMWARE/GDFT.h index 205f6b1..8252217 100644 --- a/SENSORY_BRIDGE_FIRMWARE/GDFT.h +++ b/SENSORY_BRIDGE_FIRMWARE/GDFT.h @@ -57,6 +57,9 @@ // Obscure audio magic happens here void IRAM_ATTR process_GDFT() { + static bool interlace_flip = false; + interlace_flip = !interlace_flip; // Switch field every frame on lower notes to save execution time + // Reset magnitude caps every frame for (uint8_t i = 0; i < NUM_ZONES; i++) { max_mags[i] = CONFIG.MAGNITUDE_FLOOR; // Higher than the average noise floor @@ -71,26 +74,45 @@ void IRAM_ATTR process_GDFT() { // Run GDFT (Goertzel-based Discrete Fourier Transform) with 64 frequencies // Fixed-point code adapted from example here: https://sourceforge.net/p/freetel/code/HEAD/tree/misc/goertzal/goertzal.c for (uint16_t i = 0; i < NUM_FREQS; i++) { // Run 64 times - int32_t q0, q1, q2; - int64_t mult; - - q1 = 0; - q2 = 0; + bool field = bitRead(i, 0); // odd or even + + // If note is part of field being rendered, is >= index 16, or is the first note + if (field == interlace_flip || i >= 16) { + int32_t q0, q1, q2; + int64_t mult; + + q1 = 0; + q2 = 0; + + float window_pos = 0.0; + for (uint16_t n = 0; n < frequencies[i].block_size; n++) { // Run Goertzel for "block_size" iterations + int32_t sample = 0; + sample = ((int32_t)sample_window[SAMPLE_HISTORY_LENGTH - 1 - n] * (int32_t)window_lookup[uint16_t(window_pos)]) >> 16; + mult = (int64_t)frequencies[i].coeff_q14 * (int64_t)q1; + q0 = (sample >> 6) + (mult >> 14) - q2; + q2 = q1; + q1 = q0; + + window_pos += frequencies[i].window_mult; + } - for (uint16_t n = 0; n < frequencies[i].block_size; n++) { // Run Goertzel for "block_size" iterations mult = (int64_t)frequencies[i].coeff_q14 * (int64_t)q1; - q0 = (sample_window[SAMPLE_HISTORY_LENGTH - 1 - n] >> 6) + (mult >> 14) - q2; - q2 = q1; - q1 = q0; - } + magnitudes[i] = q2 * q2 + q1 * q1 - ((int32_t)(mult >> 14)) * q2; // Calculate raw magnitudes - mult = (int64_t)frequencies[i].coeff_q14 * (int64_t)q1; - magnitudes[i] = q2 * q2 + q1 * q1 - ((int32_t)(mult >> 14)) * q2; // Calculate raw magnitudes + // Normalize output + magnitudes[i] *= float(frequencies[i].block_size_recip); - magnitudes[i] *= float(frequencies[i].block_size_recip); // Normalize output + // Scale magnitudes this way to help even out the response curve further + float prog = i / float(NUM_FREQS); + prog *= prog; + if (prog < 0.1) { + prog = 0.1; + } + magnitudes[i] *= prog; - if (magnitudes[i] < 0.0) { // Prevent negative values - magnitudes[i] = 0.0; + if (magnitudes[i] < 0.0) { // Prevent negative values + magnitudes[i] = 0.0; + } } } @@ -112,7 +134,7 @@ void IRAM_ATTR process_GDFT() { // Gather noise data if noise_complete == false if (noise_complete == false) { for (uint8_t i = 0; i < NUM_FREQS; i += 1) { - if (magnitudes[i] > noise_samples[i]) { + if (magnitudes[i] > noise_samples[i]) { noise_samples[i] = magnitudes[i]; } } @@ -129,7 +151,7 @@ void IRAM_ATTR process_GDFT() { // Apply noise reduction data, estimate max values for (uint8_t i = 0; i < NUM_FREQS; i += 1) { if (noise_complete == true) { - magnitudes[i] -= noise_samples[i] * 1.5; // Treat noise 1.5x louder than calibration + magnitudes[i] -= noise_samples[i] * 1.2; // Treat noise 1.2x louder than calibration if (magnitudes[i] < 0.0) { magnitudes[i] = 0.0; } @@ -214,12 +236,12 @@ void IRAM_ATTR process_GDFT() { for (uint8_t i = 0; i < NUM_FREQS; i += 1) { if (mag_targets[i] > mag_followers[i]) { float delta = mag_targets[i] - mag_followers[i]; - mag_followers[i] += delta * (smoothing_follower * 0.5); + mag_followers[i] += delta * (smoothing_follower * 0.45); } else if (mag_targets[i] < mag_followers[i]) { float delta = mag_followers[i] - mag_targets[i]; - mag_followers[i] -= delta * (smoothing_follower * 0.375); + mag_followers[i] -= delta * (smoothing_follower * 0.55); } } @@ -228,11 +250,11 @@ void IRAM_ATTR process_GDFT() { for (uint8_t i = 0; i < NUM_ZONES; i++) { if (max_mags[i] > max_mags_followers[i]) { float delta = max_mags[i] - max_mags_followers[i]; - max_mags_followers[i] += delta * 0.075; + max_mags_followers[i] += delta * 0.05; } if (max_mags[i] < max_mags_followers[i]) { float delta = max_mags_followers[i] - max_mags[i]; - max_mags_followers[i] -= delta * 0.075; + max_mags_followers[i] -= delta * 0.05; } } @@ -264,8 +286,8 @@ void IRAM_ATTR process_GDFT() { mag_float = mag_float * (1.0 - smoothing_exp_average) + mag_float_last[i] * smoothing_exp_average; mag_float_last[i] = mag_float; - mag_float *= CONFIG.GAIN; - if(mag_float > 1.0){ + mag_float *= (CONFIG.GAIN); + if (mag_float > 1.0) { mag_float = 1.0; } diff --git a/SENSORY_BRIDGE_FIRMWARE/SENSORY_BRIDGE_FIRMWARE.ino b/SENSORY_BRIDGE_FIRMWARE/SENSORY_BRIDGE_FIRMWARE.ino index 7fa5b1d..95d447e 100644 --- a/SENSORY_BRIDGE_FIRMWARE/SENSORY_BRIDGE_FIRMWARE.ino +++ b/SENSORY_BRIDGE_FIRMWARE/SENSORY_BRIDGE_FIRMWARE.ino @@ -50,7 +50,7 @@ ---------------------------------------------------------------------*/ -#define FIRMWARE_VERSION 30001 // Try "V" on the Serial port for this! +#define FIRMWARE_VERSION 30100 // Try "V" on the Serial port for this! // Mm P M = Major version, m = Minor version, P = Patch version // (i.e 3.5.4 would be 30504) @@ -98,6 +98,9 @@ enum lightshow_modes { // Setup, runs only one time --------------------------------------------------------- void setup() { init_system(); // (system.h) Initialize all hardware and arrays + + // Create thread specifically for LED updates + xTaskCreatePinnedToCore(led_thread, "led_task", 4096, NULL, tskIDLE_PRIORITY + 1, &led_task, 1); } // Loop, runs forever after setup() -------------------------------------------------- @@ -142,13 +145,9 @@ void loop() { lookahead_smoothing(); // (GDFT.h) // Peek at upcoming frames to study/prevent flickering - function_id = 9; - render_leds(t_now_us); // (BELOW in this file) - // Convert the data we found into a beautiful show - - function_id = 10; + function_id = 8; log_fps(t_now_us); // (system.h) - // Log the current FPS + // Log the audio system FPS if (debug_mode == true) { function_id = 31; @@ -158,46 +157,58 @@ void loop() { yield(); // Otherwise the ESP32 will collapse into a black hole or something } -// Run the lights! ------------------------------------------------------------------- -void render_leds(uint32_t t_now_us) { - if (noise_complete == true) { // If we're not gathering ambient noise data - if (mode_transition_queued == true || noise_transition_queued == true) { // If transition queued - run_transition_fade(); // (led_utilities.h) Fade to black between modes - } +// Run the lights in their own thread! ------------------------------------------------------------- +void led_thread(void* arg) { + while (true) { + if (led_thread_halt == false) { + if (noise_complete == true) { // If we're not gathering ambient noise data + if (mode_transition_queued == true || noise_transition_queued == true) { // If transition queued + run_transition_fade(); // (led_utilities.h) Fade to black between modes + } + + // Based on the value of CONFIG.LIGHTSHOW_MODE, we call a + // different rendering function from lightshow_modes.h: + + if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_GDFT) { + light_mode_gdft(); // (lightshow_modes.h) GDFT spectrogram display + } + else if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_GDFT_CHROMAGRAM) { + light_mode_gdft_chromagram(); // (lightshow_modes.h) GDFT chromagram display + } + else if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_BLOOM) { + light_mode_bloom(false); // (lightshow_modes.h) Bloom Mode display + } + else if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_BLOOM_FAST) { + light_mode_bloom(true); // (lightshow_modes.h) Bloom Mode display (faster) + } + else if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_VU) { + light_mode_vu(); // (lightshow_modes.h) VU Mode display + } + else if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_VU_DOT) { + light_mode_vu_dot(); // (lightshow_modes.h) VU Mode display (dot version) + } + + if (CONFIG.MIRROR_ENABLED) { // Mirroring logic + // Don't scale Bloom Mode before mirroring + if (CONFIG.LIGHTSHOW_MODE != LIGHT_MODE_BLOOM && CONFIG.LIGHTSHOW_MODE != LIGHT_MODE_BLOOM_FAST) { + scale_image_to_half(); // (led_utilities.h) Image is now 50% height + } + shift_leds_up(64); // (led_utilities.h) Move image up one half + mirror_image_downwards(); // (led_utilities.h) Mirror downwards + } + } else { + noise_cal_led_readout(); // (noise_cal.h) Show the noise profile and progress during calibration + } - // Based on the value of CONFIG.LIGHTSHOW_MODE, we call a - // different rendering function from lightshow_modes.h: - - if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_GDFT) { - light_mode_gdft(); // (lightshow_modes.h) GDFT spectrogram display - } - else if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_GDFT_CHROMAGRAM) { - light_mode_gdft_chromagram(); // (lightshow_modes.h) GDFT chromagram display - } - else if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_BLOOM) { - light_mode_bloom(false); // (lightshow_modes.h) Bloom Mode display - } - else if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_BLOOM_FAST) { - light_mode_bloom(true); // (lightshow_modes.h) Bloom Mode display (faster) - } - else if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_VU) { - light_mode_vu(); // (lightshow_modes.h) VU Mode display - } - else if (CONFIG.LIGHTSHOW_MODE == LIGHT_MODE_VU_DOT) { - light_mode_vu_dot(); // (lightshow_modes.h) VU Mode display (dot version) + show_leds(); // This sends final RGB data to the LEDS (led_utilities.h) + LED_FPS = FastLED.getFPS(); } - if (CONFIG.MIRROR_ENABLED) { // Mirroring logic - // Don't scale Bloom Mode before mirroring - if (CONFIG.LIGHTSHOW_MODE != LIGHT_MODE_BLOOM && CONFIG.LIGHTSHOW_MODE != LIGHT_MODE_BLOOM_FAST) { - scale_image_to_half(); // (led_utilities.h) Image is now 50% height - } - shift_leds_up(64); // (led_utilities.h) Move image up one half - mirror_image_downwards(); // (led_utilities.h) Mirror downwards + if (CONFIG.LED_TYPE == LED_NEOPIXEL) { + vTaskDelay(1); // delay for 1ms to avoid hogging the CPU + } + else if (CONFIG.LED_TYPE == LED_DOTSTAR) { // More delay to compensate for faster LEDs + vTaskDelay(3); // delay for 3ms to avoid hogging the CPU } - } else { - noise_cal_led_readout(); // (noise_cal.h) Show the noise profile and progress during calibration } - - show_leds(); // This sends final RGB data to the LEDS (led_utilities.h) } diff --git a/SENSORY_BRIDGE_FIRMWARE/bridge_fs.h b/SENSORY_BRIDGE_FIRMWARE/bridge_fs.h index b2f4eb2..25f7190 100644 --- a/SENSORY_BRIDGE_FIRMWARE/bridge_fs.h +++ b/SENSORY_BRIDGE_FIRMWARE/bridge_fs.h @@ -88,29 +88,10 @@ void load_config() { config_buffer[i] = file.read(); } - // Temporary buffer is used to compare saved config version with this firmware versions' defaults. - // If a leftover config file version is a mismatch with the current firmware version, the config - // and noise_cal files need to be factory reset, to avoid reading potentially corrupt values - conf CONFIG_TEMP; - memcpy(&CONFIG_TEMP, config_buffer, sizeof(CONFIG_TEMP)); + memcpy(&CONFIG, config_buffer, sizeof(CONFIG)); if (debug_mode) { - USBSerial.print("STORED VERSION IS: "); - USBSerial.println(CONFIG_TEMP.VERSION); - - USBSerial.print("CURRENT VERSION IS: "); - USBSerial.println(CONFIG_DEFAULTS.VERSION); - } - - if (CONFIG_TEMP.VERSION != CONFIG_DEFAULTS.VERSION) { - if(debug_mode){USBSerial.println("STORED CONFIG FILE IS OUTDATED!");} - //USBSerial.println("Factory resetting now to avoid potentially incompatible data!"); - //save_backup_config(); - //queue_factory_reset = true; - } - else { - memcpy(&CONFIG, &CONFIG_TEMP, sizeof(CONFIG_TEMP)); - if(debug_mode){USBSerial.println("READ CONFIG SUCCESSFULLY");} + USBSerial.println("READ CONFIG SUCCESSFULLY"); } } file.close(); @@ -122,10 +103,14 @@ void load_config() { // Save noise calibration to LittleFS void save_ambient_noise_calibration() { - if(debug_mode){USBSerial.print("SAVING AMBIENT_NOISE PROFILE... ");} + if (debug_mode) { + USBSerial.print("SAVING AMBIENT_NOISE PROFILE... "); + } File file = LittleFS.open("/noise_cal.bin", FILE_WRITE); if (!file) { - if(debug_mode){USBSerial.println("Failed to open file for writing!");} + if (debug_mode) { + USBSerial.println("Failed to open file for writing!"); + } return; } @@ -144,15 +129,21 @@ void save_ambient_noise_calibration() { } file.close(); - if(debug_mode){USBSerial.println("SAVE COMPLETE");} + if (debug_mode) { + USBSerial.println("SAVE COMPLETE"); + } } // Load noise calibration from LittleFS void load_ambient_noise_calibration() { - if(debug_mode){USBSerial.print("LOADING AMBIENT_NOISE PROFILE... ");} + if (debug_mode) { + USBSerial.print("LOADING AMBIENT_NOISE PROFILE... "); + } File file = LittleFS.open("/noise_cal.bin", FILE_READ); if (!file) { - if(debug_mode){USBSerial.println("Failed to open file for reading!");} + if (debug_mode) { + USBSerial.println("Failed to open file for reading!"); + } return; } @@ -169,7 +160,9 @@ void load_ambient_noise_calibration() { } file.close(); - if(debug_mode){USBSerial.println("LOAD COMPLETE");} + if (debug_mode) { + USBSerial.println("LOAD COMPLETE"); + } } // Initialize LittleFS diff --git a/SENSORY_BRIDGE_FIRMWARE/constants.h b/SENSORY_BRIDGE_FIRMWARE/constants.h index 281dbe4..768ea6b 100644 --- a/SENSORY_BRIDGE_FIRMWARE/constants.h +++ b/SENSORY_BRIDGE_FIRMWARE/constants.h @@ -1,8 +1,8 @@ // AUDIO ####################################################### #define SERIAL_BAUD 230400 -#define DEFAULT_SAMPLE_RATE 24400 -#define SAMPLE_HISTORY_LENGTH 2048 +#define DEFAULT_SAMPLE_RATE 12200 +#define SAMPLE_HISTORY_LENGTH 4096 // Don't change this unless you're willing to do a lot of other work on the code :/ #define NATIVE_RESOLUTION 128 @@ -46,6 +46,17 @@ const float notes[] = { // OTHER ####################################################### +const float dither_table[8] = { + 0.125, + 0.250, + 0.375, + 0.500, + 0.625, + 0.750, + 0.875, + 1.000 +}; + #define SWEET_SPOT_LEFT_CHANNEL 0 #define SWEET_SPOT_CENTER_CHANNEL 1 #define SWEET_SPOT_RIGHT_CHANNEL 2 diff --git a/SENSORY_BRIDGE_FIRMWARE/globals.h b/SENSORY_BRIDGE_FIRMWARE/globals.h index 350f96d..feed37d 100644 --- a/SENSORY_BRIDGE_FIRMWARE/globals.h +++ b/SENSORY_BRIDGE_FIRMWARE/globals.h @@ -30,8 +30,7 @@ struct conf { bool REVERSE_ORDER; bool IS_MAIN_UNIT; uint32_t MAX_CURRENT_MA; - - uint32_t VERSION; + bool TEMPORAL_DITHERING; }; // ------------------------------------------------------------ @@ -49,13 +48,13 @@ conf CONFIG = { DEFAULT_SAMPLE_RATE, // SAMPLE_RATE 12, // NOTE_OFFSET 1, // SQUARE_ITER - 1000, // MAGNITUDE_FLOOR + 20, // MAGNITUDE_FLOOR LED_NEOPIXEL, // LED_TYPE 128, // LED_COUNT GRB, // LED_COLOR_ORDER true, // LED_INTERPOLATION - 1600, // MAX_BLOCK_SIZE - 256, // SAMPLES_PER_CHUNK + 1500, // MAX_BLOCK_SIZE + 128, // SAMPLES_PER_CHUNK 1.0, // GAIN true, // BOOT_ANIMATION 750, // SWEET_SPOT_MIN_LEVEL @@ -66,8 +65,7 @@ conf CONFIG = { false, // REVERSE_ORDER false, // IS_MAIN_UNIT 1500, // MAX_CURRENT_MA - - FIRMWARE_VERSION, // VERSION + true, // TEMPORAL_DITHERING }; conf CONFIG_DEFAULTS; // Used for resetting to default values at runtime @@ -93,7 +91,7 @@ freq frequencies[NUM_FREQS]; // ------------------------------------------------------------ // Hann window lookup table (generated in system.h) ----------- -int16_t window_lookup[2048] = { 0 }; +float window_lookup[4096] = { 0 }; // ------------------------------------------------------------ // A-weighting lookup table (parsed in system.h) -------------- @@ -171,13 +169,18 @@ CRGB leds_fade[128]; CRGB *leds_out; +uint8_t dither_step = 0; +bool led_thread_halt = false; +TaskHandle_t led_task; + // ------------------------------------------------------------ // Benchmarking (system.h) ------------------------------------ Ticker cpu_usage; volatile uint16_t function_id = 0; volatile uint16_t function_hits[32] = {0}; -float FPS = 0.0; +float SYSTEM_FPS = 0.0; +float LED_FPS = 0.0; // ------------------------------------------------------------ // SensorySync P2P network (p2p.h) ---------------------------- diff --git a/SENSORY_BRIDGE_FIRMWARE/knobs.h b/SENSORY_BRIDGE_FIRMWARE/knobs.h index 8a045ce..d7c17bb 100644 --- a/SENSORY_BRIDGE_FIRMWARE/knobs.h +++ b/SENSORY_BRIDGE_FIRMWARE/knobs.h @@ -94,16 +94,6 @@ void check_knobs(uint32_t t_now) { chromatic_mode = true; } - // This is only used to fade in when booting! - if(t_now >= 1000 && noise_transition_queued == false && mode_transition_queued == false){ - if(MASTER_BRIGHTNESS < 1.0){ - MASTER_BRIGHTNESS += 0.01; - } - if(MASTER_BRIGHTNESS > 1.0){ - MASTER_BRIGHTNESS = 1.00; - } - } - // Mood knob processing float smoothing_top_half = (CONFIG.MOOD - 0.5); if (smoothing_top_half < 0.0) { diff --git a/SENSORY_BRIDGE_FIRMWARE/led_utilities.h b/SENSORY_BRIDGE_FIRMWARE/led_utilities.h index 6a9e272..9faa2ec 100644 --- a/SENSORY_BRIDGE_FIRMWARE/led_utilities.h +++ b/SENSORY_BRIDGE_FIRMWARE/led_utilities.h @@ -69,6 +69,21 @@ CRGB lerp_led(float index, CRGB* led_array) { } void show_leds() { + dither_step++; + if (dither_step >= 8) { + dither_step = 0; + } + + // This is only used to fade in when booting! + if (millis() >= 1000 && noise_transition_queued == false && mode_transition_queued == false) { + if (MASTER_BRIGHTNESS < 1.0) { + MASTER_BRIGHTNESS += 0.005; + } + if (MASTER_BRIGHTNESS > 1.0) { + MASTER_BRIGHTNESS = 1.00; + } + } + if (CONFIG.LED_COUNT == NATIVE_RESOLUTION) { memcpy(leds_out, leds, sizeof(leds)); } else { // If not native resolution, use interpolation if enabled @@ -92,13 +107,15 @@ void show_leds() { } } - if(CONFIG.REVERSE_ORDER == true){ + if (CONFIG.REVERSE_ORDER == true) { reverse_leds(leds_out, CONFIG.LED_COUNT); } - + // PHOTONS knob is squared and applied here: FastLED.setBrightness((255 * MASTER_BRIGHTNESS) * (CONFIG.PHOTONS * CONFIG.PHOTONS) * silent_scale); + FastLED.setDither(CONFIG.TEMPORAL_DITHERING); FastLED.show(); + //USBSerial.println(MASTER_BRIGHTNESS); } void init_leds() { @@ -143,16 +160,22 @@ void init_leds() { USBSerial.println(leds_started == true ? PASS : FAIL); } +void save_leds_to_aux() { + memcpy(leds_aux, leds, sizeof(leds)); +} + +void load_leds_from_aux() { + memcpy(leds, leds_aux, sizeof(leds)); +} + void save_leds_to_last() { memcpy(leds_last, leds, sizeof(leds)); } - void load_leds_from_last() { memcpy(leds, leds_last, sizeof(leds)); } - void save_leds_to_temp() { memcpy(leds_temp, leds, sizeof(leds)); } @@ -163,6 +186,7 @@ void load_leds_from_temp() { } void blocking_flash(CRGB col) { + led_thread_halt = true; for (uint8_t i = 0; i < NATIVE_RESOLUTION; i++) { leds[i] = CRGB(0, 0, 0); } @@ -173,14 +197,16 @@ void blocking_flash(CRGB col) { leds[i] = col; } show_leds(); - delay(150); + FastLED.delay(150); for (uint8_t i = 0; i < NATIVE_RESOLUTION; i++) { leds[i] = CRGB(0, 0, 0); } show_leds(); - delay(150); + FastLED.delay(150); } + + led_thread_halt = false; } void clear_all_led_buffers() { @@ -259,7 +285,7 @@ void intro_animation() { leds[i] = out_col; } show_leds(); - delay(4); + FastLED.delay(3); } clear_all_led_buffers(); @@ -326,7 +352,7 @@ void intro_animation() { } } show_leds(); - delay(5); + FastLED.delay(6); } MASTER_BRIGHTNESS = 0.0; ledcWrite(SWEET_SPOT_LEFT_CHANNEL, 0); @@ -336,7 +362,7 @@ void intro_animation() { void run_transition_fade() { if (MASTER_BRIGHTNESS > 0.0) { - MASTER_BRIGHTNESS -= 0.05; + MASTER_BRIGHTNESS -= 0.02; if (MASTER_BRIGHTNESS < 0.0) { MASTER_BRIGHTNESS = 0.0; @@ -408,3 +434,32 @@ void fade_top_half(bool shifted = false) { leds[(NATIVE_RESOLUTION - 1 - i) + shift].b *= fade; } } + +// Draws anti-aliased 1-D lines using Xiaolin Wu's algorithm onto a CRGB array +// Start and end positions are floating point for subpixel positioning +void draw_line(CRGB* led_array, CRGB line_color, float start_pos, float end_pos) { + // Get the start and end positions as integers + int16_t x0 = max((int)start_pos, 0); + int16_t x1 = min((int)end_pos, NATIVE_RESOLUTION - 1); + float gradient = (float)(line_color.r - line_color.b) / (x1 - x0); + + // Determine whether the line is going left or right + if (x0 > x1) { + // If the line is going left, swap the start and end positions + int16_t temp = x0; + x0 = x1; + x1 = temp; + } + + // Iterate over the pixels of the line + for (uint16_t x = x0; x <= x1; x++) { + float t = (x - x0) / (float)(x1 - x0); + int16_t y = (int16_t)(line_color.b + gradient * (x - x0)); + // determine the coverage of the current pixel + uint16_t coverage = (uint16_t)((1 - t) * 8); + + // Blend the line color with the background color + CRGB blended_color = blend(line_color, led_array[x], coverage); + led_array[x] = blended_color; + } +} diff --git a/SENSORY_BRIDGE_FIRMWARE/lightshow_modes.h b/SENSORY_BRIDGE_FIRMWARE/lightshow_modes.h index b5394a3..73fc471 100644 --- a/SENSORY_BRIDGE_FIRMWARE/lightshow_modes.h +++ b/SENSORY_BRIDGE_FIRMWARE/lightshow_modes.h @@ -3,28 +3,40 @@ void light_mode_gdft() { for (uint8_t i = 0; i < NUM_FREQS; i += 1) { // 64 freqs float bin = note_spectrogram_smooth[i]; for (uint8_t s = 0; s < CONFIG.SQUARE_ITER; s++) { - bin = bin * bin; + bin = (bin * bin); + } + + float led_brightness_raw = 254 * bin; // -1 for temporal dithering below + uint16_t led_brightness = led_brightness_raw; + float fract = led_brightness_raw - led_brightness; + + if (CONFIG.TEMPORAL_DITHERING == true) { + if (fract >= dither_table[dither_step]) { + led_brightness += 1; + } } - uint16_t led_brightness = 255 * bin; brightness_levels[i] = led_brightness; // Can use this value later if needed - float led_hue; + float led_hue_a; + float led_hue_b; if (chromatic_mode == true) { - led_hue = 21.33333333 * i; // Makes hue completely cycle once per octave + led_hue_a = 21.33333333 * i; // Makes hue completely cycle once per octave + led_hue_b = led_hue_a + 10.66666666; } else { - led_hue = 255 * chroma_val; // User color selection + led_hue_a = 255 * chroma_val; // User color selection + led_hue_b = led_hue_a; } CRGB col1 = CRGB(0, 0, 0); hsv2rgb_spectrum( // Spectrum has better low-light color resolution than default "rainbow" HSV behavior - CHSV(led_hue, 255, brightness_levels[i]), + CHSV(led_hue_a, 255, brightness_levels[i]), col1); CRGB col2 = CRGB(0, 0, 0); hsv2rgb_spectrum( // Spectrum has better low-light color resolution than default "rainbow" HSV behavior - CHSV(led_hue + 10.66666666, 255, brightness_levels[i]), + CHSV(led_hue_b, 255, brightness_levels[i]), col2); leds[i * 2 + 0] = col1; // Two LEDs at a time so that mirror mode works gracefully @@ -42,6 +54,16 @@ void light_mode_gdft_chromagram() { bin = bin * bin; } + float led_brightness_raw = 254 * bin; // -1 for temporal dithering below + uint16_t led_brightness = led_brightness_raw; + float fract = led_brightness_raw - led_brightness; + + if (CONFIG.TEMPORAL_DITHERING == true) { + if (fract >= dither_table[dither_step]) { + led_brightness += 1; + } + } + float led_hue; if (chromatic_mode == true) { led_hue = 255 * prog; @@ -50,7 +72,10 @@ void light_mode_gdft_chromagram() { led_hue = 255 * chroma_val; } - leds[i] = CHSV(led_hue, 255, 255 * bin); + hsv2rgb_spectrum( // Spectrum has better low-light color resolution than default "rainbow" HSV behavior + CHSV(led_hue, 255, led_brightness), + leds[i] + ); } } @@ -60,69 +85,76 @@ void light_mode_bloom(bool fast_scroll) { iter++; - CRGB sum_color = CRGB(0, 0, 0); - float brightness_sum = 0.0; - for (uint8_t i = 0; i < 12; i++) { - float prog = i / float(12); + if (bitRead(iter, 0) == 0) { + CRGB sum_color = CRGB(0, 0, 0); + float brightness_sum = 0.0; + for (uint8_t i = 0; i < 12; i++) { + float prog = i / float(12); - float bin = note_chromagram[i]; // * (1.0 / chromagram_max_val); + float bin = note_chromagram[i]; // * (1.0 / chromagram_max_val); - float bright = bin; - for (uint8_t s = 0; s < CONFIG.SQUARE_ITER; s++) { - bright *= bright; - } - bright *= 1.5; - if (bright > 1.0) { - bright = 1.0; - } + float bright = bin; + for (uint8_t s = 0; s < CONFIG.SQUARE_ITER; s++) { + bright *= bright; + } + bright *= 1.5; + if (bright > 1.0) { + bright = 1.0; + } - bright *= led_share; + bright *= led_share; - CRGB out_col; - if (chromatic_mode == true) { - hsv2rgb_spectrum( - CHSV(255 * prog, 255, bright), - out_col); - } - else { - brightness_sum += bright; + CRGB out_col; + if (chromatic_mode == true) { + hsv2rgb_spectrum( + CHSV(255 * prog, 255, bright), + out_col); + } + else { + brightness_sum += bright; + } + + sum_color += out_col; } - sum_color += out_col; - } + if (chromatic_mode == false) { + hsv2rgb_spectrum( + CHSV(255 * chroma_val, 255, brightness_sum), + sum_color); + } - if (chromatic_mode == false) { - hsv2rgb_spectrum( - CHSV(255 * chroma_val, 255, brightness_sum), - sum_color); - } + if (fast_scroll == true) { // Fast mode scrolls two LEDs at a time + for (uint8_t i = 0; i < NATIVE_RESOLUTION - 2; i++) { + leds_temp[(NATIVE_RESOLUTION - 1) - i] = leds_last[(NATIVE_RESOLUTION - 1) - i - 2]; + } - if (fast_scroll == true) { // Fast mode scrolls two LEDs at a time - for (uint8_t i = 0; i < NATIVE_RESOLUTION - 2; i++) { - leds_temp[(NATIVE_RESOLUTION - 1) - i] = leds_last[(NATIVE_RESOLUTION - 1) - i - 2]; + leds_temp[0] = sum_color; // New information goes here + leds_temp[1] = sum_color; // New information goes here } + else { // Slow mode only scrolls one LED at a time + for (uint8_t i = 0; i < NATIVE_RESOLUTION - 1; i++) { + leds_temp[(NATIVE_RESOLUTION - 1) - i] = leds_last[(NATIVE_RESOLUTION - 1) - i - 1]; + } - leds_temp[0] = sum_color; // New information goes here - leds_temp[1] = sum_color; // New information goes here - } - else { // Slow mode only scrolls one LED at a time - for (uint8_t i = 0; i < NATIVE_RESOLUTION - 1; i++) { - leds_temp[(NATIVE_RESOLUTION - 1) - i] = leds_last[(NATIVE_RESOLUTION - 1) - i - 1]; + leds_temp[0] = sum_color; // New information goes here } - leds_temp[0] = sum_color; // New information goes here - } + load_leds_from_temp(); + save_leds_to_last(); - load_leds_from_temp(); - save_leds_to_last(); + //fadeToBlackBy(leds, 128, 255-255*waveform_peak_scaled); - //fadeToBlackBy(leds, 128, 255-255*waveform_peak_scaled); + distort_logarithmic(); + //distort_exponential(); - distort_logarithmic(); - //distort_exponential(); + fade_top_half(CONFIG.MIRROR_ENABLED); // fade at different location depending if mirroring is enabled + increase_saturation(32); - fade_top_half(CONFIG.MIRROR_ENABLED); // fade at different location depending if mirroring is enabled - increase_saturation(32); + save_leds_to_aux(); + } + else{ + load_leds_from_aux(); + } } void light_mode_waveform() { @@ -170,9 +202,9 @@ void light_mode_waveform() { } /* - CHSV hsv = rgb2hsv_approximate(sum_color); - hsv.s = qadd8(hsv.s, 64); - sum_color = hsv; + CHSV hsv = rgb2hsv_approximate(sum_color); + hsv.s = qadd8(hsv.s, 64); + sum_color = hsv; */ float sum_color_float[3] = {float(sum_color.r), float(sum_color.g), float(sum_color.b)}; @@ -231,7 +263,7 @@ void light_mode_vu() { const float led_share = 255 / float(12); static float sum_color_last[3] = {0, 0, 0}; - float smoothing = (0.1 + CONFIG.MOOD * 0.9) * 0.25; + float smoothing = (0.025 + CONFIG.MOOD * 0.975) * 0.25; float led_pos = waveform_peak_scaled * (NATIVE_RESOLUTION - 1); static float led_pos_smooth = 0.0; @@ -244,6 +276,9 @@ void light_mode_vu() { led_pos_smooth = 0; } + uint16_t led_pos_smooth_whole = led_pos_smooth; + float fract = led_pos_smooth - led_pos_smooth_whole; + CRGB sum_color = CRGB(0, 0, 0); if (chromatic_mode == true) { for (uint8_t c = 0; c < 12; c++) { @@ -279,13 +314,20 @@ void light_mode_vu() { for (uint8_t i = 0; i < NATIVE_RESOLUTION; i++) { leds[i] = CRGB(0, 0, 0); - if (i <= led_pos_smooth) { + if (i < led_pos_smooth) { leds[i] = CRGB( sum_color_float[0], sum_color_float[1], sum_color_float[2] ); } + else if (i == led_pos_smooth) { + leds[i] = CRGB( + sum_color_float[0] * fract, + sum_color_float[1] * fract, + sum_color_float[2] * fract + ); + } } } @@ -294,14 +336,14 @@ void light_mode_vu_dot() { static float sum_color_last[3] = {0, 0, 0}; static float led_pos_last = 0; - float smoothing = (0.1 + CONFIG.MOOD * 0.9) * 0.25; + float smoothing = (0.025 + CONFIG.MOOD * 0.975) * 0.25; float led_pos = waveform_peak_scaled * (NATIVE_RESOLUTION - 1); static float led_pos_smooth = 0.0; led_pos_smooth = led_pos * (smoothing) + led_pos_smooth * (1.0 - smoothing); - if (led_pos_smooth > NATIVE_RESOLUTION-2) { - led_pos_smooth = NATIVE_RESOLUTION-2; + if (led_pos_smooth > NATIVE_RESOLUTION - 2) { + led_pos_smooth = NATIVE_RESOLUTION - 2; } else if (led_pos_smooth < 0) { led_pos_smooth = 0; @@ -340,9 +382,7 @@ void light_mode_vu_dot() { sum_color_last[1] = sum_color_float[1]; sum_color_last[2] = sum_color_float[2]; - for (uint8_t i = 0; i < NATIVE_RESOLUTION; i++) { - leds[i] = CRGB(0, 0, 0); - } + fadeToBlackBy(leds, NATIVE_RESOLUTION, 255); if (led_pos_last < led_pos_smooth) { for (uint8_t i = led_pos_last; i <= led_pos_smooth; i++) { diff --git a/SENSORY_BRIDGE_FIRMWARE/noise_cal.h b/SENSORY_BRIDGE_FIRMWARE/noise_cal.h index 854c318..7051c33 100644 --- a/SENSORY_BRIDGE_FIRMWARE/noise_cal.h +++ b/SENSORY_BRIDGE_FIRMWARE/noise_cal.h @@ -26,18 +26,17 @@ void clear_noise_cal() { void noise_cal_led_readout(){ float noise_cal_progress = noise_iterations / 256.0; - uint8_t prog_led_index = 128*noise_cal_progress; + uint8_t prog_led_index = NATIVE_RESOLUTION*noise_cal_progress; float max_val = 0.0; for (uint16_t i = 0; i < NUM_FREQS; i++) { if(noise_samples[i] > max_val){ max_val = noise_samples[i]; } } - for (uint16_t i = 0; i < 128; i++) { + for (uint16_t i = 0; i < NATIVE_RESOLUTION; i++) { if(i < prog_led_index){ float led_level = noise_samples[i>>1] / max_val; - CRGB out_col = CHSV(220, 255, 255*led_level); - leds[i] = out_col; + leds[i] = CHSV(220, 255, 255*led_level); } else if(i == prog_led_index){ leds[i] = CRGB(0,255,255); @@ -51,7 +50,6 @@ void noise_cal_led_readout(){ uint16_t iters_left = 64-(noise_iterations-192); float brightness_level = iters_left / 64.0; brightness_level*=brightness_level; - MASTER_BRIGHTNESS = brightness_level; - FastLED.setBrightness(255*MASTER_BRIGHTNESS); + MASTER_BRIGHTNESS = brightness_level; } } diff --git a/SENSORY_BRIDGE_FIRMWARE/serial_menu.h b/SENSORY_BRIDGE_FIRMWARE/serial_menu.h index 07c8dd5..a84e650 100644 --- a/SENSORY_BRIDGE_FIRMWARE/serial_menu.h +++ b/SENSORY_BRIDGE_FIRMWARE/serial_menu.h @@ -10,7 +10,7 @@ extern void reboot(); // system.h void tx_begin(bool error = false) { if (error == false) { - USBSerial.println("sbr{{"); + USBSerial.println("sbr{{"); } else { USBSerial.println("sberr[["); @@ -183,8 +183,8 @@ void dump_info() { USBSerial.print("CONFIG.MAX_CURRENT_MA: "); USBSerial.println(CONFIG.MAX_CURRENT_MA); - USBSerial.print("CONFIG.VERSION: "); - USBSerial.println(CONFIG.VERSION); + USBSerial.print("CONFIG.TEMPORAL_DITHERING: "); + USBSerial.println(CONFIG.TEMPORAL_DITHERING); USBSerial.print("MASTER_BRIGHTNESS: "); USBSerial.println(MASTER_BRIGHTNESS); @@ -240,8 +240,11 @@ void dump_info() { USBSerial.print("mode_destination: "); USBSerial.println(mode_destination); - USBSerial.print("AVERAGE FPS: "); - USBSerial.println(FPS); + USBSerial.print("SYSTEM_FPS: "); + USBSerial.println(SYSTEM_FPS); + + USBSerial.print("LED_FPS: "); + USBSerial.println(LED_FPS); } // This parses a completed command to decide how to handle it @@ -272,7 +275,8 @@ void parse_command(char* command_buf) { USBSerial.println(" get_main_unit | Print if this unit is set to MAIN for SensorySync"); USBSerial.println(" dump | Print tons of useful variables in realtime"); USBSerial.println(" stop | Stops the output of any enabled streams"); - USBSerial.println(" fps | Return the average FPS"); + USBSerial.println(" fps | Return the system FPS"); + USBSerial.println(" led_fps | Return the LED FPS"); USBSerial.println(" chip_id | Return the chip id (MAC) of the CPU"); USBSerial.println(" get_mode | Get lightshow mode's ID (index)"); USBSerial.println(" get_num_modes | Return the number of modes available"); @@ -306,6 +310,7 @@ void parse_command(char* command_buf) { USBSerial.println(" standby_dimming=[true/false/default] | Toggle dimming during detected silence"); USBSerial.println(" bass_mode=[true/false] | Toggle bass-mode, which alters note_offset and chromagram_range for bass-y tunes"); USBSerial.println(" max_current_ma=[int or 'default'] | Sets the maximum current FastLED will attempt to limit the LED consumption to"); + USBSerial.println(" temporal_dithering=[true/false/default] | Toggle per-LED temporal dithering that simulates higher bit-depths"); tx_end(); } @@ -465,8 +470,18 @@ void parse_command(char* command_buf) { else if (strcmp(command_buf, "fps") == 0) { tx_begin(); - USBSerial.print("FPS: "); - USBSerial.println(FPS); + USBSerial.print("SYSTEM_FPS: "); + USBSerial.println(SYSTEM_FPS); + tx_end(); + + } + + // Print the average FPS ---------------------------------- + else if (strcmp(command_buf, "led_fps") == 0) { + + tx_begin(); + USBSerial.print("LED_FPS: "); + USBSerial.println(LED_FPS); tx_end(); } @@ -474,7 +489,6 @@ void parse_command(char* command_buf) { // Print the knob values ---------------------------------- else if (strcmp(command_buf, "get_knobs") == 0) { - tx_begin(); USBSerial.print("{"); USBSerial.print('"'); @@ -500,14 +514,12 @@ void parse_command(char* command_buf) { USBSerial.print(CONFIG.MOOD); USBSerial.println('}'); - tx_end(); } // Print the button values -------------------------------- else if (strcmp(command_buf, "get_buttons") == 0) { - tx_begin(); USBSerial.print("{"); USBSerial.print('"'); @@ -525,7 +537,6 @@ void parse_command(char* command_buf) { USBSerial.print(digitalRead(mode_button.pin)); USBSerial.println('}'); - tx_end(); } @@ -779,6 +790,32 @@ void parse_command(char* command_buf) { } } + // Set LED Temporal Dithering ---------------------------- + else if (strcmp(command_type, "temporal_dithering") == 0) { + bool good = false; + if (strcmp(command_data, "default") == 0) { + CONFIG.TEMPORAL_DITHERING = CONFIG_DEFAULTS.TEMPORAL_DITHERING; + good = true; + } + else if (strcmp(command_data, "true") == 0) { + CONFIG.TEMPORAL_DITHERING = true; + good = true; + } else if (strcmp(command_data, "false") == 0) { + CONFIG.TEMPORAL_DITHERING = false; + good = true; + } else { + bad_command(command_type, command_data); + } + + if (good) { + save_config_delayed(); + tx_begin(); + USBSerial.print("CONFIG.TEMPORAL_DITHERING: "); + USBSerial.println(CONFIG.TEMPORAL_DITHERING); + tx_end(); + } + } + // Set LED Color Order ---------------------------- else if (strcmp(command_type, "led_color_order") == 0) { bool good = false; diff --git a/SENSORY_BRIDGE_FIRMWARE/system.h b/SENSORY_BRIDGE_FIRMWARE/system.h index 3d1337e..2045e67 100644 --- a/SENSORY_BRIDGE_FIRMWARE/system.h +++ b/SENSORY_BRIDGE_FIRMWARE/system.h @@ -3,13 +3,14 @@ extern void run_sweet_spot(); extern void show_leds(); void reboot() { + led_thread_halt = true; USBSerial.println("--- ! REBOOTING to apply changes (You may need to restart the Serial Monitor)"); USBSerial.flush(); for(float i = 1.0; i >= 0.0; i-=0.05){ MASTER_BRIGHTNESS = i; run_sweet_spot(); show_leds(); - delay(12); // Takes ~250ms total + FastLED.delay(12); // Takes ~250ms total } FastLED.setBrightness(0); FastLED.show(); @@ -195,12 +196,12 @@ void generate_a_weights() { void generate_window_lookup() { start_timing("GENERATING HANN WINDOW LOOKUP TABLE"); - for (uint16_t i = 0; i < 1024; i++) { - float ratio = i / 2047.0; + for (uint16_t i = 0; i < 2048; i++) { + float ratio = i / 4095.0; float weighing_factor = 0.54 * (1.0 - cos(TWOPI * ratio)); window_lookup[i] = 32767 * weighing_factor; - window_lookup[2047 - i] = 32767 * weighing_factor; + window_lookup[4095 - i] = 32767 * weighing_factor; } end_timing(); } @@ -218,7 +219,7 @@ void generate_frequency_data() { } frequencies[i].target_freq = notes[note_index]; - frequencies[i].block_size = (CONFIG.MAX_BLOCK_SIZE) - ((CONFIG.MAX_BLOCK_SIZE * 0.95) * sqrt(sqrt(prog))); + frequencies[i].block_size = (CONFIG.MAX_BLOCK_SIZE) - ((CONFIG.MAX_BLOCK_SIZE * 0.95) * sqrt(sqrt(sqrt(prog)))); frequencies[i].block_size_recip = 1.0 / float(frequencies[i].block_size); frequencies[i].zone = (i / float(NUM_FREQS)) * NUM_ZONES; @@ -227,7 +228,7 @@ void generate_frequency_data() { float coeff = 2.0 * cos(w); frequencies[i].coeff_q14 = (1 << 14) * coeff; - frequencies[i].window_mult = 2048.0 / frequencies[i].block_size; + frequencies[i].window_mult = 4096.0 / frequencies[i].block_size; } end_timing(); } @@ -322,11 +323,11 @@ void log_fps(uint32_t t_now_us) { fps_sum += fps_history[i]; } - FPS = fps_sum / 10.0; + SYSTEM_FPS = fps_sum / 10.0; if (stream_fps == true) { USBSerial.print("sbs((fps="); - USBSerial.print(FPS); + USBSerial.print(SYSTEM_FPS); USBSerial.println("))"); } diff --git a/SENSORY_BRIDGE_FIRMWARE/utilities.h b/SENSORY_BRIDGE_FIRMWARE/utilities.h index e3d2e77..90d4ed9 100644 --- a/SENSORY_BRIDGE_FIRMWARE/utilities.h +++ b/SENSORY_BRIDGE_FIRMWARE/utilities.h @@ -20,23 +20,16 @@ void bytes_to_hex_string(const byte bytes[4], char hex_string[9]) { } void print_chip_id() { - chip_id = ESP.getEfuseMac(); // Get the chip ID as a uint64_t - - chip_id_high = uint64_t(chip_id >> 32); // Split the chip ID into two uint32_t values - chip_id_low = chip_id; - + for (int i = 0; i < 17; i += 8) { + chip_id |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; + } + char hex_string[9]; // ... Create a char array to store the hex string bytes_32 b; // ........... Create a bytes_32 struct to hold the bytes of chip_id_high - - b.long_val = chip_id_high; // .................. Assign chip_id_high to the long_val member of the struct - bytes_to_hex_string(b.bytes, hex_string); // ... Convert the bytes of chip_id_high to a zero-padded hex string - USBSerial.print(hex_string); // ................... Print the hex string to the serial port - - USBSerial.print('_'); // Print an underscore separator - - b.long_val = chip_id_low; // ................... Assign chip_id_low to the long_val member of the struct + + b.long_val = chip_id; // ....................... Assign chip_id_low to the long_val member of the struct bytes_to_hex_string(b.bytes, hex_string); // ... Convert the bytes of chip_id_low to a zero-padded hex string - USBSerial.print(hex_string); // ................... Print the hex string to the serial port - + USBSerial.print(hex_string); // ................ Print the hex string to the serial port + USBSerial.println(); // Print a newline character }