imported from "final" folder
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
/// This is a work in progress showcasing a complex MIDI keyboard
|
||||
/// visualizer.
|
||||
/// To run this compiler use
|
||||
/// > pip install fastled
|
||||
/// Then go to your sketch directory and run
|
||||
/// > fastled
|
||||
|
||||
#include "shared/defs.h"
|
||||
|
||||
#if !ENABLE_SKETCH
|
||||
// avr can't compile this, neither can the esp8266.
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else
|
||||
|
||||
|
||||
//#define DEBUG_PAINTER
|
||||
//#define DEBUG_KEYBOARD 1
|
||||
|
||||
// Repeated keyboard presses in the main loop
|
||||
#define DEBUG_FORCED_KEYBOARD
|
||||
// #define DEBUG_MIDI_KEY 72
|
||||
|
||||
#define MIDI_SERIAL_PORT Serial1
|
||||
|
||||
#define FASTLED_UI // Brings in the UI components.
|
||||
#include "FastLED.h"
|
||||
|
||||
// H
|
||||
#include <Arduino.h>
|
||||
#include "shared/Keyboard.h"
|
||||
#include "shared/color.h"
|
||||
#include "shared/led_layout_array.h"
|
||||
#include "shared/Keyboard.h"
|
||||
#include "shared/Painter.h"
|
||||
#include "shared/settings.h"
|
||||
#include "arduino/LedRopeTCL.h"
|
||||
#include "arduino/ui_state.h"
|
||||
#include "shared/dprint.h"
|
||||
#include "fl/dbg.h"
|
||||
#include "fl/ui.h"
|
||||
#include "fl/unused.h"
|
||||
|
||||
// Spoof the midi library so it thinks it's running on an arduino.
|
||||
//#ifndef ARDUINO
|
||||
//#define ARDUINO 1
|
||||
//#endif
|
||||
|
||||
#ifdef MIDI_AUTO_INSTANCIATE
|
||||
#undef MIDI_AUTO_INSTANCIATE
|
||||
#define MIDI_AUTO_INSTANCIATE 0
|
||||
#endif
|
||||
|
||||
#include "arduino/MIDI.h"
|
||||
|
||||
|
||||
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MY_MIDI);
|
||||
|
||||
|
||||
FASTLED_TITLE("Luminescent Grand");
|
||||
FASTLED_DESCRIPTION("A midi keyboard visualizer.");
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// Light rope and keyboard.
|
||||
LedRopeTCL led_rope(kNumKeys);
|
||||
KeyboardState keyboard;
|
||||
|
||||
|
||||
////////////////////////////////////
|
||||
// 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(byte channel, byte midi_note, byte velocity) {
|
||||
FL_UNUSED(channel);
|
||||
FASTLED_DBG("HandleNoteOn: midi_note = " << int(midi_note) << ", velocity = " << int(velocity));
|
||||
keyboard.HandleNoteOn(midi_note, velocity, color_selector.curr_val(), millis());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// 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(byte channel, byte midi_note, byte velocity) {
|
||||
FL_UNUSED(channel);
|
||||
FASTLED_DBG("HandleNoteOn: midi_note = " << int(midi_note) << ", velocity = " << int(velocity));
|
||||
keyboard.HandleNoteOff(midi_note, velocity, millis());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// 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(byte channel, byte note, byte pressure) {
|
||||
FL_UNUSED(channel);
|
||||
keyboard.HandleAfterTouchPoly(note, pressure);
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// Detects whether the foot pedal has been touched.
|
||||
void HandleControlChange(byte channel, byte d1, byte d2) {
|
||||
FL_UNUSED(channel);
|
||||
keyboard.HandleControlChange(d1, d2);
|
||||
}
|
||||
|
||||
void HandleAfterTouchChannel(byte channel, byte pressure) {
|
||||
FL_UNUSED(channel);
|
||||
FL_UNUSED(pressure);
|
||||
#if 0 // Disabled for now.
|
||||
if (0 == pressure) {
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
Key& key = keyboard.keys_[i];
|
||||
key.SetOff();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// Called once when the app starts.
|
||||
void setup() {
|
||||
FASTLED_DBG("setup");
|
||||
// Serial port for logging.
|
||||
Serial.begin(57600);
|
||||
//start serial with midi baudrate 31250
|
||||
// Initiate MIDI communications, listen to all channels
|
||||
MY_MIDI.begin(MIDI_CHANNEL_OMNI);
|
||||
|
||||
// Connect the HandleNoteOn function to the library, so it is called upon reception of a NoteOn.
|
||||
MY_MIDI.setHandleNoteOn(HandleNoteOn);
|
||||
MY_MIDI.setHandleNoteOff(HandleNoteOff);
|
||||
MY_MIDI.setHandleAfterTouchPoly(HandleAfterTouchPoly);
|
||||
MY_MIDI.setHandleAfterTouchChannel(HandleAfterTouchChannel);
|
||||
MY_MIDI.setHandleControlChange(HandleControlChange);
|
||||
|
||||
ui_init();
|
||||
}
|
||||
|
||||
void DbgDoSimulatedKeyboardPress() {
|
||||
#ifdef DEBUG_FORCED_KEYBOARD
|
||||
static uint32_t start_time = 0;
|
||||
static bool toggle = 0;
|
||||
|
||||
const uint32_t time_on = 25;
|
||||
const uint32_t time_off = 500;
|
||||
|
||||
// Just force it on whenever this function is called.
|
||||
is_debugging = true;
|
||||
|
||||
uint32_t now = millis();
|
||||
uint32_t delta_time = now - start_time;
|
||||
|
||||
|
||||
uint32_t threshold_time = toggle ? time_off : time_on;
|
||||
if (delta_time < threshold_time) {
|
||||
return;
|
||||
}
|
||||
|
||||
int random_key = random(0, 88);
|
||||
|
||||
start_time = now;
|
||||
if (toggle) {
|
||||
HandleNoteOn(0, random_key, 64);
|
||||
} else {
|
||||
HandleNoteOff(0, random_key, 82);
|
||||
}
|
||||
toggle = !toggle;
|
||||
#endif
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////;p
|
||||
// Repeatedly called by the app.
|
||||
void loop() {
|
||||
//FASTLED_DBG("loop");
|
||||
|
||||
|
||||
// Calculate dt.
|
||||
static uint32_t s_prev_time = 0;
|
||||
uint32_t prev_time = 0;
|
||||
FASTLED_UNUSED(prev_time); // actually used in perf tests.
|
||||
uint32_t now_ms = millis();
|
||||
uint32_t delta_ms = now_ms - s_prev_time;
|
||||
s_prev_time = now_ms;
|
||||
|
||||
if (!is_debugging) {
|
||||
if (Serial.available() > 0) {
|
||||
int v = Serial.read();
|
||||
if (v == 'd') {
|
||||
is_debugging = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DbgDoSimulatedKeyboardPress();
|
||||
|
||||
const unsigned long start_time = millis();
|
||||
// Each frame we call the midi processor 100 times to make sure that all notes
|
||||
// are processed.
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
MY_MIDI.read();
|
||||
}
|
||||
|
||||
const unsigned long midi_time = millis() - start_time;
|
||||
|
||||
// Updates keyboard: releases sustained keys that.
|
||||
|
||||
const uint32_t keyboard_time_start = millis();
|
||||
|
||||
// This is kind of a hack... but give the keyboard a future time
|
||||
// so that all keys just pressed get a value > 0 for their time
|
||||
// durations.
|
||||
keyboard.Update(now_ms + delta_ms, delta_ms);
|
||||
const uint32_t keyboard_delta_time = millis() - keyboard_time_start;
|
||||
|
||||
ui_state ui_st = ui_update(now_ms, delta_ms);
|
||||
|
||||
//dprint("vis selector = ");
|
||||
//dprintln(vis_state);
|
||||
|
||||
|
||||
// These int values are for desting the performance of the
|
||||
// app. If the app ever runs slow then set kShowFps to 1
|
||||
// in the settings.h file.
|
||||
const unsigned long start_painting = millis();
|
||||
FASTLED_UNUSED(start_painting);
|
||||
|
||||
|
||||
// Paints the keyboard using the led_rope.
|
||||
Painter::VisState which_vis = Painter::VisState(ui_st.which_visualizer);
|
||||
Painter::Paint(now_ms, delta_ms, which_vis, &keyboard, &led_rope);
|
||||
|
||||
const unsigned long paint_time = millis() - start_time;
|
||||
const unsigned long total_time = midi_time + paint_time + keyboard_delta_time;
|
||||
|
||||
if (kShowFps) {
|
||||
float fps = 1.0f/(float(total_time) / 1000.f);
|
||||
Serial.print("fps - "); Serial.println(fps);
|
||||
Serial.print("midi time - "); Serial.println(midi_time);
|
||||
Serial.print("keyboard update time - "); Serial.println(keyboard_delta_time);
|
||||
Serial.print("draw & paint time - "); Serial.println(paint_time);
|
||||
}
|
||||
|
||||
EVERY_N_SECONDS(1) {
|
||||
FASTLED_DBG("is_debugging = " << is_debugging);
|
||||
}
|
||||
|
||||
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
|
||||
#endif // __AVR__
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyleft (c) 2012, Zach Vorhies
|
||||
// Public domain, no rights reserved.
|
||||
// This object holds a frame buffer and effects can be applied. This is a higher level
|
||||
// object than the TCL class which this object uses for drawing.
|
||||
|
||||
//#include "./tcl.h"
|
||||
#include <Arduino.h>
|
||||
#include "../shared/color.h"
|
||||
#include "../shared/framebuffer.h"
|
||||
#include "../shared/settings.h"
|
||||
#include "./LedRopeTCL.h"
|
||||
#include "../shared/led_layout_array.h"
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "fl/dbg.h"
|
||||
#include "fl/ui.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
|
||||
#define CHIPSET WS2812
|
||||
#define PIN_DATA 1
|
||||
#define PIN_CLOCK 2
|
||||
|
||||
namespace {
|
||||
|
||||
UIButton buttonAllWhite("All white");
|
||||
|
||||
ScreenMap init_screenmap() {
|
||||
LedColumns cols = LedLayoutArray();
|
||||
const int length = cols.length;
|
||||
int sum = 0;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
sum += cols.array[i];
|
||||
}
|
||||
ScreenMap screen_map(sum, 0.8f);
|
||||
int curr_idx = 0;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
int n = cols.array[i];
|
||||
int stagger = i % 2 ? 4 : 0;
|
||||
for (int j = 0; j < n; ++j) {
|
||||
fl::vec2f xy(i*4, j*8 + stagger);
|
||||
screen_map.set(curr_idx++, xy);
|
||||
}
|
||||
}
|
||||
|
||||
return screen_map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void LedRopeTCL::PreDrawSetup() {
|
||||
if (!lazy_initialized_) {
|
||||
// This used to do something, now it does nothing.
|
||||
lazy_initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void LedRopeTCL::RawBeginDraw() {
|
||||
PreDrawSetup();
|
||||
led_buffer_.clear();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void LedRopeTCL::RawDrawPixel(const Color3i& c) {
|
||||
RawDrawPixel(c.r_, c.g_, c.b_);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void LedRopeTCL::RawDrawPixel(byte r, byte g, byte b) {
|
||||
if (led_buffer_.size() >= mScreenMap.getLength()) {
|
||||
return;
|
||||
}
|
||||
if (buttonAllWhite.isPressed()) {
|
||||
r = 0xff;
|
||||
g = 0xff;
|
||||
b = 0xff;
|
||||
}
|
||||
CRGB c(r, g, b);
|
||||
led_buffer_.push_back(CRGB(r, g, b));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void LedRopeTCL::RawDrawPixels(const Color3i& c, int n) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
RawDrawPixel(c);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void LedRopeTCL::set_draw_offset(int val) {
|
||||
draw_offset_ = constrain(val, 0, frame_buffer_.length());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void LedRopeTCL::RawCommitDraw() {
|
||||
FASTLED_WARN("\n\n############## COMMIT DRAW ################\n\n");
|
||||
if (!controller_added_) {
|
||||
controller_added_ = true;
|
||||
CRGB* leds = led_buffer_.data();
|
||||
size_t n_leds = led_buffer_.size();
|
||||
FastLED.addLeds<APA102, PIN_DATA, PIN_CLOCK>(leds, n_leds).setScreenMap(mScreenMap);
|
||||
}
|
||||
FASTLED_WARN("FastLED.show");
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
LedRopeTCL::LedRopeTCL(int n_pixels)
|
||||
: draw_offset_(0), lazy_initialized_(false), frame_buffer_(n_pixels) {
|
||||
mScreenMap = init_screenmap();
|
||||
led_buffer_.reserve(mScreenMap.getLength());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
LedRopeTCL::~LedRopeTCL() {
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void LedRopeTCL::Draw() {
|
||||
RawBeginDraw();
|
||||
|
||||
const Color3i* begin = GetIterator(0);
|
||||
const Color3i* middle = GetIterator(draw_offset_);
|
||||
const Color3i* end = GetIterator(length() - 1);
|
||||
|
||||
for (const Color3i* it = middle; it != end; ++it) {
|
||||
RawDrawPixel(*it);
|
||||
}
|
||||
for (const Color3i* it = begin; it != middle; ++it) {
|
||||
RawDrawPixel(*it);
|
||||
}
|
||||
RawCommitDraw();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void LedRopeTCL::DrawSequentialRepeat(int repeat) {
|
||||
RawBeginDraw();
|
||||
|
||||
const Color3i* begin = GetIterator(0);
|
||||
const Color3i* middle = GetIterator(draw_offset_);
|
||||
const Color3i* end = GetIterator(length());
|
||||
for (const Color3i* it = middle; it != end; ++it) {
|
||||
for (int i = 0; i < repeat; ++i) {
|
||||
RawDrawPixel(it->r_, it->g_, it->b_);
|
||||
}
|
||||
}
|
||||
for (const Color3i* it = begin; it != middle; ++it) {
|
||||
for (int i = 0; i < repeat; ++i) {
|
||||
RawDrawPixel(it->r_, it->g_, it->b_);
|
||||
}
|
||||
}
|
||||
RawCommitDraw();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void LedRopeTCL::DrawRepeat(const int* value_array, int array_length) {
|
||||
RawBeginDraw();
|
||||
|
||||
// Make sure that the number of colors to repeat does not exceed the length
|
||||
// of the rope.
|
||||
const int len = MIN(array_length, frame_buffer_.length());
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
const Color3i* cur_color = GetIterator(i); // Current color.
|
||||
const int repeat_count = value_array[i];
|
||||
// Repeatedly send the same color down the led rope.
|
||||
for (int k = 0; k < repeat_count; ++k) {
|
||||
RawDrawPixel(cur_color->r_, cur_color->g_, cur_color->b_);
|
||||
}
|
||||
}
|
||||
// Finish the drawing.
|
||||
RawCommitDraw();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyleft (c) 2012, Zach Vorhies
|
||||
// Public domain, no rights reserved.
|
||||
// This object holds a frame buffer and effects can be applied. This is a higher level
|
||||
// object than the TCL class which this object uses for drawing.
|
||||
|
||||
#ifndef LED_REPE_TCL_H_
|
||||
#define LED_REPE_TCL_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "../shared/color.h"
|
||||
#include "../shared/framebuffer.h"
|
||||
#include "../shared/led_rope_interface.h"
|
||||
|
||||
#include "fl/vector.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/screenmap.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
// LedRopeTCL is a C++ wrapper around the Total Control Lighting LED rope
|
||||
// device driver (TCL.h). This wrapper includes automatic setup of the LED
|
||||
// rope and allows the user to use a graphics-state like interface for
|
||||
// talking to the rope. A copy of the rope led state is held in this class
|
||||
// which makes blending operations easier. After all changes by the user
|
||||
// are applied to the rope, the hardware is updated via an explicit Draw()
|
||||
// command.
|
||||
//
|
||||
// Whole-rope blink Example:
|
||||
// #include <SPI.h>
|
||||
// #include <TCL.h> // From CoolNeon (https://bitbucket.org/devries/arduino-tcl)
|
||||
// #include "LedRopeTCL.h"
|
||||
// LedRopeTCL led_rope(100); // 100 led-strand.
|
||||
//
|
||||
// void setup() {} // No setup necessary for Led rope.
|
||||
// void loop() {
|
||||
// led_rope.FillColor(LedRopeTCL::Color3i::Black());
|
||||
// led_rope.Draw();
|
||||
// delay(1000);
|
||||
// led_rope.FillColor(LedRopeTCL::Color3i::White());
|
||||
// led_rope.Draw();
|
||||
// delay(1000);
|
||||
// }
|
||||
|
||||
|
||||
class LedRopeTCL : public LedRopeInterface {
|
||||
public:
|
||||
LedRopeTCL(int n_pixels);
|
||||
virtual ~LedRopeTCL();
|
||||
|
||||
void Draw();
|
||||
void DrawSequentialRepeat(int repeat);
|
||||
void DrawRepeat(const int* value_array, int array_length);
|
||||
void set_draw_offset(int val);
|
||||
|
||||
virtual void Set(int i, const Color3i& c) {
|
||||
frame_buffer_.Set(i, c);
|
||||
}
|
||||
|
||||
Color3i* GetIterator(int i) {
|
||||
return frame_buffer_.GetIterator(i);
|
||||
}
|
||||
|
||||
int length() const { return frame_buffer_.length(); }
|
||||
|
||||
void RawBeginDraw();
|
||||
void RawDrawPixel(const Color3i& c);
|
||||
void RawDrawPixels(const Color3i& c, int n);
|
||||
void RawDrawPixel(uint8_t r, uint8_t g, uint8_t b);
|
||||
void RawCommitDraw();
|
||||
|
||||
protected:
|
||||
void PreDrawSetup();
|
||||
int draw_offset_ = 0;
|
||||
bool lazy_initialized_;
|
||||
FrameBuffer frame_buffer_;
|
||||
bool controller_added_ = false;
|
||||
fl::HeapVector<CRGB> led_buffer_;
|
||||
fl::ScreenMap mScreenMap;
|
||||
};
|
||||
|
||||
#endif // LED_REPE_TCL_H_
|
||||
@@ -0,0 +1,115 @@
|
||||
/*!
|
||||
* @file MIDI.cpp
|
||||
* Project Arduino MIDI Library
|
||||
* @brief MIDI Library for the Arduino
|
||||
* @author Francois Best
|
||||
* @date 24/02/11
|
||||
* @license MIT - Copyright (c) 2015 Francois Best
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "MIDI.h"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
BEGIN_MIDI_NAMESPACE
|
||||
|
||||
/*! \brief Encode System Exclusive messages.
|
||||
SysEx messages are encoded to guarantee transmission of data bytes higher than
|
||||
127 without breaking the MIDI protocol. Use this static method to convert the
|
||||
data you want to send.
|
||||
\param inData The data to encode.
|
||||
\param outSysEx The output buffer where to store the encoded message.
|
||||
\param inLength The length of the input buffer.
|
||||
\param inFlipHeaderBits True for Korg and other who store MSB in reverse order
|
||||
\return The length of the encoded output buffer.
|
||||
@see decodeSysEx
|
||||
Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
|
||||
*/
|
||||
unsigned encodeSysEx(const byte* inData,
|
||||
byte* outSysEx,
|
||||
unsigned inLength,
|
||||
bool inFlipHeaderBits)
|
||||
{
|
||||
unsigned outLength = 0; // Num bytes in output array.
|
||||
byte count = 0; // Num 7bytes in a block.
|
||||
outSysEx[0] = 0;
|
||||
|
||||
for (unsigned i = 0; i < inLength; ++i)
|
||||
{
|
||||
const byte data = inData[i];
|
||||
const byte msb = data >> 7;
|
||||
const byte body = data & 0x7f;
|
||||
|
||||
outSysEx[0] |= (msb << (inFlipHeaderBits ? count : (6 - count)));
|
||||
outSysEx[1 + count] = body;
|
||||
|
||||
if (count++ == 6)
|
||||
{
|
||||
outSysEx += 8;
|
||||
outLength += 8;
|
||||
outSysEx[0] = 0;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
return outLength + count + (count != 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
/*! \brief Decode System Exclusive messages.
|
||||
SysEx messages are encoded to guarantee transmission of data bytes higher than
|
||||
127 without breaking the MIDI protocol. Use this static method to reassemble
|
||||
your received message.
|
||||
\param inSysEx The SysEx data received from MIDI in.
|
||||
\param outData The output buffer where to store the decrypted message.
|
||||
\param inLength The length of the input buffer.
|
||||
\param inFlipHeaderBits True for Korg and other who store MSB in reverse order
|
||||
\return The length of the output buffer.
|
||||
@see encodeSysEx @see getSysExArrayLength
|
||||
Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
|
||||
*/
|
||||
unsigned decodeSysEx(const byte* inSysEx,
|
||||
byte* outData,
|
||||
unsigned inLength,
|
||||
bool inFlipHeaderBits)
|
||||
{
|
||||
unsigned count = 0;
|
||||
byte msbStorage = 0;
|
||||
byte byteIndex = 0;
|
||||
|
||||
for (unsigned i = 0; i < inLength; ++i)
|
||||
{
|
||||
if ((i % 8) == 0)
|
||||
{
|
||||
msbStorage = inSysEx[i];
|
||||
byteIndex = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
const byte body = inSysEx[i];
|
||||
const byte shift = inFlipHeaderBits ? 6 - byteIndex : byteIndex;
|
||||
const byte msb = byte(((msbStorage >> shift) & 1) << 7);
|
||||
byteIndex--;
|
||||
outData[count++] = msb | body;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
END_MIDI_NAMESPACE
|
||||
@@ -0,0 +1,308 @@
|
||||
/*!
|
||||
* @file MIDI.h
|
||||
* Project Arduino MIDI Library
|
||||
* @brief MIDI Library for the Arduino
|
||||
* @author Francois Best, lathoub
|
||||
* @date 24/02/11
|
||||
* @license MIT - Copyright (c) 2015 Francois Best
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "midi_Defs.h"
|
||||
#include "midi_Platform.h"
|
||||
#include "midi_Settings.h"
|
||||
#include "midi_Message.h"
|
||||
|
||||
#include "serialMIDI.h"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
BEGIN_MIDI_NAMESPACE
|
||||
|
||||
#define MIDI_LIBRARY_VERSION 0x050000
|
||||
#define MIDI_LIBRARY_VERSION_MAJOR 5
|
||||
#define MIDI_LIBRARY_VERSION_MINOR 0
|
||||
#define MIDI_LIBRARY_VERSION_PATCH 0
|
||||
|
||||
|
||||
/*! \brief The main class for MIDI handling.
|
||||
It is templated over the type of serial port to provide abstraction from
|
||||
the hardware interface, meaning you can use HardwareSerial, SoftwareSerial
|
||||
or ak47's Uart classes. The only requirement is that the class implements
|
||||
the begin, read, write and available methods.
|
||||
*/
|
||||
template<class Transport, class _Settings = DefaultSettings, class _Platform = DefaultPlatform>
|
||||
class MidiInterface
|
||||
{
|
||||
public:
|
||||
typedef _Settings Settings;
|
||||
typedef _Platform Platform;
|
||||
typedef Message<Settings::SysExMaxSize> MidiMessage;
|
||||
|
||||
public:
|
||||
inline MidiInterface(Transport&);
|
||||
inline ~MidiInterface();
|
||||
|
||||
public:
|
||||
void begin(Channel inChannel = 1);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MIDI Output
|
||||
|
||||
public:
|
||||
inline void sendNoteOn(DataByte inNoteNumber,
|
||||
DataByte inVelocity,
|
||||
Channel inChannel);
|
||||
|
||||
inline void sendNoteOff(DataByte inNoteNumber,
|
||||
DataByte inVelocity,
|
||||
Channel inChannel);
|
||||
|
||||
inline void sendProgramChange(DataByte inProgramNumber,
|
||||
Channel inChannel);
|
||||
|
||||
inline void sendControlChange(DataByte inControlNumber,
|
||||
DataByte inControlValue,
|
||||
Channel inChannel);
|
||||
|
||||
inline void sendPitchBend(int inPitchValue, Channel inChannel);
|
||||
inline void sendPitchBend(double inPitchValue, Channel inChannel);
|
||||
|
||||
inline void sendPolyPressure(DataByte inNoteNumber,
|
||||
DataByte inPressure,
|
||||
Channel inChannel) __attribute__ ((deprecated));
|
||||
|
||||
inline void sendAfterTouch(DataByte inPressure,
|
||||
Channel inChannel);
|
||||
inline void sendAfterTouch(DataByte inNoteNumber,
|
||||
DataByte inPressure,
|
||||
Channel inChannel);
|
||||
|
||||
inline void sendSysEx(unsigned inLength,
|
||||
const byte* inArray,
|
||||
bool inArrayContainsBoundaries = false);
|
||||
|
||||
inline void sendTimeCodeQuarterFrame(DataByte inTypeNibble,
|
||||
DataByte inValuesNibble);
|
||||
inline void sendTimeCodeQuarterFrame(DataByte inData);
|
||||
|
||||
inline void sendSongPosition(unsigned inBeats);
|
||||
inline void sendSongSelect(DataByte inSongNumber);
|
||||
inline void sendTuneRequest();
|
||||
|
||||
inline void sendCommon(MidiType inType, unsigned = 0);
|
||||
|
||||
inline void sendClock() { sendRealTime(Clock); };
|
||||
inline void sendStart() { sendRealTime(Start); };
|
||||
inline void sendStop() { sendRealTime(Stop); };
|
||||
inline void sendTick() { sendRealTime(Tick); };
|
||||
inline void sendContinue() { sendRealTime(Continue); };
|
||||
inline void sendActiveSensing() { sendRealTime(ActiveSensing); };
|
||||
inline void sendSystemReset() { sendRealTime(SystemReset); };
|
||||
|
||||
inline void sendRealTime(MidiType inType);
|
||||
|
||||
inline void beginRpn(unsigned inNumber,
|
||||
Channel inChannel);
|
||||
inline void sendRpnValue(unsigned inValue,
|
||||
Channel inChannel);
|
||||
inline void sendRpnValue(byte inMsb,
|
||||
byte inLsb,
|
||||
Channel inChannel);
|
||||
inline void sendRpnIncrement(byte inAmount,
|
||||
Channel inChannel);
|
||||
inline void sendRpnDecrement(byte inAmount,
|
||||
Channel inChannel);
|
||||
inline void endRpn(Channel inChannel);
|
||||
|
||||
inline void beginNrpn(unsigned inNumber,
|
||||
Channel inChannel);
|
||||
inline void sendNrpnValue(unsigned inValue,
|
||||
Channel inChannel);
|
||||
inline void sendNrpnValue(byte inMsb,
|
||||
byte inLsb,
|
||||
Channel inChannel);
|
||||
inline void sendNrpnIncrement(byte inAmount,
|
||||
Channel inChannel);
|
||||
inline void sendNrpnDecrement(byte inAmount,
|
||||
Channel inChannel);
|
||||
inline void endNrpn(Channel inChannel);
|
||||
|
||||
inline void send(const MidiMessage&);
|
||||
|
||||
public:
|
||||
void send(MidiType inType,
|
||||
DataByte inData1,
|
||||
DataByte inData2,
|
||||
Channel inChannel);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MIDI Input
|
||||
|
||||
public:
|
||||
inline bool read();
|
||||
inline bool read(Channel inChannel);
|
||||
|
||||
public:
|
||||
inline MidiType getType() const;
|
||||
inline Channel getChannel() const;
|
||||
inline DataByte getData1() const;
|
||||
inline DataByte getData2() const;
|
||||
inline const byte* getSysExArray() const;
|
||||
inline unsigned getSysExArrayLength() const;
|
||||
inline bool check() const;
|
||||
|
||||
public:
|
||||
inline Channel getInputChannel() const;
|
||||
inline void setInputChannel(Channel inChannel);
|
||||
|
||||
public:
|
||||
static inline MidiType getTypeFromStatusByte(byte inStatus);
|
||||
static inline Channel getChannelFromStatusByte(byte inStatus);
|
||||
static inline bool isChannelMessage(MidiType inType);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Input Callbacks
|
||||
|
||||
public:
|
||||
inline void setHandleMessage(void (*fptr)(const MidiMessage&)) { mMessageCallback = fptr; };
|
||||
inline void setHandleError(ErrorCallback fptr) { mErrorCallback = fptr; }
|
||||
inline void setHandleNoteOff(NoteOffCallback fptr) { mNoteOffCallback = fptr; }
|
||||
inline void setHandleNoteOn(NoteOnCallback fptr) { mNoteOnCallback = fptr; }
|
||||
inline void setHandleAfterTouchPoly(AfterTouchPolyCallback fptr) { mAfterTouchPolyCallback = fptr; }
|
||||
inline void setHandleControlChange(ControlChangeCallback fptr) { mControlChangeCallback = fptr; }
|
||||
inline void setHandleProgramChange(ProgramChangeCallback fptr) { mProgramChangeCallback = fptr; }
|
||||
inline void setHandleAfterTouchChannel(AfterTouchChannelCallback fptr) { mAfterTouchChannelCallback = fptr; }
|
||||
inline void setHandlePitchBend(PitchBendCallback fptr) { mPitchBendCallback = fptr; }
|
||||
inline void setHandleSystemExclusive(SystemExclusiveCallback fptr) { mSystemExclusiveCallback = fptr; }
|
||||
inline void setHandleTimeCodeQuarterFrame(TimeCodeQuarterFrameCallback fptr) { mTimeCodeQuarterFrameCallback = fptr; }
|
||||
inline void setHandleSongPosition(SongPositionCallback fptr) { mSongPositionCallback = fptr; }
|
||||
inline void setHandleSongSelect(SongSelectCallback fptr) { mSongSelectCallback = fptr; }
|
||||
inline void setHandleTuneRequest(TuneRequestCallback fptr) { mTuneRequestCallback = fptr; }
|
||||
inline void setHandleClock(ClockCallback fptr) { mClockCallback = fptr; }
|
||||
inline void setHandleStart(StartCallback fptr) { mStartCallback = fptr; }
|
||||
inline void setHandleTick(TickCallback fptr) { mTickCallback = fptr; }
|
||||
inline void setHandleContinue(ContinueCallback fptr) { mContinueCallback = fptr; }
|
||||
inline void setHandleStop(StopCallback fptr) { mStopCallback = fptr; }
|
||||
inline void setHandleActiveSensing(ActiveSensingCallback fptr) { mActiveSensingCallback = fptr; }
|
||||
inline void setHandleSystemReset(SystemResetCallback fptr) { mSystemResetCallback = fptr; }
|
||||
|
||||
inline void disconnectCallbackFromType(MidiType inType);
|
||||
|
||||
private:
|
||||
void launchCallback();
|
||||
|
||||
void (*mMessageCallback)(const MidiMessage& message) = nullptr;
|
||||
ErrorCallback mErrorCallback = nullptr;
|
||||
NoteOffCallback mNoteOffCallback = nullptr;
|
||||
NoteOnCallback mNoteOnCallback = nullptr;
|
||||
AfterTouchPolyCallback mAfterTouchPolyCallback = nullptr;
|
||||
ControlChangeCallback mControlChangeCallback = nullptr;
|
||||
ProgramChangeCallback mProgramChangeCallback = nullptr;
|
||||
AfterTouchChannelCallback mAfterTouchChannelCallback = nullptr;
|
||||
PitchBendCallback mPitchBendCallback = nullptr;
|
||||
SystemExclusiveCallback mSystemExclusiveCallback = nullptr;
|
||||
TimeCodeQuarterFrameCallback mTimeCodeQuarterFrameCallback = nullptr;
|
||||
SongPositionCallback mSongPositionCallback = nullptr;
|
||||
SongSelectCallback mSongSelectCallback = nullptr;
|
||||
TuneRequestCallback mTuneRequestCallback = nullptr;
|
||||
ClockCallback mClockCallback = nullptr;
|
||||
StartCallback mStartCallback = nullptr;
|
||||
TickCallback mTickCallback = nullptr;
|
||||
ContinueCallback mContinueCallback = nullptr;
|
||||
StopCallback mStopCallback = nullptr;
|
||||
ActiveSensingCallback mActiveSensingCallback = nullptr;
|
||||
SystemResetCallback mSystemResetCallback = nullptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MIDI Soft Thru
|
||||
|
||||
public:
|
||||
inline Thru::Mode getFilterMode() const;
|
||||
inline bool getThruState() const;
|
||||
|
||||
inline void turnThruOn(Thru::Mode inThruFilterMode = Thru::Full);
|
||||
inline void turnThruOff();
|
||||
inline void setThruFilterMode(Thru::Mode inThruFilterMode);
|
||||
|
||||
private:
|
||||
void thruFilter(byte inChannel);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MIDI Parsing
|
||||
|
||||
private:
|
||||
bool parse();
|
||||
inline void handleNullVelocityNoteOnAsNoteOff();
|
||||
inline bool inputFilter(Channel inChannel);
|
||||
inline void resetInput();
|
||||
inline void UpdateLastSentTime();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Transport
|
||||
|
||||
public:
|
||||
Transport* getTransport() { return &mTransport; };
|
||||
|
||||
private:
|
||||
Transport& mTransport;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Internal variables
|
||||
|
||||
private:
|
||||
Channel mInputChannel;
|
||||
StatusByte mRunningStatus_RX;
|
||||
StatusByte mRunningStatus_TX;
|
||||
byte mPendingMessage[3];
|
||||
unsigned mPendingMessageExpectedLength;
|
||||
unsigned mPendingMessageIndex;
|
||||
unsigned mCurrentRpnNumber;
|
||||
unsigned mCurrentNrpnNumber;
|
||||
bool mThruActivated : 1;
|
||||
Thru::Mode mThruFilterMode : 7;
|
||||
MidiMessage mMessage;
|
||||
unsigned long mLastMessageSentTime;
|
||||
unsigned long mLastMessageReceivedTime;
|
||||
unsigned long mSenderActiveSensingPeriodicity;
|
||||
bool mReceiverActiveSensingActivated;
|
||||
int8_t mLastError;
|
||||
|
||||
private:
|
||||
inline StatusByte getStatus(MidiType inType,
|
||||
Channel inChannel) const;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
unsigned encodeSysEx(const byte* inData,
|
||||
byte* outSysEx,
|
||||
unsigned inLength,
|
||||
bool inFlipHeaderBits = false);
|
||||
unsigned decodeSysEx(const byte* inSysEx,
|
||||
byte* outData,
|
||||
unsigned inLength,
|
||||
bool inFlipHeaderBits = false);
|
||||
|
||||
END_MIDI_NAMESPACE
|
||||
|
||||
#include "MIDI.hpp"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,157 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "fl/ui.h"
|
||||
#include "fl/dbg.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
// Done by hand. Old school.
|
||||
class ToggleButton {
|
||||
public:
|
||||
ToggleButton(int pin) : pin_(pin), on_(false), debounce_timestamp_(0), changed_(false) {
|
||||
pinMode(pin_, OUTPUT);
|
||||
digitalWrite(pin_, LOW);
|
||||
delay(1);
|
||||
}
|
||||
|
||||
// true - button is pressed.
|
||||
bool Read() {
|
||||
Update(millis());
|
||||
return changed_;
|
||||
}
|
||||
|
||||
void Update(uint32_t time_now) {
|
||||
if ((time_now - debounce_timestamp_) < 150) {
|
||||
changed_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int val = Read_Internal();
|
||||
changed_ = on_ != val;
|
||||
|
||||
if (changed_) { // Has the toggle switch changed?
|
||||
on_ = val; // Set the new toggle switch value.
|
||||
// Protect against debouncing artifacts by putting a 200ms delay from the
|
||||
// last time the value changed.
|
||||
if ((time_now - debounce_timestamp_) > 150) {
|
||||
//++curr_val_; // ... and increment the value.
|
||||
debounce_timestamp_ = time_now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool Read_Internal() {
|
||||
// Toggle the pin back to INPUT and take a reading.
|
||||
pinMode(pin_, INPUT);
|
||||
bool on = (digitalRead(pin_) == HIGH);
|
||||
// Switch the pin back to output so that we can enable the
|
||||
// pulldown resister.
|
||||
pinMode(pin_, OUTPUT);
|
||||
digitalWrite(pin_, LOW);
|
||||
return on;
|
||||
}
|
||||
|
||||
|
||||
int pin_;
|
||||
bool on_;
|
||||
uint32_t debounce_timestamp_;
|
||||
bool changed_;
|
||||
};
|
||||
|
||||
// This is the new type that is built into the midi shield.
|
||||
class MidiShieldButton {
|
||||
public:
|
||||
MidiShieldButton(int pin) : pin_(pin) {
|
||||
pinMode(pin_, INPUT_PULLUP);
|
||||
delay(1);
|
||||
}
|
||||
|
||||
bool Read() {
|
||||
// Toggle the pin back to INPUT and take a reading.
|
||||
int val = digitalRead(pin_) == LOW;
|
||||
|
||||
return val;
|
||||
}
|
||||
private:
|
||||
int pin_;
|
||||
};
|
||||
|
||||
class Potentiometer {
|
||||
public:
|
||||
Potentiometer(int sensor_pin) : sensor_pin_(sensor_pin) {}
|
||||
float Read() {
|
||||
float avg = 0.0;
|
||||
// Filter by reading the value multiple times and taking
|
||||
// the average.
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
avg += analogRead(sensor_pin_);
|
||||
}
|
||||
avg = avg / 8.0f;
|
||||
return avg;
|
||||
}
|
||||
private:
|
||||
int sensor_pin_;
|
||||
};
|
||||
|
||||
typedef MidiShieldButton DigitalButton;
|
||||
|
||||
|
||||
class CountingButton {
|
||||
public:
|
||||
explicit CountingButton(int but_pin) : button_(but_pin), curr_val_(0), mButton("Counting UIButton") {
|
||||
debounce_timestamp_ = millis();
|
||||
on_ = Read();
|
||||
}
|
||||
|
||||
void Update(uint32_t time_now) {
|
||||
bool clicked = mButton.clicked();
|
||||
bool val = Read() || mButton.clicked();
|
||||
bool changed = val != on_;
|
||||
|
||||
if (clicked) {
|
||||
++curr_val_;
|
||||
debounce_timestamp_ = time_now;
|
||||
return;
|
||||
}
|
||||
|
||||
if (changed) { // Has the toggle switch changed?
|
||||
on_ = val; // Set the new toggle switch value.
|
||||
// Protect against debouncing artifacts by putting a 200ms delay from the
|
||||
// last time the value changed.
|
||||
if ((time_now - debounce_timestamp_) > 16) {
|
||||
if (on_) {
|
||||
++curr_val_; // ... and increment the value.
|
||||
}
|
||||
debounce_timestamp_ = time_now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int curr_val() const { return curr_val_; }
|
||||
|
||||
private:
|
||||
bool Read() {
|
||||
return button_.Read();
|
||||
}
|
||||
|
||||
DigitalButton button_;
|
||||
bool on_;
|
||||
int curr_val_;
|
||||
unsigned long debounce_timestamp_;
|
||||
UIButton mButton;
|
||||
};
|
||||
|
||||
class ColorSelector {
|
||||
public:
|
||||
ColorSelector(int sensor_pin) : but_(sensor_pin) {}
|
||||
|
||||
void Update() {
|
||||
but_.Update(millis());
|
||||
}
|
||||
|
||||
int curr_val() const { return but_.curr_val() % 7; }
|
||||
private:
|
||||
CountingButton but_;
|
||||
};
|
||||
@@ -0,0 +1,232 @@
|
||||
/*!
|
||||
* @file midi_Defs.h
|
||||
* Project Arduino MIDI Library
|
||||
* @brief MIDI Library for the Arduino - Definitions
|
||||
* @author Francois Best, lathoub
|
||||
* @date 24/02/11
|
||||
* @license MIT - Copyright (c) 2015 Francois Best
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "midi_Namespace.h"
|
||||
|
||||
#if ARDUINO
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <inttypes.h>
|
||||
typedef uint8_t byte;
|
||||
#endif
|
||||
|
||||
BEGIN_MIDI_NAMESPACE
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define MIDI_CHANNEL_OMNI 0
|
||||
#define MIDI_CHANNEL_OFF 17 // and over
|
||||
|
||||
#define MIDI_PITCHBEND_MIN -8192
|
||||
#define MIDI_PITCHBEND_MAX 8191
|
||||
|
||||
/*! Receiving Active Sensing
|
||||
*/
|
||||
static const uint16_t ActiveSensingTimeout = 300;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Type definitions
|
||||
|
||||
typedef byte StatusByte;
|
||||
typedef byte DataByte;
|
||||
typedef byte Channel;
|
||||
typedef byte FilterMode;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Errors
|
||||
static const uint8_t ErrorParse = 0;
|
||||
static const uint8_t ErrorActiveSensingTimeout = 1;
|
||||
static const uint8_t WarningSplitSysEx = 2;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Aliasing
|
||||
|
||||
using ErrorCallback = void (*)(int8_t);
|
||||
using NoteOffCallback = void (*)(Channel channel, byte note, byte velocity);
|
||||
using NoteOnCallback = void (*)(Channel channel, byte note, byte velocity);
|
||||
using AfterTouchPolyCallback = void (*)(Channel channel, byte note, byte velocity);
|
||||
using ControlChangeCallback = void (*)(Channel channel, byte, byte);
|
||||
using ProgramChangeCallback = void (*)(Channel channel, byte);
|
||||
using AfterTouchChannelCallback = void (*)(Channel channel, byte);
|
||||
using PitchBendCallback = void (*)(Channel channel, int);
|
||||
using SystemExclusiveCallback = void (*)(byte * array, unsigned size);
|
||||
using TimeCodeQuarterFrameCallback = void (*)(byte data);
|
||||
using SongPositionCallback = void (*)(unsigned beats);
|
||||
using SongSelectCallback = void (*)(byte songnumber);
|
||||
using TuneRequestCallback = void (*)(void);
|
||||
using ClockCallback = void (*)(void);
|
||||
using StartCallback = void (*)(void);
|
||||
using TickCallback = void (*)(void);
|
||||
using ContinueCallback = void (*)(void);
|
||||
using StopCallback = void (*)(void);
|
||||
using ActiveSensingCallback = void (*)(void);
|
||||
using SystemResetCallback = void (*)(void);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/*! Enumeration of MIDI types */
|
||||
enum MidiType: uint8_t
|
||||
{
|
||||
InvalidType = 0x00, ///< For notifying errors
|
||||
NoteOff = 0x80, ///< Channel Message - Note Off
|
||||
NoteOn = 0x90, ///< Channel Message - Note On
|
||||
AfterTouchPoly = 0xA0, ///< Channel Message - Polyphonic AfterTouch
|
||||
ControlChange = 0xB0, ///< Channel Message - Control Change / Channel Mode
|
||||
ProgramChange = 0xC0, ///< Channel Message - Program Change
|
||||
AfterTouchChannel = 0xD0, ///< Channel Message - Channel (monophonic) AfterTouch
|
||||
PitchBend = 0xE0, ///< Channel Message - Pitch Bend
|
||||
SystemExclusive = 0xF0, ///< System Exclusive
|
||||
SystemExclusiveStart = SystemExclusive, ///< System Exclusive Start
|
||||
TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame
|
||||
SongPosition = 0xF2, ///< System Common - Song Position Pointer
|
||||
SongSelect = 0xF3, ///< System Common - Song Select
|
||||
Undefined_F4 = 0xF4,
|
||||
Undefined_F5 = 0xF5,
|
||||
TuneRequest = 0xF6, ///< System Common - Tune Request
|
||||
SystemExclusiveEnd = 0xF7, ///< System Exclusive End
|
||||
Clock = 0xF8, ///< System Real Time - Timing Clock
|
||||
Undefined_F9 = 0xF9,
|
||||
Tick = Undefined_F9, ///< System Real Time - Timing Tick (1 tick = 10 milliseconds)
|
||||
Start = 0xFA, ///< System Real Time - Start
|
||||
Continue = 0xFB, ///< System Real Time - Continue
|
||||
Stop = 0xFC, ///< System Real Time - Stop
|
||||
Undefined_FD = 0xFD,
|
||||
ActiveSensing = 0xFE, ///< System Real Time - Active Sensing
|
||||
SystemReset = 0xFF, ///< System Real Time - System Reset
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/*! Enumeration of Thru filter modes */
|
||||
struct Thru
|
||||
{
|
||||
enum Mode
|
||||
{
|
||||
Off = 0, ///< Thru disabled (nothing passes through).
|
||||
Full = 1, ///< Fully enabled Thru (every incoming message is sent back).
|
||||
SameChannel = 2, ///< Only the messages on the Input Channel will be sent back.
|
||||
DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back.
|
||||
};
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/*! \brief Enumeration of Control Change command numbers.
|
||||
See the detailed controllers numbers & description here:
|
||||
http://www.somascape.org/midi/tech/spec.html#ctrlnums
|
||||
*/
|
||||
enum MidiControlChangeNumber: uint8_t
|
||||
{
|
||||
// High resolution Continuous Controllers MSB (+32 for LSB) ----------------
|
||||
BankSelect = 0,
|
||||
ModulationWheel = 1,
|
||||
BreathController = 2,
|
||||
// CC3 undefined
|
||||
FootController = 4,
|
||||
PortamentoTime = 5,
|
||||
DataEntryMSB = 6,
|
||||
ChannelVolume = 7,
|
||||
Balance = 8,
|
||||
// CC9 undefined
|
||||
Pan = 10,
|
||||
ExpressionController = 11,
|
||||
EffectControl1 = 12,
|
||||
EffectControl2 = 13,
|
||||
// CC14 undefined
|
||||
// CC15 undefined
|
||||
GeneralPurposeController1 = 16,
|
||||
GeneralPurposeController2 = 17,
|
||||
GeneralPurposeController3 = 18,
|
||||
GeneralPurposeController4 = 19,
|
||||
|
||||
DataEntryLSB = 38,
|
||||
|
||||
// Switches ----------------------------------------------------------------
|
||||
Sustain = 64,
|
||||
Portamento = 65,
|
||||
Sostenuto = 66,
|
||||
SoftPedal = 67,
|
||||
Legato = 68,
|
||||
Hold = 69,
|
||||
|
||||
// Low resolution continuous controllers -----------------------------------
|
||||
SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off
|
||||
SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off
|
||||
SoundController3 = 72, ///< Synth: Release Time FX: Distortion On/Off
|
||||
SoundController4 = 73, ///< Synth: Attack Time FX: EQ On/Off
|
||||
SoundController5 = 74, ///< Synth: Brightness FX: Expander On/Off
|
||||
SoundController6 = 75, ///< Synth: Decay Time FX: Reverb On/Off
|
||||
SoundController7 = 76, ///< Synth: Vibrato Rate FX: Delay On/Off
|
||||
SoundController8 = 77, ///< Synth: Vibrato Depth FX: Pitch Transpose On/Off
|
||||
SoundController9 = 78, ///< Synth: Vibrato Delay FX: Flange/Chorus On/Off
|
||||
SoundController10 = 79, ///< Synth: Undefined FX: Special Effects On/Off
|
||||
GeneralPurposeController5 = 80,
|
||||
GeneralPurposeController6 = 81,
|
||||
GeneralPurposeController7 = 82,
|
||||
GeneralPurposeController8 = 83,
|
||||
PortamentoControl = 84,
|
||||
// CC85 to CC90 undefined
|
||||
Effects1 = 91, ///< Reverb send level
|
||||
Effects2 = 92, ///< Tremolo depth
|
||||
Effects3 = 93, ///< Chorus send level
|
||||
Effects4 = 94, ///< Celeste depth
|
||||
Effects5 = 95, ///< Phaser depth
|
||||
DataIncrement = 96,
|
||||
DataDecrement = 97,
|
||||
NRPNLSB = 98, ///< Non-Registered Parameter Number (LSB)
|
||||
NRPNMSB = 99, ///< Non-Registered Parameter Number (MSB)
|
||||
RPNLSB = 100, ///< Registered Parameter Number (LSB)
|
||||
RPNMSB = 101, ///< Registered Parameter Number (MSB)
|
||||
|
||||
// Channel Mode messages ---------------------------------------------------
|
||||
AllSoundOff = 120,
|
||||
ResetAllControllers = 121,
|
||||
LocalControl = 122,
|
||||
AllNotesOff = 123,
|
||||
OmniModeOff = 124,
|
||||
OmniModeOn = 125,
|
||||
MonoModeOn = 126,
|
||||
PolyModeOn = 127
|
||||
};
|
||||
|
||||
struct RPN
|
||||
{
|
||||
enum RegisteredParameterNumbers: uint16_t
|
||||
{
|
||||
PitchBendSensitivity = 0x0000,
|
||||
ChannelFineTuning = 0x0001,
|
||||
ChannelCoarseTuning = 0x0002,
|
||||
SelectTuningProgram = 0x0003,
|
||||
SelectTuningBank = 0x0004,
|
||||
ModulationDepthRange = 0x0005,
|
||||
NullFunction = (0x7f << 7) + 0x7f,
|
||||
};
|
||||
};
|
||||
|
||||
END_MIDI_NAMESPACE
|
||||
@@ -0,0 +1,103 @@
|
||||
/*!
|
||||
* @file midi_Message.h
|
||||
* Project Arduino MIDI Library
|
||||
* @brief MIDI Library for the Arduino - Message struct definition
|
||||
* @author Francois Best
|
||||
* @date 11/06/14
|
||||
* @license MIT - Copyright (c) 2015 Francois Best
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "midi_Namespace.h"
|
||||
#include "midi_Defs.h"
|
||||
#include "fl/memfill.h"
|
||||
|
||||
BEGIN_MIDI_NAMESPACE
|
||||
|
||||
/*! The Message structure contains decoded data of a MIDI message
|
||||
read from the serial port with read()
|
||||
*/
|
||||
template<unsigned SysExMaxSize>
|
||||
struct Message
|
||||
{
|
||||
/*! Default constructor
|
||||
\n Initializes the attributes with their default values.
|
||||
*/
|
||||
inline Message()
|
||||
: channel(0)
|
||||
, type(MIDI_NAMESPACE::InvalidType)
|
||||
, data1(0)
|
||||
, data2(0)
|
||||
, valid(false)
|
||||
{
|
||||
fl::memfill(sysexArray, 0, sSysExMaxSize * sizeof(DataByte));
|
||||
}
|
||||
|
||||
/*! The maximum size for the System Exclusive array.
|
||||
*/
|
||||
static const unsigned sSysExMaxSize = SysExMaxSize;
|
||||
|
||||
/*! The MIDI channel on which the message was recieved.
|
||||
\n Value goes from 1 to 16.
|
||||
*/
|
||||
Channel channel;
|
||||
|
||||
/*! The type of the message
|
||||
(see the MidiType enum for types reference)
|
||||
*/
|
||||
MidiType type;
|
||||
|
||||
/*! The first data byte.
|
||||
\n Value goes from 0 to 127.
|
||||
*/
|
||||
DataByte data1;
|
||||
|
||||
/*! The second data byte.
|
||||
If the message is only 2 bytes long, this one is null.
|
||||
\n Value goes from 0 to 127.
|
||||
*/
|
||||
DataByte data2;
|
||||
|
||||
/*! System Exclusive dedicated byte array.
|
||||
\n Array length is stocked on 16 bits,
|
||||
in data1 (LSB) and data2 (MSB)
|
||||
*/
|
||||
DataByte sysexArray[sSysExMaxSize];
|
||||
|
||||
/*! This boolean indicates if the message is valid or not.
|
||||
There is no channel consideration here,
|
||||
validity means the message respects the MIDI norm.
|
||||
*/
|
||||
bool valid;
|
||||
|
||||
/*! Total Length of the message.
|
||||
*/
|
||||
unsigned length;
|
||||
|
||||
inline unsigned getSysExSize() const
|
||||
{
|
||||
const unsigned size = unsigned(data2) << 8 | data1;
|
||||
return size > sSysExMaxSize ? sSysExMaxSize : size;
|
||||
}
|
||||
};
|
||||
|
||||
END_MIDI_NAMESPACE
|
||||
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* @file midi_Namespace.h
|
||||
* Project Arduino MIDI Library
|
||||
* @brief MIDI Library for the Arduino - Namespace declaration
|
||||
* @author Francois Best
|
||||
* @date 24/02/11
|
||||
* @license MIT - Copyright (c) 2015 Francois Best
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MIDI_NAMESPACE midi
|
||||
#define BEGIN_MIDI_NAMESPACE namespace MIDI_NAMESPACE {
|
||||
#define END_MIDI_NAMESPACE }
|
||||
|
||||
#define USING_NAMESPACE_MIDI using namespace MIDI_NAMESPACE;
|
||||
|
||||
BEGIN_MIDI_NAMESPACE
|
||||
|
||||
END_MIDI_NAMESPACE
|
||||
@@ -0,0 +1,51 @@
|
||||
/*!
|
||||
* @file midi_Platform.h
|
||||
* Project Arduino MIDI Library
|
||||
* @brief MIDI Library for the Arduino - Platform
|
||||
* @license MIT - Copyright (c) 2015 Francois Best
|
||||
* @author lathoub
|
||||
* @date 22/03/20
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "midi_Defs.h"
|
||||
|
||||
BEGIN_MIDI_NAMESPACE
|
||||
|
||||
#if ARDUINO
|
||||
|
||||
// DefaultPlatform is the Arduino Platform
|
||||
struct DefaultPlatform
|
||||
{
|
||||
static unsigned long now() { return ::millis(); };
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
struct DefaultPlatform
|
||||
{
|
||||
static unsigned long now() { return 0; };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
END_MIDI_NAMESPACE
|
||||
@@ -0,0 +1,104 @@
|
||||
/*!
|
||||
* @file midi_Settings.h
|
||||
* Project Arduino MIDI Library
|
||||
* @brief MIDI Library for the Arduino - Settings
|
||||
* @author Francois Best
|
||||
* @date 24/02/11
|
||||
* @license MIT - Copyright (c) 2015 Francois Best
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "midi_Defs.h"
|
||||
|
||||
BEGIN_MIDI_NAMESPACE
|
||||
|
||||
/*! \brief Default Settings for the MIDI Library.
|
||||
|
||||
To change the default settings, don't edit them there, create a subclass and
|
||||
override the values in that subclass, then use the MIDI_CREATE_CUSTOM_INSTANCE
|
||||
macro to create your instance. The settings you don't override will keep their
|
||||
default value. Eg:
|
||||
\code{.cpp}
|
||||
struct MySettings : public midi::DefaultSettings
|
||||
{
|
||||
static const unsigned SysExMaxSize = 1024; // Accept SysEx messages up to 1024 bytes long.
|
||||
};
|
||||
|
||||
MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial2, midi, MySettings);
|
||||
\endcode
|
||||
*/
|
||||
struct DefaultSettings
|
||||
{
|
||||
/*! Running status enables short messages when sending multiple values
|
||||
of the same type and channel.\n
|
||||
Must be disabled to send USB MIDI messages to a computer
|
||||
Warning: does not work with some hardware, enable with caution.
|
||||
*/
|
||||
static const bool UseRunningStatus = false;
|
||||
|
||||
/*! NoteOn with 0 velocity should be handled as NoteOf.\n
|
||||
Set to true to get NoteOff events when receiving null-velocity NoteOn messages.\n
|
||||
Set to false to get NoteOn events when receiving null-velocity NoteOn messages.
|
||||
*/
|
||||
static const bool HandleNullVelocityNoteOnAsNoteOff = true;
|
||||
|
||||
/*! Setting this to true will make MIDI.read parse only one byte of data for each
|
||||
call when data is available. This can speed up your application if receiving
|
||||
a lot of traffic, but might induce MIDI Thru and treatment latency.
|
||||
*/
|
||||
static const bool Use1ByteParsing = true;
|
||||
|
||||
/*! Maximum size of SysEx receivable. Decrease to save RAM if you don't expect
|
||||
to receive SysEx, or adjust accordingly.
|
||||
*/
|
||||
static const unsigned SysExMaxSize = 128;
|
||||
|
||||
/*! Global switch to turn on/off sender ActiveSensing
|
||||
Set to true to send ActiveSensing
|
||||
Set to false will not send ActiveSensing message (will also save memory)
|
||||
*/
|
||||
static const bool UseSenderActiveSensing = false;
|
||||
|
||||
/*! Global switch to turn on/off receiver ActiveSensing
|
||||
Set to true to check for message timeouts (via ErrorCallback)
|
||||
Set to false will not check if chained device are still alive (if they use ActiveSensing) (will also save memory)
|
||||
*/
|
||||
static const bool UseReceiverActiveSensing = false;
|
||||
|
||||
/*! Active Sensing is intended to be sent
|
||||
repeatedly by the sender to tell the receiver that a connection is alive. Use
|
||||
of this message is optional. When initially received, the
|
||||
receiver will expect to receive another Active Sensing
|
||||
message each 300ms (max), and if it does not then it will
|
||||
assume that the connection has been terminated. At
|
||||
termination, the receiver will turn off all voices and return to
|
||||
normal (non- active sensing) operation.
|
||||
|
||||
Typical value is 250 (ms) - an Active Sensing command is send every 250ms.
|
||||
(All Roland devices send Active Sensing every 250ms)
|
||||
|
||||
Setting this field to 0 will disable sending MIDI active sensing.
|
||||
*/
|
||||
static const uint16_t SenderActiveSensingPeriodicity = 0;
|
||||
};
|
||||
|
||||
END_MIDI_NAMESPACE
|
||||
@@ -0,0 +1,125 @@
|
||||
/*!
|
||||
* @file serialMIDI.h
|
||||
* Project Arduino MIDI Library
|
||||
* @brief MIDI Library for the Arduino - Platform
|
||||
* @license MIT - Copyright (c) 2015 Francois Best
|
||||
* @author lathoub, Francois Best
|
||||
* @date 22/03/20
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "midi_Namespace.h"
|
||||
|
||||
BEGIN_MIDI_NAMESPACE
|
||||
|
||||
struct DefaultSerialSettings
|
||||
{
|
||||
/*! Override the default MIDI baudrate to transmit over USB serial, to
|
||||
a decoding program such as Hairless MIDI (set baudrate to 115200)\n
|
||||
http://projectgus.github.io/hairless-midiserial/
|
||||
*/
|
||||
static const long BaudRate = 31250;
|
||||
};
|
||||
|
||||
template <class SerialPort, class _Settings = DefaultSerialSettings>
|
||||
class SerialMIDI
|
||||
{
|
||||
typedef _Settings Settings;
|
||||
|
||||
public:
|
||||
SerialMIDI(SerialPort& inSerial)
|
||||
: mSerial(inSerial)
|
||||
{
|
||||
};
|
||||
|
||||
public:
|
||||
static const bool thruActivated = true;
|
||||
|
||||
void begin()
|
||||
{
|
||||
// Initialise the Serial port
|
||||
#if defined(AVR_CAKE)
|
||||
mSerial. template open<Settings::BaudRate>();
|
||||
#else
|
||||
mSerial.begin(Settings::BaudRate);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool beginTransmission(MidiType)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
void write(byte value)
|
||||
{
|
||||
mSerial.write(value);
|
||||
};
|
||||
|
||||
void endTransmission()
|
||||
{
|
||||
};
|
||||
|
||||
byte read()
|
||||
{
|
||||
return mSerial.read();
|
||||
};
|
||||
|
||||
unsigned available()
|
||||
{
|
||||
return mSerial.available();
|
||||
};
|
||||
|
||||
private:
|
||||
SerialPort& mSerial;
|
||||
};
|
||||
|
||||
/*! \brief Create an instance of the library attached to a serial port.
|
||||
You can use HardwareSerial or SoftwareSerial for the serial port.
|
||||
Example: MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, midi2);
|
||||
Then call midi2.begin(), midi2.read() etc..
|
||||
*/
|
||||
#define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \
|
||||
MIDI_NAMESPACE::SerialMIDI<Type> serial##Name(SerialPort);\
|
||||
MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<Type>> Name((MIDI_NAMESPACE::SerialMIDI<Type>&)serial##Name);
|
||||
|
||||
#if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__)
|
||||
// Leonardo, Due and other USB boards use Serial1 by default.
|
||||
#define MIDI_CREATE_DEFAULT_INSTANCE() \
|
||||
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
|
||||
#else
|
||||
/*! \brief Create an instance of the library with default name, serial port
|
||||
and settings, for compatibility with sketches written with pre-v4.2 MIDI Lib,
|
||||
or if you don't bother using custom names, serial port or settings.
|
||||
*/
|
||||
#define MIDI_CREATE_DEFAULT_INSTANCE() \
|
||||
MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI);
|
||||
#endif
|
||||
|
||||
/*! \brief Create an instance of the library attached to a serial port with
|
||||
custom settings.
|
||||
@see DefaultSettings
|
||||
@see MIDI_CREATE_INSTANCE
|
||||
*/
|
||||
#define MIDI_CREATE_CUSTOM_INSTANCE(Type, SerialPort, Name, Settings) \
|
||||
MIDI_NAMESPACE::SerialMIDI<Type, Settings> serial##Name(SerialPort);\
|
||||
MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<Type, Settings>> Name((MIDI_NAMESPACE::SerialMIDI<Type, Settings>&)serial##Name);
|
||||
|
||||
END_MIDI_NAMESPACE
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "../shared/defs.h"
|
||||
|
||||
|
||||
#if ENABLE_SKETCH
|
||||
|
||||
|
||||
#include "./ui_state.h"
|
||||
#include "shared/Painter.h"
|
||||
#include "fl/dbg.h"
|
||||
#include "fl/unused.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
//#define UI_V1 // Based off the old midi shield with hand assmebled buttons.
|
||||
#define UI_V2 // Based on a new midi shield with buttons. https://learn.sparkfun.com/tutorials/midi-shield-hookup-guide
|
||||
#define UI_DBG
|
||||
|
||||
|
||||
#ifndef A3
|
||||
#define A3 3
|
||||
#warning "A3 is not defined, using 3"
|
||||
#endif
|
||||
#ifndef A4
|
||||
#define A4 4
|
||||
#warning "A4 is not defined, using 4"
|
||||
#endif
|
||||
|
||||
#ifdef __STM32F1__
|
||||
// Missing A-type pins, just use digital pins mapped to analog.
|
||||
#define PIN_POT_COLOR_SENSOR D3
|
||||
#define PIN_POT_VEL_SENSOR D4
|
||||
#else
|
||||
#define PIN_POT_COLOR_SENSOR A3
|
||||
#define PIN_POT_VEL_SENSOR A4
|
||||
#endif
|
||||
|
||||
#define PIN_VIS_SELECT 2
|
||||
#define PIN_COLOR_SELECT 4
|
||||
|
||||
Potentiometer velocity_pot(PIN_POT_VEL_SENSOR);
|
||||
Potentiometer color_pot(PIN_POT_COLOR_SENSOR);
|
||||
|
||||
float read_color_selector() {
|
||||
return color_pot.Read();
|
||||
}
|
||||
|
||||
float read_velocity_bias() {
|
||||
return velocity_pot.Read();
|
||||
}
|
||||
|
||||
|
||||
//DigitalButton custom_notecolor_select(4);
|
||||
ColorSelector color_selector(PIN_COLOR_SELECT);
|
||||
CountingButton vis_selector(PIN_VIS_SELECT);
|
||||
|
||||
void ui_init() {
|
||||
}
|
||||
|
||||
|
||||
ui_state ui_update(uint32_t now_ms, uint32_t delta_ms) {
|
||||
FL_UNUSED(delta_ms);
|
||||
|
||||
ui_state out;
|
||||
vis_selector.Update(now_ms);
|
||||
color_selector.Update();
|
||||
int32_t curr_val = vis_selector.curr_val();
|
||||
FASTLED_DBG("curr_val: " << curr_val);
|
||||
|
||||
out.color_scheme = color_selector.curr_val();
|
||||
|
||||
//bool notecolor_on = custom_notecolor_select.Read();
|
||||
|
||||
//Serial.print("color selector: "); Serial.println(out.color_scheme);
|
||||
|
||||
//float velocity = read_velocity_bias();
|
||||
//float color_selector = read_color_selector();
|
||||
|
||||
//Serial.print("velocity: "); Serial.print(velocity); Serial.print(", color_selector: "); Serial.print(color_selector);
|
||||
|
||||
//if (notecolor_on) {
|
||||
// Serial.print(", notecolor_on: "); Serial.print(notecolor_on);
|
||||
//}
|
||||
|
||||
//Serial.print("color_scheme: "); Serial.print(out.color_scheme); Serial.print(", vis selector: "); Serial.print(curr_val);
|
||||
|
||||
//Serial.println("");
|
||||
|
||||
//Serial.print("curr_val: "); Serial.print(curr_val);// Serial.print(", button state: "); Serial.println(vis_selector.button_.curr_val());;
|
||||
|
||||
out.which_visualizer = static_cast<Painter::VisState>(curr_val % Painter::kNumVisStates);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
#endif // ENABLE_SKETCH
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "buttons.h"
|
||||
|
||||
|
||||
extern ColorSelector color_selector;
|
||||
extern CountingButton vis_selector;
|
||||
|
||||
struct ui_state {
|
||||
ui_state() { memset(this, 0, sizeof(*this)); }
|
||||
int which_visualizer;
|
||||
int color_scheme;
|
||||
//float color_wheel_pos;
|
||||
//float velocity_bias_pos; // default to 1.0f for buttons missing velocity.
|
||||
};
|
||||
|
||||
void ui_init();
|
||||
ui_state ui_update(uint32_t now_ms, uint32_t delta_ms);
|
||||
|
||||
@@ -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_
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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_
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
#include "dprint.h"
|
||||
|
||||
bool is_debugging = false;
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user