Compare commits

2 Commits

Author SHA1 Message Date
Hykilpikonna af86289acd [+] S3 test 2023-04-11 14:16:02 -04:00
Hykilpikonna 103cd75096 [+] Test 2023-03-25 19:36:39 -04:00
21 changed files with 148 additions and 610 deletions
+3 -45
View File
@@ -20,14 +20,14 @@
Open-KeyPrec is an open-sourced, pressure-sensitive electric keyboard percussion (mallet) MIDI instrument. It has 61 keys, spanning 5 octaves of playing range from C2 to C7 like a standard Marimba. It also has a MIDI control panel with 16 buttons, 12 knobs (8 × 280° potentiometers and 4 × 360° rotary encoders).
The sensors are controlled by a development board, and the output is converted into a standard MIDI device. You can use audio workstation softwares like GarageBand or Reaper (or open-source alternatives like LMMS, Zrythm, or Ardour) to simulate the sounds of all kinds of different instruments.
The sensors are controlled by an ESP32 development board, and the output is converted into a standard MIDI device. You can use audio workstation softwares like GarageBand or Reaper (or open-source alternatives like LMMS, Zrythm, or Ardour) to simulate the sounds of all kinds of different instruments.
## Code
**Source code structure**
* `/src` - PC driver written in Rust
* `/firmware` - Development board firmware written in C++
* `/firmware` - ESP32 module firmware written in C++
* `/circuits` - Printed circuit boards
* `/models` - 3D-printable models
@@ -38,22 +38,6 @@ The sensors are controlled by a development board, and the output is converted i
* CLion: IDE for Rust & C++
* PlatformIO: Firmware development toolkit
**Pin Configuration**
| Pins | I/O | Description |
|------|-----|-------------------------------------------|
| 4 | O | Mux Select for C2-B6 |
| 5 | IA | Mux Input for C2-B6 |
| 1 | IA | Input for C7 |
| 8 | I | (Panel) Input for 4 rotary encoders |
| 3 | O | Mux Select for buttons and potentiometers |
| 2 | I | Mux Input for buttons |
| 1 | IA | Mux Input for potentiometers |
| 3 | O | RGB LED Control |
Total ADC channels required: 7
Total GPIO required: 23
## Build
### Materials Required
@@ -69,36 +53,10 @@ Total GPIO required: 23
### 1. Order PCBs
I ordered my PCBs from JLC, but any PCB service would work. They should accept the exported Gerber zip files in `circuits/Exports` folder.
I created two circuit boards for this keyboard, one is the multiplexer ([Mux v2.zip](circuits/Mux%20v2.zip)) which uses the CD74HC4067 1:16 multiplexer ic to convert the 61 separate analog inputs requirement into only 5 analog inputs and 4 digital switch outputs.
It also haso diodes and resistors for voltage protection. The other pcb ([OKP-Panel-SMT.zip](circuits/OKP-Panel-SMT.zip)) is for the MIDI control panel containing the buttons and knobs.
1. Go to jlcpcb.com (Or jlc.com if you read Chinese)
2. Upload the Gerber file
3. Set parameters: FR-4, 2 Layers, Color, leave default for other options
**PCB Assembly (SMT)**
If you're not comfortable soldering small SMD components, you can order PCB Assembly from JLC.
You can also select which parts you would like to SMT based on your soldering abilities.
For example, in the Mux pcb, I ordered SMT for only the 0603 Zener diodes and the CD74HC4067 ic, and hand-soldered everything else.
1. Order "PCB Assembly" (SMT) and click confirm
2. In the SMT menu, upload the BOM and CPL (PickAndPlace) files
3. After uploading, remove the parts you want to manually solder (they're pretty expensive, since someone in the factory still have to manually solder them for you)
4. Order manually-soldered parts somewhere else (e.g. szlcsc.com)
!!!!!!!!!! TODO !!!!!!!!!!
## Usage
### Flash Firmware
1. Install PlatformIO Core ([Guide](https://platformio.org/install/cli))
2. Open the `firmware` directory
3. In your preferred IDE, edit `src/config.h` with your pin configuration.
4. Edit `platformio.ini` to match your dev board.
5. Connect dev board, build and upload firmware using `platformio run --target upload`
### Setup MIDI Device
!!!!!!!!!! TODO !!!!!!!!!!
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -17
View File
@@ -13,16 +13,6 @@
;board = wemos_d1_uno32
;framework = arduino
[env:s3]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
lib_deps =
adafruit/Adafruit NeoPixel@^1.11.0
paulstoffregen/Encoder@^1.4.2
; ESP32-S2 and C3 both has a problem where the ADC jumps around a LOT
;[env:s2]
;platform = espressif32
;board = lolin_s2_mini
@@ -33,14 +23,11 @@ lib_deps =
;board = dfrobot_beetle_esp32c3
;framework = arduino
; YD STM32F411CEU6
;[env:stm32f411ce]
;platform = ststm32
;board = blackpill_f411ce
;framework = arduino
;upload_protocol = dfu
[env:s3]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
; Arduino nano is good but too few pins
;[env:n328p]
;platform = atmelavr
;board = nanoatmega328
-81
View File
@@ -1,81 +0,0 @@
#ifndef FIRMWARE_CONFIG_H
#define FIRMWARE_CONFIG_H
#include "utils.h"
// ========================================
// Main Keyboard Pin Configuration
// ========================================
// LED indicator for pulling update
const int LED_REFRESH = 45;
// 4 1:16 Multiplexers: GPIO pins for each analog multiplexer that handles 12 sensors of an octave
// Notes are connected in order: Mux #1 (0-11), Mux #2 (12-23), Mux #3 (24-35), Mux #4 (36-47), Mux #5 (48-59)
const int NUM_MUX = 5;
const int PINS_PER_MUX = 12;
const int MUX_IN[NUM_MUX] = {4, 5, 6, 7, 8};
// Select pins for every multiplexer, each multiplexer has 4 select pins, all sel0 are connected to 14, etc.
const int NUM_MUX_SEL = 4;
const int MUX_SEL_OUT[NUM_MUX_SEL] = {14, 13, 12, 11};
// LED Light strip
const int LK_PIN = 2;
const int LK_LIGHTS_PER_METER = 60;
constexpr float LK_NUM_METERS = 2;
const int LK_KEY_SPACING_MM = 7;
const int LK_KEY_LEN_MM = 30;
const int LK_OFFSET_MM = 25; // Length in mm from the start of the strip to the first key
const float LK_LIGHTS_PER_MM = LK_LIGHTS_PER_METER / 1000.0;
constexpr int LK_NUM_LIGHTS = (int) (LK_LIGHTS_PER_METER * LK_NUM_METERS + 0.5);
// ========================================
// MIDI Panel Pin Configuration
// ========================================
// 3 1:8 Multiplexers for the midi panel
const int P_PINS_PER_MUX = 8;
const int P_KEY_MUX_IN = 36;
const int P_BUTTON_MUX_IN = 35; // Digital signal inputs for button multiplexers
const int P_KNOB_MUX_IN = 10; // Analog signal inputs for potentiometer
// Select pins for every multiplexer, each multiplexer has 3 select pins
const int P_NUM_MUX_SEL = 3;
const int P_MUX_SEL_OUT[P_NUM_MUX_SEL] = {37, 38, 39};
// Rotary encoder pins
const int P_NUM_ROTARY = 4;
const int P_ROTARY_A[P_NUM_ROTARY] = {18, 17, 16, 15};
const int P_ROTARY_B[P_NUM_ROTARY] = {19, 20, 21, 47};
// LED light strips
const int P_LED_KEY = 42;
const int P_LED_KNOB = 41;
const int P_LED_ROTARY = 40;
// ========================================
// Constants
// ========================================
typedef struct note
{
char name[4];
u16 midi;
} Note;
// 61 Notes from C2 to C7
const int NUM_NOTES = 61;
const int NUM_SHARP_NOTES = 25;
const int NUM_REGULAR_NOTES = NUM_NOTES - NUM_SHARP_NOTES;
const Note notes[] = {
{"C2", 36}, {"C#2", 37}, {"D2", 38}, {"D#2", 39}, {"E2", 40}, {"F2", 41}, {"F#2", 42}, {"G2", 43}, {"G#2", 44}, {"A2", 45}, {"A#2", 46}, {"B2", 47},
{"C3", 48}, {"C#3", 49}, {"D3", 50}, {"D#3", 51}, {"E3", 52}, {"F3", 53}, {"F#3", 54}, {"G3", 55}, {"G#3", 56}, {"A3", 57}, {"A#3", 58}, {"B3", 59},
{"C4", 60}, {"C#4", 61}, {"D4", 62}, {"D#4", 63}, {"E4", 64}, {"F4", 65}, {"F#4", 66}, {"G4", 67}, {"G#4", 68}, {"A4", 69}, {"A#4", 70}, {"B4", 71},
{"C5", 72}, {"C#5", 73}, {"D5", 74}, {"D#5", 75}, {"E5", 76}, {"F5", 77}, {"F#5", 78}, {"G5", 79}, {"G#5", 80}, {"A5", 81}, {"A#5", 82}, {"B5", 83},
{"C6", 84}, {"C#6", 85}, {"D6", 86}, {"D#6", 87}, {"E6", 88}, {"F6", 89}, {"F#6", 90}, {"G6", 91}, {"G#6", 92}, {"A6", 93}, {"A#6", 94}, {"B6", 95},
{"C7", 96}
};
#endif //FIRMWARE_CONFIG_H
-127
View File
@@ -1,127 +0,0 @@
//
// Created by Hykilpikonna on 4/21/23.
//
#include <Arduino.h>
#include "config.h"
#include "utils.h"
#include "Adafruit_NeoPixel.h"
#include <unordered_map>
#include <queue>
const int ANIM_QUEUE_SIZE = 100;
const int FRAME_DELAY_MS = 20;
using namespace std;
class KeyboardLights
{
private:
Adafruit_NeoPixel led_key;
unordered_map<int, int> key_to_light;
TaskHandle_t thread{};
u32 animation[ANIM_QUEUE_SIZE][LK_NUM_LIGHTS]{};
u32 last_frame[LK_NUM_LIGHTS]{};
int anim_index = 0;
static int wrap(int i) { return (i + LK_NUM_LIGHTS) % LK_NUM_LIGHTS; }
void update()
{
// Render this frame
auto frame = animation[anim_index];
for (int i = 0; i < LK_NUM_LIGHTS; i++)
{
if (frame[i] == 0 && last_frame[i] == 0) continue;
last_frame[i] = frame[i];
led_key.setPixelColor(i, frame[i]);
}
led_key.show();
// Clear the current frame
for (int i = 0; i < LK_NUM_LIGHTS; i++) frame[i] = 0;
anim_index = (anim_index + 1) % ANIM_QUEUE_SIZE;
}
[[noreturn]] static void loop(void *pv)
{
auto *lights = (KeyboardLights *) pv;
while (true)
{
lights->update();
vTaskDelay(FRAME_DELAY_MS / portTICK_PERIOD_MS);
}
}
public:
KeyboardLights() : led_key(LK_NUM_LIGHTS, LK_PIN, NEO_GRB + NEO_KHZ800)
{
// Initialize key to light map
int regi = 0;
for (int i = NUM_NOTES - 1; i >= 0; i--)
{
auto note = notes[i];
auto ki = (float) regi;
if (note.name[1] == '#') ki -= 0.5;
// Calculate the length from the start of the keyboard to the key
ki *= LK_KEY_SPACING_MM + LK_KEY_LEN_MM;
ki += LK_KEY_LEN_MM / 2.0; // Center of the key
// Calculate the index of the light at the same length position
ki *= LK_LIGHTS_PER_MM;
// Convert key index to mm, then to light index
key_to_light[i] = (int) round(ki);
// Increment the regular note index if it's not a sharp note
if (note.name[1] != '#') regi++;
}
}
void begin()
{
pinModeSafe(LK_PIN, OUTPUT);
led_key.begin();
xTaskCreate(loop, "loopLights", 4096, this, 1, &thread);
}
void hit(int key)
{
// 1. Calculate the starting index
int start = key_to_light[key];
Serial.printf("Key %d -> Light %d\n", key, start);
// 2. Start animation
// Fade out for one light
// for (int i = 0; i < 50; i++)
// {
// animation[(anim_index + i) % ANIM_QUEUE_SIZE][start] = Adafruit_NeoPixel::ColorHSV(0, 255, (50 - i) * 2);
// }
// Fade out from the center to both sides
int hue = random(0, 65535);
for (int i = 0; i < 25; i++)
{
// Pick a random hue
Serial.printf("Hue: %d\n", hue);
int v = (25 - i) * 2;
int s = 255;
auto frame = animation[(anim_index + i) % ANIM_QUEUE_SIZE];
frame[start] = Adafruit_NeoPixel::ColorHSV(hue, s, v);
// Sides
for (int j = 1; j <= min(i, 10); j++)
{
frame[(start + j) % LK_NUM_LIGHTS] = Adafruit_NeoPixel::ColorHSV(hue, s, MAX(v - j * 2, 0));
frame[(start - j + LK_NUM_LIGHTS) % LK_NUM_LIGHTS] = Adafruit_NeoPixel::ColorHSV(hue, s, MAX(v - j * 2, 0));
}
}
}
};
+140 -100
View File
@@ -1,89 +1,79 @@
#include <Arduino.h>
#include "config.h"
#include "Adafruit_NeoPixel.h"
#include "main.h"
#include "utils.h"
#include "panel.cpp"
#include "keyboard_lights.cpp"
#include <chrono>
#include <cinttypes>
#include <driver/adc.h>
u32 lasts[NUM_NOTES]; // variable to store the value coming from the sensor
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define u64 uint64_t
#define timeMillis() std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count()
#define min(a, b) ((a) < (b) ? (a) : (b))
#define let auto
#define val const auto
u64 start_time = 0;
typedef struct note {
char name[4];
u16 midi;
} Note;
// 61 Notes from C2 to C7
val NUM_NOTES = 61;
const Note notes[] = {
{"C2", 36}, {"C#2", 37}, {"D2", 38}, {"D#2", 39}, {"E2", 40}, {"F2", 41}, {"F#2", 42}, {"G2", 43}, {"G#2", 44}, {"A2", 45}, {"A#2", 46}, {"B2", 47},
{"C3", 48}, {"C#3", 49}, {"D3", 50}, {"D#3", 51}, {"E3", 52}, {"F3", 53}, {"F#3", 54}, {"G3", 55}, {"G#3", 56}, {"A3", 57}, {"A#3", 58}, {"B3", 59},
{"C4", 60}, {"C#4", 61}, {"D4", 62}, {"D#4", 63}, {"E4", 64}, {"F4", 65}, {"F#4", 66}, {"G4", 67}, {"G#4", 68}, {"A4", 69}, {"A#4", 70}, {"B4", 71},
{"C5", 72}, {"C#5", 73}, {"D5", 74}, {"D#5", 75}, {"E5", 76}, {"F5", 77}, {"F#5", 78}, {"G5", 79}, {"G#5", 80}, {"A5", 81}, {"A#5", 82}, {"B5", 83},
{"C6", 84}, {"C#6", 85}, {"D6", 86}, {"D#6", 87}, {"E6", 88}, {"F6", 89}, {"F#6", 90}, {"G6", 91}, {"G#6", 92}, {"A6", 93}, {"A#6", 94}, {"B6", 95},
{"C7", 96}
};
// LED pins
val PIN_LED = 37;
let led_state = true;
// 5 Multiplexers: GPIO pins for each analog multiplexer that handles 12 sensors (1 octave)
// Notes are connected in order: Mux #1 (0-11), Mux #2 (12-23), Mux #3 (24-35), Mux #4 (36-47), Mux #5 (48-59)
val NUM_MUX = 5;
val PINS_PER_MUX = 12;
const adc1_channel_t mux_in[] = {ADC1_CHANNEL_1, ADC1_CHANNEL_2, ADC1_CHANNEL_3, ADC1_CHANNEL_4, ADC1_CHANNEL_5};
//const int mux_in[] = {A0, A1, A2, A3, A4};
// Select pins for every multiplexer, each multiplexer has 4 select pins, all sel0 are connected to 14, etc.
val NUM_MUX_SEL = 4;
const int mux_sel[] = {40, 38, 36, 34};
// Multisampling: Take multiple samples for each sensor and average them
val MULTISAMPLING = 1;
int lasts[NUM_NOTES]; // variable to store the value coming from the sensor
u64 last_hit_times[NUM_NOTES];
u32 bounce_delay = 50; // Minimum time between two hits
let max_sensor = 4096;
let max_threshold = 3000;
let active_threshold = 400; // Minimum value to be considered as a hit
let led_refresh_on = false;
Panel panel;
KeyboardLights keyboardLights;
val max_sensor = 4096;
val max_threshold = 2000;
val active_threshold = 100; // Minimum value to be considered as a hit
void setup()
{
// Initialize pins
pinModeSafe(LED_REFRESH, OUTPUT);
for (int pin: MUX_IN) pinModeSafe(pin, INPUT);
for (int pin: MUX_SEL_OUT) pinModeSafe(pin, OUTPUT);
// Turn on LED
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, led_state);
// Initialize serial
Serial.begin(115200);
Serial.printf("Initialized\r\n");
panel.begin();
keyboardLights.begin();
}
u64 fps_last_update = 0;
u32 fps_updates = 0;
const u32 fps_interval_ms = 1000;
void countFps(u64 time)
{
// Report FPS every second
fps_updates++;
if (time - fps_last_update >= fps_interval_ms)
{
fps_last_update = time;
double fps = 1.0 * fps_updates / fps_interval_ms * 1000;
Serial.printf("FPS: %.2f\r\n", fps);
fps_updates = 0;
// Initialize pin and serial
for (int pin: mux_in) pinMode(pin, INPUT);
adc1_config_width(ADC_WIDTH_MAX);
for (val pin : mux_in) {
adcAttachPin(pin);
adc1_config_channel_atten(pin, ADC_ATTEN_DB_11);
}
}
// for (val pin: mux_sel) pinMode(pin, OUTPUT);
Serial.begin(9600);
Serial.println("Initialized");
void readKeyboard()
{
u64 time = millis();
countFps(time);
// Toggle LED refresh indicator
digitalWrite(LED_REFRESH, led_refresh_on = !led_refresh_on);
// Loop through each multiplexer state
for (int i = 0; i < PINS_PER_MUX; i++)
{
// Set select pins
for (int j = 0; j < NUM_MUX_SEL; j++)
{
// i >> j is the jth bit of i
digitalWrite(MUX_SEL_OUT[j], (i >> j) & 1);
}
// Read input pins from the multiplexer
for (int j = 0; j < NUM_MUX; j++)
{
int note_id = j * PINS_PER_MUX + i;
if (note_id >= NUM_NOTES) break;
// Read the analog input
u32 v = analogRead(MUX_IN[j]);
if (v != lasts[note_id])
{
on_sensor_update(note_id, time, lasts[note_id], v);
}
lasts[note_id] = v;
}
}
// start_time = timeMillis();
}
/**
@@ -91,40 +81,90 @@ void readKeyboard()
*
* @param id Sensor index
*/
void on_sensor_update(int id, u64 time, u32 last, u32 current)
void on_sensor_update(int id, u64 time, int last, int current)
{
// If the last hit is too close, ignore this hit
let last_hit_time = last_hit_times[id];
if (time - last_hit_time < bounce_delay) return;
// If the value is above the threshold
if (current > active_threshold)
// If the last value is larger than the current value, check timeout
if (last > current && last > active_threshold)
{
// If the last value is below the threshold, it's a new hit
if (last < active_threshold)
u64 elapsed = time - last_hit_times[id];
if (elapsed < 150)
{
// Send MIDI message
// /hit <note> <velocity>
Serial.printf("/hit %d %d\r\n", notes[id].midi,
MIN((last - active_threshold) * 127 / (max_threshold - active_threshold), 127));
// Lights
keyboardLights.hit(id);
// If the last hit is too close, ignore this hit
return;
}
}
else if (last > active_threshold)
{
// Released
// The last value is a local maximum,
// and we read it as the hit strength of our note. Send midi command to the host.
// /hit <note> <velocity>
Serial.printf("/hit %d %d\r\n", notes[id].midi,
min((last - active_threshold) * 127 / (max_threshold - active_threshold), 127));
// Update last hit time
last_hit_times[id] = time;
}
}
/**
* Read sensor value with multisampling
*
* @param pin Sensor pin
* @return Sensor value
*/
int read_sensor(int pin)
{
let sum = 0;
for (let i = 0; i < MULTISAMPLING; i++) {
sum += analogRead(pin);
}
return (int) round(((float) sum) / ((float) MULTISAMPLING));
}
void loop()
{
readKeyboard();
// u64 time = timeMillis();
// u64 elapsed = time - start_time;
// for (int i = 0; i < LK_NUM_LIGHTS; i++)
// Toggle LED
led_state = !led_state;
digitalWrite(PIN_LED, led_state);
delay(100);
// // Loop through each multiplexer input
for (val mux : mux_in)
{
val v = read_sensor(mux);
Serial.printf("%d ", v);
}
Serial.println();
// Serial.printf("%" PRIu64 "=============\r\n", elapsed);
// Loop through each multiplexer state
// for (int i = 0; i < PINS_PER_MUX; i++)
// {
// lk.setPixelColor(i, Adafruit_NeoPixel::ColorHSV(last_hue + i * hue_interval, 255, brightness));
// // Set select pins
// for (int j = 0; j < NUM_MUX_SEL; j++)
// {
// // i >> j is the jth bit of i
// digitalWrite(mux_sel[j], (i >> j) & 1);
// }
//
// // Read four input pins from the multiplexer
// for (int j = 0; j < NUM_MUX; j++)
// {
// int note_id = j * PINS_PER_MUX + i;
// if (note_id >= NUM_NOTES) break;
//
// // Read the analog input
// int v = read_sensor(mux_in[j]);
// if (v != lasts[note_id])
// {
// // Serial prints are really slow, so don't use them in debug mode
// Serial.printf("%s %d\r\n", notes[note_id].name, v);
// on_sensor_update(note_id, time, lasts[note_id], v);
// }
// lasts[note_id] = v;
// }
// }
}
// Serial.printf("Loop %" PRIu64 "\r\n", elapsed);
}
-15
View File
@@ -1,15 +0,0 @@
//
// Created by Hykilpikonna on 4/13/23.
//
#ifndef FIRMWARE_MAIN_H
#define FIRMWARE_MAIN_H
/**
* Called when the sensor value changes
*
* @param id Sensor index
*/
void on_sensor_update(int id, u64 time, u32 last, u32 current);
#endif //FIRMWARE_MAIN_H
-184
View File
@@ -1,184 +0,0 @@
//
// Created by Hykilpikonna on 4/21/23.
//
#include <Arduino.h>
#include "config.h"
#include "Adafruit_NeoPixel.h"
#include "Encoder.h"
/**
* Class controlling the MIDI panel
*/
class Panel {
private:
Adafruit_NeoPixel led_key;
Adafruit_NeoPixel led_knob;
Adafruit_NeoPixel led_rotary;
u16 last_hue = 0;
u8 brightness = 40;
bool key_states[P_PINS_PER_MUX]{};
bool btn_states[P_PINS_PER_MUX]{};
u32 pot_states[P_PINS_PER_MUX]{};
Encoder *encoders[P_NUM_ROTARY]{};
int encoder_states[P_NUM_ROTARY]{};
TaskHandle_t panelThread{};
public:
Panel() :
led_key(4, P_LED_KEY, NEO_GRB + NEO_KHZ800),
led_knob(9, P_LED_KNOB, NEO_GRB + NEO_KHZ800),
led_rotary(9, P_LED_ROTARY, NEO_GRB + NEO_KHZ800)
{}
void begin()
{
for (int pin: P_MUX_SEL_OUT) pinModeSafe(pin, OUTPUT);
for (int pin: P_ROTARY_A) pinModeSafe(pin, INPUT);
for (int pin: P_ROTARY_B) pinModeSafe(pin, INPUT);
pinModeSafe(P_BUTTON_MUX_IN, INPUT);
pinModeSafe(P_KEY_MUX_IN, INPUT);
pinModeSafe(P_KNOB_MUX_IN, INPUT);
pinModeSafe(P_LED_KEY, OUTPUT);
pinModeSafe(P_LED_KNOB, OUTPUT);
pinModeSafe(P_LED_ROTARY, OUTPUT);
led_key.begin();
led_knob.begin();
led_rotary.begin();
// Initialize encoders
for (int i = 0; i < P_NUM_ROTARY; i++)
{
encoders[i] = new Encoder(P_ROTARY_A[i], P_ROTARY_B[i]);
}
xTaskCreate(loopPanel, "loopPanel", 4096, this, 1, &panelThread);
}
private:
void readPanel()
{
const auto hue_interval = 512;
last_hue += hue_interval;
// Read rotary encoders
for (int i = 0; i < P_NUM_ROTARY; ++i)
{
int state = encoders[i]->read();
if (encoder_states[i] != state)
{
encoder_states[i] = state;
Serial.printf("Rotary changed - id: %d, value: %d\r\n", i, state);
led_rotary.setPixelColor(i, Adafruit_NeoPixel::ColorHSV(last_hue, 255, brightness));
led_rotary.show();
}
}
// Read buttons
for (int i = 0; i < P_PINS_PER_MUX; ++i)
{
// Set select pins
for (int j = 0; j < P_NUM_MUX_SEL; ++j)
{
// i >> j is the jth bit of i
digitalWrite(P_MUX_SEL_OUT[j], (i >> j) & 1);
}
vTaskDelay(1);
// Read button
int key = !digitalRead(P_KEY_MUX_IN);
int btn = !digitalRead(P_BUTTON_MUX_IN);
// If the state is changed, call button callback
if (key_states[i] != key)
{
key_states[i] = key;
onKey(i, key);
}
if (btn_states[i] != btn)
{
btn_states[i] = btn;
onBtn(i, btn);
}
// Read potentiometer
int pot = (int) round(analogRead(P_KNOB_MUX_IN) / 16.0);
onPotRead(i, pot);
// If the state is changed, call potentiometer callback
if (ABS(pot_states[i] - pot) > 4)
{
pot_states[i] = pot;
onPotChange(i, pot);
}
}
delay(10);
led_key.show();
led_knob.show();
led_rotary.show();
}
void onKey(int id, bool state)
{
// Check if it's one of the larger keys (the first 4)
if (id < 4)
{
if (state)
{
// Set a random color for the key's LED
led_key.setPixelColor(id, Adafruit_NeoPixel::ColorHSV(random(0, 65535), 255, brightness));
led_key.show();
}
else
{
// Clear the key's LED
led_key.setPixelColor(id, 0);
led_key.show();
}
}
// Key 5 = clear
if (id == 4 && state)
{
led_key.clear();
led_key.show();
}
Serial.printf("Key changed - id: %d, state: %d\r\n", id, state);
}
void onBtn(int id, bool state)
{
Serial.printf("Button changed - id: %d, state: %d\r\n", id, state);
}
void onPotRead(int id, u8 value)
{
// Set LED
led_knob.setPixelColor(id, Adafruit_NeoPixel::ColorHSV(last_hue, 255, value));
led_knob.show();
}
void onPotChange(int id, u8 value)
{
// Serial.printf("Potentiometer changed - id: %d, value: %d\r\n", id, value);
}
[[noreturn]] static void loopPanel(void* pvParameters)
{
auto* panel = (Panel*) pvParameters;
while (true)
{
panel->readPanel();
}
}
};
-3
View File
@@ -1,3 +0,0 @@
#include "utils.h"
-15
View File
@@ -1,15 +0,0 @@
#ifndef FIRMWARE_UTILS_H
#define FIRMWARE_UTILS_H
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define u64 uint64_t
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define ABS(a) ((a) < 0 ? -(a) : (a))
#define let auto
#define pinModeSafe(pin, mode) do { if (pin != -1) pinMode(pin, mode); } while (false)
#endif //FIRMWARE_UTILS_H
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -23
View File
@@ -10,29 +10,7 @@ fn start() -> Result<()> {
let midi_out = MidiOutput::new("MIDI Output")?;
let mut conn_out = midi_out.create_virtual("Virtual MIDI Output").unwrap();
// List serial devices
let ports = serialport::available_ports()?;
// If there are no ports available, quit
if ports.is_empty() {
println!("No serial ports found.");
exit(0);
}
// Let the user choose a port
println!("Available serial ports:");
for (i, p) in ports.iter().enumerate() {
println!("{}: {}", i, p.port_name);
}
println!("Choose a port (range 0-{}): ", ports.len() - 1);
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let choice = input.trim().parse::<usize>().unwrap();
let port = &ports[choice];
// Open serial port
let serial_port = serialport::new(port.port_name.as_str(), 115200)
let serial_port = serialport::new("/dev/cu.usbmodem14501", 9600)
.timeout(std::time::Duration::from_millis(10))
.open()?;
let mut reader = BufReader::new(serial_port);