imported from "final" folder

This commit is contained in:
2025-11-28 12:12:50 +01:00
parent f9288986cf
commit ff8e725b35
1061 changed files with 225150 additions and 96 deletions

View File

@@ -0,0 +1,75 @@
// Copyleft (c) 2012, Zach Vorhies
// Public domain, no rights reserved.
#ifndef APPROXIMATING_FUNCTION_H_
#define APPROXIMATING_FUNCTION_H_
//#include <Arduino.h>
template<typename X, typename Y>
const Y MapT(const X& x,
const X& x1, const X& x2,
const Y& y1, const Y& y2) {
Y return_val = static_cast<Y>((x - x1) * (y2 - y1) / (x2 - x1) + y1);
return return_val;
}
template <typename KeyT, typename ValT>
struct InterpData {
InterpData(const KeyT& k, const ValT& v) : key(k), val(v) {}
KeyT key;
ValT val;
};
template <typename KeyT, typename ValT>
inline void SelectInterpPoints(const KeyT& k,
const InterpData<KeyT, ValT>* array,
const int n, // Number of elements in array.
int* dest_lower_bound,
int* dest_upper_bound) {
if (n < 1) {
*dest_lower_bound = *dest_upper_bound = -1;
return;
}
if (k < array[0].key) {
*dest_lower_bound = *dest_upper_bound = 0;
return;
}
for (int i = 0; i < n - 1; ++i) {
const InterpData<KeyT, ValT>& curr = array[i];
const InterpData<KeyT, ValT>& next = array[i+1];
if (curr.key <= k && k <= next.key) {
*dest_lower_bound = i;
*dest_upper_bound = i+1;
return;
}
}
*dest_lower_bound = n - 1;
*dest_upper_bound = n - 1;
}
template <typename KeyT, typename ValT>
inline ValT Interp(const KeyT& k, const InterpData<KeyT, ValT>* array, const int n) {
if (n < 1) {
return ValT(0);
}
int low_idx = -1;
int high_idx = -1;
SelectInterpPoints<KeyT, ValT>(k, array, n, &low_idx, &high_idx);
if (low_idx == high_idx) {
return array[low_idx].val;
}
const InterpData<KeyT, ValT>* curr = &array[low_idx];
const InterpData<KeyT, ValT>* next = &array[high_idx];
// map(...) only works on integers. MapT<> is the same thing but it works on
// all datatypes.
return MapT<KeyT, ValT>(k, curr->key, next->key, curr->val, next->val);
}
#endif // APPROXIMATING_FUNCTION_H_

View File

@@ -0,0 +1,211 @@
#include <Arduino.h>
#include "./util.h"
#include "./color_mapper.h"
#include "./Keyboard.h"
#include "./dprint.h"
#include "fl/unused.h"
Key::Key() : on_(false), sustained_(false), sustain_pedal_on_(false),
velocity_(0), idx_(0), event_time_(0) {}
void Key::SetOn(uint8_t vel, const ColorHSV& color, uint32_t now_ms) {
if (curr_color_.v_ < color.v_) { // if the new color is "brighter" than the current color.
velocity_ = vel;
curr_color_ = color;
event_time_ = now_ms;
}
orig_color_ = curr_color_;
event_time_ = now_ms;
on_ = true;
}
void Key::SetOff(uint32_t now_ms) {
orig_color_ = curr_color_;
on_ = false;
event_time_ = now_ms;
sustained_ = false;
}
void Key::SetSustained() {
sustained_ = true;
}
void Key::Update(uint32_t now_ms, uint32_t delta_ms, bool sustain_pedal_on) {
if (sustained_ && !sustain_pedal_on) {
sustained_ = false;
SetOff(now_ms);
}
sustain_pedal_on_ = sustain_pedal_on;
UpdateIntensity(now_ms, delta_ms);
}
float Key::VelocityFactor() const { return velocity_ / 127.f; }
float Key::CalcAttackDecayFactor(uint32_t delta_ms) const {
bool dampened_key = (idx_ < kFirstNoteNoDamp);
float active_lights_factor = ::CalcDecayFactor(
sustain_pedal_on_,
on_,
idx_,
VelocityFactor(),
dampened_key,
delta_ms);
return active_lights_factor;
}
float Key::AttackRemapFactor(uint32_t now_ms) {
if (on_) {
return ::AttackRemapFactor(now_ms - event_time_);
} else {
return 1.0;
}
}
float Key::IntensityFactor() const {
return intensity_;
}
void Key::UpdateIntensity(uint32_t now_ms, uint32_t delta_ms) {
if (on_) {
// Intensity can be calculated by a
float intensity =
CalcAttackDecayFactor(now_ms - event_time_) *
VelocityFactor() *
AttackRemapFactor(now_ms);
// This is FRAME RATE DEPENDENT FUNCTION!!!!
// CHANGE TO TIME INDEPENDENT BEFORE SUBMIT.
intensity_ = (.9f * intensity) + (.1f * intensity_);
} else if(intensity_ > 0.0f) { // major cpu hotspot.
if (sustain_pedal_on_) {
float delta_s = delta_ms / 1000.f;
if (intensity_ > .5f) {
const float kRate = .12f;
// Time flexible decay function. Stays accurate
// even as the frame rate changes.
// Formula: A = Pe^(r*t)
intensity_ = intensity_ * exp(-delta_s * kRate);
} else {
// Quickly fade at the bottom end of the transition.
const float kRate = .05f;
intensity_ -= delta_s * kRate;
}
} else {
float delta_s = delta_ms / 1000.f;
if (intensity_ > .5f) {
const float kRate = 12.0f;
// Time flexible decay function. Stays accurate
// even as the frame rate changes.
// Formula: A = Pe^(r*t)
intensity_ = intensity_ * exp(-delta_s * kRate);
} else {
// Quickly fade at the bottom end of the transition.
const float kRate = 2.0f;
intensity_ -= delta_s * kRate;
}
}
intensity_ = constrain(intensity_, 0.0f, 1.0f);
}
}
void KeyboardState::HandleNoteOn(uint8_t midi_note, uint8_t velocity, int color_selector_value, uint32_t now_ms) {
if (0 == velocity) {
// Some keyboards signify "NoteOff" with a velocity of zero.
HandleNoteOff(midi_note, velocity, now_ms);
return;
}
#ifdef DEBUG_KEYBOARD
dprint("HandleNoteOn:");
dprint("midi_note = ");
dprint(midi_note);
dprint(", velocity = ");
dprintln(velocity);
#endif
float brightness = ToBrightness(velocity);
dprint("brightness: "); dprintln(brightness);
ColorHSV pixel_color_hsv = SelectColor(midi_note, brightness,
color_selector_value);
// TODO: Give a key access to the Keyboard owner, therefore it could inspect the
// sustained variable instead of passing it here.
Key* key = GetKey(midi_note);
dprint("key indx: "); dprintln(key->idx_);
key->SetOn(velocity, pixel_color_hsv, now_ms);
}
void KeyboardState::HandleNoteOff(uint8_t midi_note, uint8_t /*velocity*/, uint32_t now_ms) {
#ifdef DEBUG_KEYBOARD
dprint("HandleNoteOff:");
dprint("midi_note = ");
dprint(midi_note);
dprint(", velocity = ");
dprintln(velocity);
#endif
Key* key = GetKey(midi_note);
if (sustain_pedal_) {
key->SetSustained();
} else {
key->SetOff(now_ms);
}
}
void KeyboardState::HandleControlChange(uint8_t d1, uint8_t d2) {
// Note that d1 and d2 just mean "data-1" and "data-2".
// TODO: Find out what d1 and d2 should be called.
const bool foot_pedal = (d1 == kMidiFootPedal);
if (foot_pedal) {
// Spec says that if that values 0-63 are OFF, otherwise ON.
sustain_pedal_ = (d2 >= 64);
}
}
void KeyboardState::HandleAfterTouchPoly(uint8_t note, uint8_t pressure) {
FL_UNUSED(note);
FL_UNUSED(pressure);
dprintln("HandleAfterTouchPoly");
dprint("\tnote = ");
dprint(note);
dprint(", pressure = ");
dprintln(pressure);
}
KeyboardState::KeyboardState() : sustain_pedal_(false), keys_() {
for (int i = 0; i < kNumKeys; ++i) {
keys_[i].idx_ = i;
}
}
void KeyboardState::Update(uint32_t now_ms, uint32_t delta_ms) {
for (int i = 0; i < kNumKeys; ++i) {
keys_[i].Update(now_ms, delta_ms, sustain_pedal_);
}
}
uint8_t KeyboardState::KeyIndex(int midi_pitch) {
//return constrain(midi_pitch, 21, 108) - 21;
return ::KeyIndex(midi_pitch);
}
Key* KeyboardState::GetKey(int midi_pitch) {
uint8_t idx = KeyIndex(midi_pitch);
return &keys_[idx];
}

View File

@@ -0,0 +1,108 @@
#ifndef KEYBOARD_H_
#define KEYBOARD_H_
#include <Arduino.h>
#include "color.h"
#include "./util.h"
class KeyboardState;
// // NOTE: AS OF NOV-12-2013 we've disable all of the auto-sustained
// notes in the high end of the keyboard.
enum {
// kFirstNoteNoDamp = 69, // First key that has no dampener
kFirstNoteNoDamp = 89, // DISABLED - Greater than last key.
};
inline uint8_t KeyIndex(int midi_pitch) {
return constrain(midi_pitch, 21, 108) - 21;
}
struct Key {
Key();
void SetOn(uint8_t vel, const ColorHSV& color, uint32_t now_ms);
void SetOff(uint32_t now_ms);
void SetSustained();
void Update(uint32_t now_ms, uint32_t delta_ms, bool sustain_pedal_on);
float VelocityFactor() const;
float CalcAttackDecayFactor(uint32_t delta_ms) const;
float AttackRemapFactor(uint32_t now_ms);
float IntensityFactor() const;
void UpdateIntensity(uint32_t now_ms, uint32_t delta_ms);
bool on_; // Max number of MIDI keys.
bool sustained_;
bool sustain_pedal_on_;
uint8_t velocity_;
int idx_;
unsigned long event_time_;
// 0.0 -> 1.0 How intense the key is, used for light sequences to represent
// 0 -> 0% of lights on to 1.0 -> 100% of lights on. this is a smooth
// value through time.
float intensity_;
ColorHSV orig_color_;
ColorHSV curr_color_;
};
// Interface into the Keyboard state.
// Convenience class which holds all the keys in the keyboard. Also
// has a convenience function will allows one to map the midi notes
// (21-108) to the midi keys (0-88).
class KeyboardState {
public:
// NOTE: AS OF NOV-12-2013 we've disable all of the auto-sustained
// notes in the high end of the keyboard.
//enum {
// kFirstNoteNoDamp = 69, // First key that has no dampener
// kFirstNoteNoDamp = 89, // DISABLED - Greater than last key.
//};
KeyboardState();
void Update(uint32_t now_ms, uint32_t delta_ms);
////////////////////////////////////
// Called when the note is pressed.
// Input:
// channel - Ignored.
// midi_note - Value between 21-108 which maps to the keyboard keys.
// velocity - Value between 0-127
void HandleNoteOn(uint8_t midi_note, uint8_t velocity, int color_selector_value, uint32_t now_ms);
/////////////////////////////////////////////////////////
// Called when the note is released.
// Input:
// channel - Ignored.
// midi_note - Value between 21-108 which maps to the keyboard keys.
// velocity - Value between 0-127
void HandleNoteOff(uint8_t midi_note, uint8_t velocity, uint32_t now_ms);
/////////////////////////////////////////////////////////
// This is uninmplemented because the test keyboard didn't
// have this functionality. Right now the only thing it does is
// print out that the key was pressed.
void HandleAfterTouchPoly(uint8_t note, uint8_t pressure);
/////////////////////////////////////////////////////////
// Detects whether the foot pedal has been touched.
void HandleControlChange(uint8_t d1, uint8_t d2);
static uint8_t KeyIndex(int midi_pitch);
Key* GetKey(int midi_pitch);
static const int kNumKeys = 88;
bool sustain_pedal_;
Key keys_[kNumKeys];
};
#endif // KEYBOARD_H_

View File

@@ -0,0 +1,485 @@
#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();
}

View File

@@ -0,0 +1,58 @@
#ifndef PAINTER_H
#define PAINTER_H
#include "./Keyboard.h"
#include "./ApproximatingFunction.h"
#include "./util.h"
#include "./settings.h"
#include "./led_rope_interface.h"
struct Painter {
enum VisState {
kVUMidNote = 0,
kColumnNote,
kBlockNote,
kVUNote,
kVUSpaceInvaders,
kVegas,
kBrightSurprise,
kNumVisStates,
};
////////////////////////////////////////////////////
static void Paint(uint32_t now_ms,
uint32_t delta_ms,
VisState vis_state,
KeyboardState* keyboard,
LedRopeInterface* light_rope);
private:
static void PaintVuNotes(uint32_t now_ms,
const KeyboardState& keyboard,
const int* led_column_table, int led_column_table_length,
LedRopeInterface* led_rope);
static void PaintVuMidNotesFade(uint32_t delta_ms,
const KeyboardState& keyboard,
const int* led_column_table, int led_column_table_length,
LedRopeInterface* led_rope);
// This is a crazy effect, lets keep this around.
static void VegasVisualizer(const KeyboardState& keyboard,
const int* led_column_table, int led_column_table_length,
LedRopeInterface* led_rope);
static void PaintBrightSurprise(const KeyboardState& keyboard,
const int* led_column_table, int led_column_table_length,
LedRopeInterface* led_rope);
static void PaintVuSpaceInvaders(uint32_t now_ms,
const KeyboardState& keyboard,
const int* led_column_table, int led_column_table_length,
LedRopeInterface* led_rope);
};
#endif // PAINTER_H

View File

@@ -0,0 +1,151 @@
#include <Arduino.h>
#include "./color.h"
#include "./util.h"
#include "fl/math_macros.h"
///////////////////////////////////////////////////////////////////////////////
void Color3i::Mul(const Color3i& other_color) {
int r = r_;
int g = g_;
int b = b_;
r = r * int(other_color.r_) / 255;
g = g * int(other_color.g_) / 255;
b = b * int(other_color.b_) / 255;
Set(r, g, b);
}
///////////////////////////////////////////////////////////////////////////////
void Color3i::Mulf(float scale) {
const int s = static_cast<int>(scale * 255.0f);
int r = static_cast<int>(r_) * s / 255;
int g = static_cast<int>(g_) * s / 255;
int b = static_cast<int>(b_) * s / 255;
Set(r, g, b);
}
///////////////////////////////////////////////////////////////////////////////
void Color3i::Sub(const Color3i& color) {
if (r_ < color.r_) r_ = 0;
else r_ -= color.r_;
if (g_ < color.g_) g_ = 0;
else g_ -= color.g_;
if (b_ < color.b_) b_ = 0;
else b_ -= color.b_;
}
///////////////////////////////////////////////////////////////////////////////
void Color3i::Add(const Color3i& color) {
if ((255 - r_) < color.r_) r_ = 255;
else r_ += color.r_;
if ((255 - g_) < color.g_) g_ = 255;
else g_ += color.g_;
if ((255 - b_) < color.b_) b_ = 255;
else b_ += color.b_;
}
///////////////////////////////////////////////////////////////////////////////
uint8_t Color3i::Get(int rgb_index) const {
const uint8_t* rgb = At(rgb_index);
return rgb ? *rgb : 0;
}
///////////////////////////////////////////////////////////////////////////////
void Color3i::Set(int rgb_index, uint8_t val) {
uint8_t* rgb = At(rgb_index);
if (rgb) {
*rgb = val;
}
}
///////////////////////////////////////////////////////////////////////////////
void Color3i::Interpolate(const Color3i& other_color, float t) {
if (0.0f >= t) {
Set(other_color);
} else if (1.0f <= t) {
return;
}
Color3i new_color = other_color;
new_color.Mul(1.0f - t);
this->Mul(t);
this->Add(new_color);
}
///////////////////////////////////////////////////////////////////////////////
uint8_t* Color3i::At(int rgb_index) {
switch(rgb_index) {
case 0: return &r_;
case 1: return &g_;
case 2: return &b_;
}
return NULL;
}
///////////////////////////////////////////////////////////////////////////////
const uint8_t* Color3i::At(int rgb_index) const {
switch(rgb_index) {
case 0: return &r_;
case 1: return &g_;
case 2: return &b_;
}
return NULL;
}
///////////////////////////////////////////////////////////////////////////////
void ColorHSV::FromRGB(const Color3i& rgb) {
typedef double FloatT;
FloatT r = (FloatT) rgb.r_/255.f;
FloatT g = (FloatT) rgb.g_/255.f;
FloatT b = (FloatT) rgb.b_/255.f;
FloatT max_rgb = MAX(r, MAX(g, b));
FloatT min_rgb = MIN(r, MIN(g, b));
v_ = max_rgb;
FloatT d = max_rgb - min_rgb;
s_ = max_rgb == 0 ? 0 : d / max_rgb;
if (max_rgb == min_rgb) {
h_ = 0; // achromatic
} else {
if (max_rgb == r) {
h_ = (g - b) / d + (g < b ? 6 : 0);
} else if (max_rgb == g) {
h_ = (b - r) / d + 2;
} else if (max_rgb == b) {
h_ = (r - g) / d + 4;
}
h_ /= 6;
}
}
///////////////////////////////////////////////////////////////////////////////
Color3i ColorHSV::ToRGB() const {
typedef double FloatT;
FloatT r = 0;
FloatT g = 0;
FloatT b = 0;
int i = int(h_ * 6);
FloatT f = h_ * 6.0 - static_cast<FloatT>(i);
FloatT p = v_ * (1.0 - s_);
FloatT q = v_ * (1.0 - f * s_);
FloatT t = v_ * (1.0 - (1.0 - f) * s_);
switch(i % 6){
case 0: r = v_, g = t, b = p; break;
case 1: r = q, g = v_, b = p; break;
case 2: r = p, g = v_, b = t; break;
case 3: r = p, g = q, b = v_; break;
case 4: r = t, g = p, b = v_; break;
case 5: r = v_, g = p, b = q; break;
}
return Color3i(round(r * 255), round(g * 255), round(b * 255));
}

View File

@@ -0,0 +1,112 @@
#ifndef COLOR_H_
#define COLOR_H_
#include "fl/stdint.h"
struct Color3i {
static Color3i Black() { return Color3i(0x0, 0x0, 0x0); }
static Color3i White() { return Color3i(0xff, 0xff, 0xff); }
static Color3i Red() { return Color3i(0xff, 0x00, 0x00); }
static Color3i Orange() { return Color3i(0xff, 0xff / 2,00); }
static Color3i Yellow() { return Color3i(0xff, 0xff,00); }
static Color3i Green() { return Color3i(0x00, 0xff, 0x00); }
static Color3i Cyan() { return Color3i(0x00, 0xff, 0xff); }
static Color3i Blue() { return Color3i(0x00, 0x00, 0xff); }
Color3i(uint8_t r, uint8_t g, uint8_t b) { Set(r,g,b); }
Color3i() { Set(0xff, 0xff, 0xff); }
Color3i(const Color3i& other) { Set(other); }
Color3i& operator=(const Color3i& other) {
if (this != &other) {
Set(other);
}
return *this;
}
void Set(uint8_t r, uint8_t g, uint8_t b) { r_ = r; g_ = g; b_ = b; }
void Set(const Color3i& c) { Set(c.r_, c.g_, c.b_); }
void Mul(const Color3i& other_color);
void Mulf(float scale); // Input range is 0.0 -> 1.0
void Mul(uint8_t val) {
Mul(Color3i(val, val, val));
}
void Sub(const Color3i& color);
void Add(const Color3i& color);
uint8_t Get(int rgb_index) const;
void Set(int rgb_index, uint8_t val);
void Fill(uint8_t val) { Set(val, val, val); }
uint8_t MaxRGB() const {
uint8_t max_r_g = r_ > g_ ? r_ : g_;
return max_r_g > b_ ? max_r_g : b_;
}
template <typename PrintStream>
inline void Print(PrintStream* stream) const {
stream->print("RGB:\t");
stream->print(r_); stream->print(",\t");
stream->print(g_); stream->print(",\t");
stream->print(b_);
stream->print("\n");
}
void Interpolate(const Color3i& other_color, float t);
uint8_t* At(int rgb_index);
const uint8_t* At(int rgb_index) const;
uint8_t r_, g_, b_;
};
struct ColorHSV {
ColorHSV() : h_(0), s_(0), v_(0) {}
ColorHSV(float h, float s, float v) {
Set(h,s,v);
}
explicit ColorHSV(const Color3i& color) {
FromRGB(color);
}
ColorHSV& operator=(const Color3i& color) {
FromRGB(color);
return *this;
}
ColorHSV(const ColorHSV& other) {
Set(other);
}
ColorHSV& operator=(const ColorHSV& other) {
if (this != &other) {
Set(other);
}
return *this;
}
void Set(const ColorHSV& other) {
Set(other.h_, other.s_, other.v_);
}
void Set(float h, float s, float v) {
h_ = h;
s_ = s;
v_ = v;
}
template <typename PrintStream>
inline void Print(PrintStream* stream) {
stream->print("HSV:\t");
stream->print(h_); stream->print(",\t");
stream->print(s_); stream->print(",\t");
stream->print(v_); stream->print("\n");
}
bool operator==(const ColorHSV& other) const {
return h_ == other.h_ && s_ == other.s_ && v_ == other.v_;
}
bool operator!=(const ColorHSV& other) const {
return !(*this == other);
}
void FromRGB(const Color3i& rgb);
Color3i ToRGB() const;
float h_, s_, v_;
};
#endif // COLOR_H_

View File

@@ -0,0 +1,172 @@
#include <Arduino.h>
#include "fl/stdint.h"
#include "./color_mapper.h"
#include "./color.h"
#include "./util.h"
// Serves as the pallet for selecting a color.
struct ColorScheme {
ColorScheme(const ColorHSV& c0,
const ColorHSV& c1,
const ColorHSV& c2,
const ColorHSV& c3,
const ColorHSV& c4,
const ColorHSV& c5,
const ColorHSV& c6,
const ColorHSV& c7,
const ColorHSV& c8,
const ColorHSV& c9,
const ColorHSV& c10,
const ColorHSV& c11) {
data[0] = c0;
data[1] = c1;
data[2] = c2;
data[3] = c3;
data[4] = c4;
data[5] = c5;
data[6] = c6;
data[7] = c7;
data[8] = c8;
data[9] = c9;
data[10] = c10;
data[11] = c11;
}
ColorHSV data[12];
};
const ColorScheme& SelectColorScheme(int indx) {
static ColorScheme color_schemes[] = {
// Coda
ColorScheme(
ColorHSV(Color3i(0xff, 0x00, 0x00)), // C
ColorHSV(Color3i(0x00, 0x80, 0xff)), // C
ColorHSV(Color3i(0xff, 0xff, 0x00)), // D
ColorHSV(Color3i(0x80, 0x00, 0xff)), // D#
ColorHSV(Color3i(0x00, 0xff, 0x00)), // E
ColorHSV(Color3i(0xff, 0x00, 0x80)), // F
ColorHSV(Color3i(0x00, 0xff, 0xff)), // F#
ColorHSV(Color3i(0xff, 0x80, 0x00)), // G
ColorHSV(Color3i(0x00, 0x00, 0xff)), // G#
ColorHSV(Color3i(0x80, 0xff, 0x00)), // A
ColorHSV(Color3i(0xff, 0x00, 0xff)), // A#
ColorHSV(Color3i(0x00, 0xff, 0x80))
),
// Frequency
ColorScheme(
ColorHSV(Color3i(0xfc, 0xff, 0x00)), // C
ColorHSV(Color3i(0x00, 0xff, 0x73)), // C#
ColorHSV(Color3i(0x00, 0xa7, 0xff)),
ColorHSV(Color3i(0x00, 0x20, 0xff)),
ColorHSV(Color3i(0x35, 0x00, 0xff)),
ColorHSV(Color3i(0x56, 0x00, 0xb6)),
ColorHSV(Color3i(0x4e, 0x00, 0x6c)),
ColorHSV(Color3i(0x9f, 0x00, 0x00)), // G
ColorHSV(Color3i(0xdb, 0x00, 0x00)),
ColorHSV(Color3i(0xff, 0x36, 0x00)), // A
ColorHSV(Color3i(0xff, 0xc1, 0x00)),
ColorHSV(Color3i(0xbf, 0xff, 0x00)) // B
),
// SCRIABIN
ColorScheme(
ColorHSV(Color3i(0xff, 0x00, 0x00)), // C
ColorHSV(Color3i(0x8f, 0x00, 0xff)), // C#
ColorHSV(Color3i(0xff, 0xff, 0x00)), // D
ColorHSV(Color3i(0x71, 0x63, 0x95)), // D#
ColorHSV(Color3i(0x4f, 0xa1, 0xc2)), // E
ColorHSV(Color3i(0xc1, 0x01, 0x01)), // F
ColorHSV(Color3i(0x00, 0x00, 0xff)), // F#
ColorHSV(Color3i(0xff, 0x66, 0x00)), // G
ColorHSV(Color3i(0x96, 0x00, 0xff)), // G#
ColorHSV(Color3i(0x00, 0xff, 0x00)), // A
ColorHSV(Color3i(0x71, 0x63, 0x95)), // A#
ColorHSV(Color3i(0x4f, 0xa1, 0xc2)) // B
),
// LUIS BERTRAND CASTEL
ColorScheme(
ColorHSV(Color3i(0x00, 0x00, 0xff)), // C
ColorHSV(Color3i(0x0d, 0x98, 0xba)), // C#
ColorHSV(Color3i(0x00, 0xff, 0x00)), // D
ColorHSV(Color3i(0x80, 0x80, 0x00)), // D#
ColorHSV(Color3i(0xff, 0xff, 0x00)), // E
ColorHSV(Color3i(0xff, 0xd7, 0x00)), // F
ColorHSV(Color3i(0xff, 0x5a, 0x00)), // F#
ColorHSV(Color3i(0xff, 0x00, 0x00)), // G
ColorHSV(Color3i(0xdc, 0x14, 0x3c)), // G#
ColorHSV(Color3i(0x8f, 0x00, 0xff)), // A
ColorHSV(Color3i(0x22, 0x00, 0xcd)), // A#
ColorHSV(Color3i(0x5a, 0x00, 0x95)) // B
),
// H VON HELMHOHOLTZ
ColorScheme(
ColorHSV(Color3i(0xff, 0xff, 0x06)), // C
ColorHSV(Color3i(0x00, 0xff, 0x00)), // C#
ColorHSV(Color3i(0x21, 0x9e, 0xbd)), // D
ColorHSV(Color3i(0x00, 0x80, 0xff)), // D#
ColorHSV(Color3i(0x6f, 0x00, 0xff)), // E
ColorHSV(Color3i(0x8f, 0x00, 0xff)), // F
ColorHSV(Color3i(0xff, 0x00, 0x00)), // F#
ColorHSV(Color3i(0xff, 0x20, 0x00)), // G
ColorHSV(Color3i(0xff, 0x38, 0x00)), // G#
ColorHSV(Color3i(0xff, 0x3f, 0x00)), // A
ColorHSV(Color3i(0xff, 0x3f, 0x34)), // A#
ColorHSV(Color3i(0xff, 0xa5, 0x00)) // B
),
// ZIEVERINK
ColorScheme(
ColorHSV(Color3i(0x9a, 0xcd, 0x32)), // C
ColorHSV(Color3i(0x00, 0xff, 0x00)), // C#
ColorHSV(Color3i(0x00, 0xdd, 0xdd)), // D
ColorHSV(Color3i(0x00, 0x00, 0xff)), // D#
ColorHSV(Color3i(0x6f, 0x00, 0xff)), // E
ColorHSV(Color3i(0x8f, 0x00, 0xff)), // F
ColorHSV(Color3i(0x7f, 0x1a, 0xe5)), // F#
ColorHSV(Color3i(0xbd, 0x00, 0x20)), // G
ColorHSV(Color3i(0xff, 0x00, 0x00)), // G#
ColorHSV(Color3i(0xff, 0x44, 0x00)), // A
ColorHSV(Color3i(0xff, 0xc4, 0x00)), // A#
ColorHSV(Color3i(0xff, 0xff, 0x00)) // B
),
// ROSICRUCIAN ORDER
ColorScheme(
ColorHSV(Color3i(0x9a, 0xcd, 0x32)), // C
ColorHSV(Color3i(0x00, 0xff, 0x00)), // C#
ColorHSV(Color3i(0x21, 0x9e, 0xbd)), // D
ColorHSV(Color3i(0x00, 0x00, 0xff)), // D#
ColorHSV(Color3i(0x8a, 0x2b, 0xe2)), // E
ColorHSV(Color3i(0x8b, 0x00, 0xff)), // F
ColorHSV(Color3i(0xf7, 0x53, 0x94)), // F#
ColorHSV(Color3i(0xbd, 0x00, 0x20)), // G
ColorHSV(Color3i(0xee, 0x20, 0x4d)), // G#
ColorHSV(Color3i(0xff, 0x3f, 0x34)), // A
ColorHSV(Color3i(0xff, 0xa5, 0x00)), // A#
ColorHSV(Color3i(0xff, 0xff, 0x00)) // B
),
};
static const int n = sizeof(color_schemes) / sizeof(color_schemes[0]);
indx = constrain(indx, 0, n - 1);
return color_schemes[indx];
};
const ColorHSV SelectColor(int midi_note, float brightness, int color_selector_val) {
const uint8_t fun_note = FundamentalNote(midi_note);
const ColorScheme& color_scheme = SelectColorScheme(color_selector_val);
ColorHSV color = color_scheme.data[fun_note];
color.v_ = brightness;
return color;
}

View File

@@ -0,0 +1,10 @@
#ifndef COLOR_MAPPER_H_
#define COLOR_MAPPER_H_
#include "./color.h"
const ColorHSV SelectColor(int midi_note, float brightness, int color_selector_val);
#endif // COLOR_MAPPER_H_

View File

@@ -0,0 +1,8 @@
#pragma once
#if defined(__AVR__) || defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_TEENSYLC)
#define ENABLE_SKETCH 0
#else
#define ENABLE_SKETCH 1
#endif

View File

@@ -0,0 +1,5 @@
#include "dprint.h"
bool is_debugging = false;

View File

@@ -0,0 +1,15 @@
#pragma once
#include "fl/unused.h"
extern bool is_debugging;
//#define ENABLE_DPRINT
#ifdef ENABLE_DPRINT
#define dprint(x) if (is_debugging) { Serial.print(x); }
#define dprintln(x) if (is_debugging) { Serial.println(x); }
#else
#define dprint(x) FL_UNUSED(x)
#define dprintln(x) FL_UNUSED(x)
#endif

View File

@@ -0,0 +1,58 @@
#include <Arduino.h>
#include "./framebuffer.h"
#include "./color.h"
FrameBufferBase::FrameBufferBase(Color3i* array, int n_pixels)
: color_array_(array), n_color_array_(n_pixels) {}
FrameBufferBase::~FrameBufferBase() {}
void FrameBufferBase::Set(int i, const Color3i& c) {
color_array_[i] = c;
}
void FrameBufferBase::Set(int i, int length, const Color3i& color) {
for (int j = 0; j < length; ++j) {
Set(i + j, color);
}
}
void FrameBufferBase::FillColor(const Color3i& color) {
for (int i = 0; i < n_color_array_; ++i) {
color_array_[i] = color;
}
}
void FrameBufferBase::ApplyBlendSubtract(const Color3i& color) {
for (int i = 0; i < n_color_array_; ++i) {
color_array_[i].Sub(color);
}
}
void FrameBufferBase::ApplyBlendAdd(const Color3i& color) {
for (int i = 0; i < n_color_array_; ++i) {
color_array_[i].Add(color);
}
}
void FrameBufferBase::ApplyBlendMultiply(const Color3i& color) {
for (int i = 0; i < n_color_array_; ++i) {
color_array_[i].Mul(color);
}
}
Color3i* FrameBufferBase::GetIterator(int i) {
return color_array_ + i;
}
// Length in pixels.
int FrameBufferBase::length() const { return n_color_array_; }
FrameBuffer::FrameBuffer(int n_pixels)
: FrameBufferBase(static_cast<Color3i*>(malloc(sizeof(Color3i) * n_pixels)),
n_pixels) {
}
FrameBuffer::~FrameBuffer() {
free(color_array_);
color_array_ = NULL;
n_color_array_ = 0;
}

View File

@@ -0,0 +1,35 @@
#ifndef FRAME_BUFFER_H_
#define FRAME_BUFFER_H_
struct Color3i;
class FrameBufferBase {
public:
FrameBufferBase(Color3i* array, int n_pixels);
virtual ~FrameBufferBase();
void Set(int i, const Color3i& c);
void Set(int i, int length, const Color3i& color);
void FillColor(const Color3i& color);
void ApplyBlendSubtract(const Color3i& color);
void ApplyBlendAdd(const Color3i& color);
void ApplyBlendMultiply(const Color3i& color);
Color3i* GetIterator(int i);
// Length in pixels.
int length() const;
protected:
Color3i* color_array_;
int n_color_array_;
};
class FrameBuffer : public FrameBufferBase {
public:
FrameBuffer(int n_pixels);
virtual ~FrameBuffer();
};
#endif // FRAME_BUFFER_H_

View File

@@ -0,0 +1,205 @@
#include "./led_layout_array.h"
#include "./settings.h"
LedColumns LedCurtinArray() {
static const int kLedRepeatTable[] = {
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22,
22
};
const int* out = kLedRepeatTable;
const int size = sizeof(kLedRepeatTable) / sizeof(*kLedRepeatTable);
return LedColumns(out, size);
}
LedColumns LedLuminescentArray() {
/////////////////////////////////////////////////////////
// Repeat data for the LED piano.
static const int kLedRepeatTable[] = {
25,
25,
26,
26,
27,
27,
28,
27,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
27,
28,
27,
27,
26,
26,
25,
25,
24,
24,
23,
23,
21,
20,
18,
17,
15,
14,
12,
11,
10,
10,
9,
9,
8,
8,
7,
7,
6,
6,
5,
6,
5,
5,
4,
5,
4,
4,
3,
4,
3,
3,
2,
2,
1
};
const int* out = kLedRepeatTable;
const int size = sizeof(kLedRepeatTable) / sizeof(*kLedRepeatTable);
return LedColumns(out, size);
}
LedColumns LedLayoutArray() {
if (kUseLedCurtin) {
return LedCurtinArray();
} else {
return LedLuminescentArray();
}
}

View File

@@ -0,0 +1,14 @@
#ifndef LED_ARRAY_H_
#define LED_ARRAY_H_
struct LedColumns {
LedColumns(const int* a, int l) : array(a), length(l) {}
LedColumns(const LedColumns& other) : array(other.array), length(other.length) {}
const int* array;
int length;
};
LedColumns LedLayoutArray();
#endif // LED_ARRAY_H_

View File

@@ -0,0 +1,33 @@
#ifndef LED_ROPE_INTERFACE_H_
#define LED_ROPE_INTERFACE_H_
#include "./color.h"
class LedRopeInterface {
public:
virtual ~LedRopeInterface() {}
virtual void Set(int i, const Color3i& c) = 0;
virtual void Set(int i, int length, const Color3i& color) {
for (int j = 0; j < length; ++j) {
Set(i + j, color);
}
}
virtual Color3i* GetIterator(int i) = 0;
virtual int length() const = 0;
virtual void DrawSequentialRepeat(int repeat) = 0;
virtual void DrawRepeat(const int* value_array, int array_length) = 0;
virtual void RawBeginDraw() = 0;
virtual void RawDrawPixel(const Color3i& c) = 0;
virtual void RawDrawPixels(const Color3i& c, int n) = 0;
virtual void RawDrawPixel(uint8_t r, uint8_t g, uint8_t b) = 0;
virtual void RawCommitDraw() = 0;
};
#endif // LED_ROPE_INTERFACE_H_

View File

@@ -0,0 +1,26 @@
#ifndef CONSTAINTS_H_
#define CONSTAINTS_H_
enum {
kNumKeys = 88, // Don't change this. 88 keys on a keyboard.
kNumLightsPerNote = 20,
// Controls the speed of the light rope. Higher values result in
// slower draw time, however the data integrity increases.
kLightClockDivisor = 12,
kNewLightClockDivisor = 16,
// Led Curtain is a mode that we used on the bus. When this is
// zero it's assume that we are using the TCL led lighting.
kUseLedCurtin = 0,
kShowFps = 0, // If true then the fps is printed to the console.
// Coda's keyboard indicates that this is the value when the
// foot pedal is pressed. There is probably a more universal
// way of detecting this value that works with more keyboards.
kMidiFootPedal = 64,
};
#endif // CONSTAINTS_H_

View File

@@ -0,0 +1,115 @@
#include <Arduino.h>
#include "./util.h"
#include "ApproximatingFunction.h"
#include "settings.h"
/*
// C - 0, C# - 1, D - 2, D# - 3... B - 11.
// http://cote.cc/w/wp-content/uploads/drupal/blog/logic-midi-note-numbers.png
*/
uint8_t FundamentalNote(int midi_note) {
return midi_note % 12;
}
float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
// Given an input time.
float AttackRemapFactor(uint32_t delta_t_ms) {
typedef InterpData<uint32_t, float> Datum;
static const Datum kData[] = {
Datum(0, .5),
Datum(80, 1.0),
};
static const int n = sizeof(kData) / sizeof(kData[0]);
return Interp(delta_t_ms, kData, n);
}
float MapDecayTime(uint8_t key_idx) {
typedef InterpData<uint8_t, float> Datum;
static const float bias = 1.3f;
// key then time for decay in milliseconds.
// First value is the KEY on the keyboard, second value is the
// time. The KEY must be IN ORDER or else the algorithm will fail.
static const Datum kInterpData[] = {
Datum(0, 21.0f * 1000.0f * bias),
Datum(11, 19.4 * 1000.0f * bias),
Datum(22, 15.1f * 1000.0f * bias),
Datum(35, 12.5f * 1000.0f * bias),
Datum(44, 10.f * 1000.0f * bias),
Datum(50, 8.1f * 1000.0f * bias),
Datum(53, 5.3f * 1000.0f * bias),
Datum(61, 4.0f * 1000.0f * bias),
Datum(66, 5.0f * 1000.0f * bias),
Datum(69, 4.6f * 1000.0f * bias),
Datum(70, 4.4f * 1000.0f * bias),
Datum(71, 4.3f * 1000.0f * bias),
Datum(74, 3.9f * 1000.0f * bias),
Datum(80, 1.9f * 1000.0f * bias),
Datum(81, 1.8f * 1000.0f * bias),
Datum(82, 1.7f * 1000.0f * bias),
Datum(83, 1.5f * 1000.0f * bias),
Datum(84, 1.3f * 1000.0f * bias),
Datum(86, 1.0f * 1000.0f * bias),
Datum(87, 0.9f * 1000.0f * bias),
};
static const int n = sizeof(kInterpData) / sizeof(kInterpData[0]);
float approx_val = Interp(key_idx, kInterpData, n);
return approx_val;
}
// Returns a value in the range 1->0 indicating how intense the note is. This
// value will go to 0 as time progresses, and will be 1 when the note is first
// pressed.
float CalcDecayFactor(bool sustain_pedal_on,
bool key_on,
int key_idx,
float velocity,
bool dampened_key,
float time_elapsed_ms) {
static const float kDefaultDecayTime = .2f * 1000.f;
static const float kBias = 1.10;
float decay_time = kDefaultDecayTime; // default - no sustain.
if (key_on || sustain_pedal_on || !dampened_key) {
decay_time = MapDecayTime(key_idx) * max(0.25f, velocity);
}
// decay_interp is a value which starts off as 1.0 to signify the start of the
// key press and gradually decreases to 0.0. For example, if the decay time is 1 second
// then at the time = 0s, decay_interp is 1.0, and after one second decay_interp is 0.0
float intensity_factor = mapf(time_elapsed_ms,
0.0, decay_time * kBias,
1.0, 0.0); // Startup at full brightness -> no brighness.
// When decay_interp reaches 0, the lighting sequence is effectively finished. However
// because this is time based and time keeps on going this value will move into negative
// territory, we take care of this by simply clamping all negative values to 0.0.
intensity_factor = constrain(intensity_factor, 0.0f, 1.0f);
return intensity_factor;
}
float ToBrightness(int velocity) {
typedef InterpData<int, float> Datum;
static const Datum kData[] = {
Datum(0, 0.02),
Datum(32, 0.02),
Datum(64, 0.10),
Datum(80, 0.30),
Datum(90, 0.90),
Datum(100, 1.00),
Datum(120, 1.00),
Datum(127, 1.00)
};
static const int n = sizeof(kData) / sizeof(kData[0]);
const float val = Interp(velocity, kData, n);
return val;
}

View File

@@ -0,0 +1,39 @@
#ifndef UTIL_H_
#define UTIL_H_
#include "fl/stdint.h"
#include "./ApproximatingFunction.h"
#include "./settings.h"
#ifndef round
#define round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))
#endif // round
/*
// C - 0, C# - 1, D - 2, D# - 3... B - 11.
// http://cote.cc/w/wp-content/uploads/drupal/blog/logic-midi-note-numbers.png
*/
uint8_t FundamentalNote(int midi_note);
float mapf(float x, float in_min, float in_max, float out_min, float out_max);
// Given an input time.
float AttackRemapFactor(uint32_t delta_t_ms);
float MapDecayTime(uint8_t key_idx);
// Returns a value in the range 1->0 indicating how intense the note is. This
// value will go to 0 as time progresses, and will be 1 when the note is first
// pressed.
float CalcDecayFactor(bool sustain_pedal_on,
bool key_on,
int key_idx,
float velocity,
bool dampened_key,
float time_elapsed_ms);
float ToBrightness(int velocity);
#endif // UTIL_H_