sTodo-m5paper-client/libraries/FastLED/examples/LuminescentGrand/shared/Painter.cpp
2025-06-30 20:47:33 +02:00

485 lines
14 KiB
C++

#include <Arduino.h>
#include "./Painter.h"
#include "./led_layout_array.h"
#include "./dprint.h"
#include "./Keyboard.h"
#include "fl/math_macros.h"
#include "fl/math.h"
#include "fl/warn.h"
namespace {
float LuminanceDecay(float time) {
typedef InterpData<float, float> Datum;
static const Datum kData[] = {
Datum(0, 0),
Datum(1, 0),
Datum(10, 0),
Datum(47, 60),
Datum(120, 100),
Datum(230, 160),
Datum(250, 255),
Datum(254, 255),
Datum(255, 64),
};
const float key = time * 255.f;
static const int n = sizeof(kData) / sizeof(kData[0]);
float approx_val = Interp(key, kData, n);
static const float k = (1.0f / 255.f);
const float out = approx_val * k;
return out;
}
float CalcLuminance(float time_delta_ms,
bool sustain_pedal_on,
const Key& key,
int key_idx) {
if (key.curr_color_.v_ <= 0.0) {
return 0.0;
}
const bool dampened_key = (key_idx < kFirstNoteNoDamp);
const float decay_factor = CalcDecayFactor(sustain_pedal_on,
key.on_,
key_idx,
key.velocity_ * (1.f/127.f), // Normalizing
dampened_key,
time_delta_ms);
if (key.on_) {
//const float brigthness_factor = sin(key.orig_color_.v_ * PI / 2.0);
float brigthness_factor = 0.0f;
if (kUseLedCurtin) {
brigthness_factor = sqrt(sqrt(key.orig_color_.v_));
} else {
//brigthness_factor = key.orig_color_.v_ * key.orig_color_.v_;
brigthness_factor = key.orig_color_.v_;
}
return LuminanceDecay(decay_factor) * brigthness_factor;
//return 1.0f;
} else {
return decay_factor * key.orig_color_.v_;
}
}
float CalcSaturation(float time_delta_ms, const ColorHSV& color, bool key_on) {
if (color.v_ <= 0.0) {
return color.s_;
}
if (!key_on) {
return 1.0f;
}
static const float kDefaultSaturationTime = 0.05f * 1000.f;
// At time = 0.0 the saturation_factor will be at 0.0 and then transition to 1.0
float saturation_factor = mapf(time_delta_ms,
0.0f, kDefaultSaturationTime,
0.0f, 1.0f);
// As time increases the saturation factor will continue
// to grow past 1.0. We use min to clamp it back to 1.0.
saturation_factor = MIN(1.0f, saturation_factor);
// TODO - make the saturation interpolate between the original
// color and the unsaturated state.
return saturation_factor;
}
} // namespace.
void Painter::Paint(uint32_t now_ms,
uint32_t delta_ms,
VisState vis_state,
KeyboardState* keyboard,
LedRopeInterface* light_rope) {
for (int i = 0; i < KeyboardState::kNumKeys; ++i) {
Key& key = keyboard->keys_[i];
const float time_delta_ms = static_cast<float>(now_ms - key.event_time_);
const float lum = CalcLuminance(time_delta_ms, keyboard->sustain_pedal_, key, i);
const float sat = CalcSaturation(time_delta_ms, key.curr_color_, key.on_);
//if (key.idx_ == 56) {
// dprint("lum: "); dprint(lum*255.f); dprint(" sat:"); dprintln(sat*255.f);
//}
key.curr_color_.v_ = lum;
key.curr_color_.s_ = sat;
// Removing this line breaks one of the visualizers...
// TODO: Figure out a cleaner solution.
light_rope->Set(i, key.curr_color_.ToRGB());
}
LedColumns led_columns = LedLayoutArray();
switch (vis_state) {
case Painter::kBlockNote: {
light_rope->DrawSequentialRepeat(kNumLightsPerNote);
break;
}
case Painter::kColumnNote: {
light_rope->DrawRepeat(led_columns.array, kNumKeys);
break;
}
case Painter::kVUNote: {
PaintVuNotes(now_ms, *keyboard, led_columns.array, kNumKeys, light_rope);
break;
}
case Painter::kVUMidNote: {
PaintVuMidNotesFade(delta_ms, *keyboard, led_columns.array, kNumKeys, light_rope);
break;
}
case Painter::kVegas: { // aka "vegas mode?"
VegasVisualizer(*keyboard, led_columns.array, kNumKeys, light_rope);
break;
}
case Painter::kBrightSurprise: {
PaintBrightSurprise(*keyboard, led_columns.array, kNumKeys, light_rope);
break;
}
case Painter::kVUSpaceInvaders: {
PaintVuSpaceInvaders(now_ms, *keyboard, led_columns.array, kNumKeys, light_rope);
break;
}
default:
dprint("Unknown mode: "); dprint(vis_state); dprint(".\n");
break;
}
}
void Painter::PaintVuNotes(uint32_t /*now_ms*/,
const KeyboardState& keyboard,
const int* led_column_table, int led_column_table_length,
LedRopeInterface* led_rope) {
FASTLED_WARN("\n\n############## VU NOTES ################\n\n");
led_rope->RawBeginDraw();
for (int i = 0; i < led_column_table_length; ++i) {
const Key& key = keyboard.keys_[i];
// Map the white keys to the bottom and the black keys to the top.
bool black_key = false;
switch (key.idx_ % 12) {
case 1:
case 4:
case 6:
case 9:
case 11:
black_key = true;
break;
}
const int pixel_count = led_column_table[i];
const int draw_pixel_count = ceil(pixel_count * sqrt(key.curr_color_.v_));
const int black_pixel_count = pixel_count - draw_pixel_count;
const Color3i& c = *led_rope->GetIterator(i);
const bool reverse_correct = black_key == (key.idx_ % 2);
if (reverse_correct) {
for (int j = 0; j < draw_pixel_count; ++j) {
if (j < draw_pixel_count - 1) {
led_rope->RawDrawPixel(c);
} else {
// Last pixel.
ColorHSV hsv(random(512) / 512.f, random(512) / 512.f, 1.0);
led_rope->RawDrawPixel(hsv.ToRGB());
}
}
for (int j = 0; j < black_pixel_count; ++j) {
led_rope->RawDrawPixel(Color3i::Black());
}
} else {
for (int j = 0; j < black_pixel_count; ++j) {
led_rope->RawDrawPixel(Color3i::Black());
}
for (int j = draw_pixel_count - 1; j >= 0; --j) {
if (j < draw_pixel_count - 1) {
led_rope->RawDrawPixel(c);
} else {
// Last pixel.
ColorHSV hsv(random(512) / 512.f, random(512) / 512.f, 1.0);
led_rope->RawDrawPixel(hsv.ToRGB());
}
}
}
}
led_rope->RawCommitDraw();
}
void Painter::PaintVuMidNotesFade(uint32_t /*delta_ms*/,
const KeyboardState& keyboard,
const int* led_column_table, int led_column_table_length,
LedRopeInterface* led_rope) {
FASTLED_WARN("\n\n############## VU MID NOTES FADE ################\n\n");
struct DrawPoints {
int n_black0;
int n_fade0;
int n_fill;
int n_fade1;
int n_black1;
float fade_factor; // 0->1.0
float SumBrightness() const {
float out = 0;
out += n_fill;
out += (fade_factor * n_fade0);
out += (fade_factor * n_fade1);
return out;
}
};
// Generator for the DrawPoints struct above.
// n_led: How many led's there are in total.
// factor: 0->1, indicates % of led's "on".
struct F {
static DrawPoints Generate(int n_led, float factor) {
DrawPoints out;
memset(&out, 0, sizeof(out));
if (n_led == 0 || factor == 0.0f) {
out.n_black0 = n_led;
return out;
}
const int is_odd = (n_led % 2);
const int n_half_lights = n_led / 2 + is_odd;
const float f_half_fill = n_half_lights * factor;
const int n_half_fill = static_cast<int>(f_half_fill); // Truncates float.
float fade_pix_perc = f_half_fill - static_cast<float>(n_half_fill);
int n_fade_pix = fade_pix_perc < 1.0f;
if (n_half_fill == 0) {
n_fade_pix = 1;
}
int n_half_black = n_half_lights - n_half_fill - n_fade_pix;
int n_fill_pix = 0;
if (n_half_fill > 0) {
n_fill_pix = n_half_fill * 2 + (is_odd ? -1 : 0);
}
out.n_black0 = n_half_black;
out.n_fade0 = n_fade_pix;
out.n_fill = n_fill_pix;
out.n_fade1 = n_fade_pix;
if (!n_fill_pix && is_odd) {
out.n_fade1 = 0;
}
out.n_black1 = n_half_black;
out.fade_factor = fade_pix_perc;
return out;
}
};
led_rope->RawBeginDraw();
for (int i = 0; i < led_column_table_length; ++i) {
const Key& key = keyboard.keys_[i];
float active_lights_factor = key.IntensityFactor();
//if (key.curr_color_.v_ <= 0.f) {
// active_lights_factor = 0.0;
//}
const int n_led = led_column_table[i];
if (active_lights_factor > 0.0f) {
DrawPoints dp = F::Generate(n_led, active_lights_factor);
ColorHSV hsv = key.curr_color_;
hsv.v_ = 1.0;
Color3i color = hsv.ToRGB();
// Now figure out optional fade color
Color3i fade_col;
ColorHSV c = key.curr_color_;
c.v_ = dp.fade_factor;
fade_col = c.ToRGB();
// Output to graphics.
led_rope->RawDrawPixels(Color3i::Black(), dp.n_black0);
led_rope->RawDrawPixels(fade_col, dp.n_fade0);
led_rope->RawDrawPixels(color, dp.n_fill);
led_rope->RawDrawPixels(fade_col, dp.n_fade1);
led_rope->RawDrawPixels(Color3i::Black(), dp.n_black1);
#ifdef DEBUG_PAINTER
if (active_lights_factor > 0.0) {
int total_lights_on = dp.SumBrightness();
//dprint("total_lights_on: "); dprint(total_lights_on);
//dprint(", total lights written: "); dprintln(total_lights_on + dp.n_black0 + dp.n_black1);
//float total = (dp.n_fade0 * dp.fade_factor) + (dp.n_fade1 * dp.fade_factor) + static_cast<float>(dp.n_fill);
#define P(X) dprint(", "#X ": "); dprint(X);
//dprint("active_lights_factor: "); dprintln(active_lights_factor);
//P(dp.n_black0); P(dp.n_fade0); P(dp.n_fill); P(dp.n_fade1); P(dp.n_black1); P(dp.fade_factor);
P(total_lights_on);
P(active_lights_factor);
//P(total);
dprintln("");
}
#endif
} else {
led_rope->RawDrawPixels(Color3i::Black(), n_led);
}
}
led_rope->RawCommitDraw();
}
void Painter::VegasVisualizer(const KeyboardState& keyboard,
const int* led_column_table, int led_column_table_length,
LedRopeInterface* led_rope) {
led_rope->RawBeginDraw();
uint32_t skipped_lights = 0;
for (int i = 0; i < led_column_table_length; ++i) {
const Key& key = keyboard.keys_[i];
uint32_t painted_lights = 0;
// % of lights that are active.
const float active_lights_factor = led_column_table[i] * sqrt(key.curr_color_.v_);
const float inactive_lights_factor = 1.0f - active_lights_factor;
const float taper_point_1 = inactive_lights_factor / 2.0f;
const float taper_point_2 = taper_point_1 + active_lights_factor;
const int taper_idx_1 = static_cast<int>(floor(taper_point_1 * led_column_table[i]));
const int taper_idx_2 = static_cast<int>(floor(taper_point_2 * led_column_table[i]));
const Color3i c = key.curr_color_.ToRGB();
for (int i = 0; i < taper_idx_1 / 2; ++i) {
led_rope->RawDrawPixel(Color3i::Black());
painted_lights++;
}
int length = taper_idx_2 - taper_idx_1;
for (int i = 0; i < min(200, length); ++i) {
led_rope->RawDrawPixel(c);
painted_lights++;
}
length = led_column_table[i] - taper_idx_2;
for (int i = 0; i < length; ++i) {
led_rope->RawDrawPixel(Color3i::Black());
painted_lights++;
}
skipped_lights += MAX(0, static_cast<int32_t>(led_column_table[i]) - static_cast<int32_t>(painted_lights));
}
for (uint32_t i = 0; i < skipped_lights; ++i) {
led_rope->RawDrawPixel(Color3i::Black());
}
led_rope->RawCommitDraw();
}
void Painter::PaintBrightSurprise(
const KeyboardState& keyboard,
const int* led_column_table, int led_column_table_length,
LedRopeInterface* led_rope) {
led_rope->RawBeginDraw();
int total_counted = 0;
float r, g, b;
r = g = b = 0;
for (int i = 0; i < KeyboardState::kNumKeys; ++i) {
const Key& key = keyboard.keys_[i];
if (key.curr_color_.v_ > 0.0f) {
const Color3i rgb = key.curr_color_.ToRGB();
r += rgb.r_;
g += rgb.g_;
b += rgb.b_;
++total_counted;
}
}
float denom = total_counted ? total_counted : 1;
r /= denom;
g /= denom;
b /= denom;
const Color3i rgb(r, g, b);
for (int i = 0; i < led_column_table_length; ++i) {
const int n = led_column_table[i];
for (int i = 0; i < n; ++i) {
led_rope->RawDrawPixel(rgb);
}
}
led_rope->RawCommitDraw();
}
void Painter::PaintVuSpaceInvaders(uint32_t /*now_ms*/,
const KeyboardState& keyboard,
const int* led_column_table, int led_column_table_length,
LedRopeInterface* led_rope) {
led_rope->RawBeginDraw();
Color3i black = Color3i::Black();
for (int i = 0; i < led_column_table_length; ++i) {
const Key& key = keyboard.keys_[i];
const int pixel_count = led_column_table[i];
const int draw_pixel_count = ceil(pixel_count * sqrt(key.curr_color_.v_));
const int black_pixel_count = pixel_count - draw_pixel_count;
// If i is even
if (i % 2 == 0) {
for (int j = 0; j < black_pixel_count; ++j) {
led_rope->RawDrawPixel(*led_rope->GetIterator(i));
}
for (int j = 0; j < draw_pixel_count; ++j) {
led_rope->RawDrawPixel(black);
}
} else {
for (int j = 0; j < draw_pixel_count; ++j) {
led_rope->RawDrawPixel(black);
}
for (int j = 0; j < black_pixel_count; ++j) {
led_rope->RawDrawPixel(*led_rope->GetIterator(i));
}
}
}
led_rope->RawCommitDraw();
}