#include #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 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(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(f_half_fill); // Truncates float. float fade_pix_perc = f_half_fill - static_cast(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(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(floor(taper_point_1 * led_column_table[i])); const int taper_idx_2 = static_cast(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(led_column_table[i]) - static_cast(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(); }