Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b4b96e65a | |||
| 26f65eed10 | |||
| 9be88be1f4 | |||
| 10d19a5e58 | |||
| a2ad5d244e | |||
| dc1a6ff5c3 | |||
| 7c06da08b4 | |||
| 3f31780deb | |||
| 24d981886a | |||
| f88f52c4ee |
@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
This library helps make writing firmware easier by abstracting away the initialization and peripheral interactions. Now your firmware code can just focus on the logic and behavior of the app, and keep the low level code neatly tucked away in this library.
|
This library helps make writing firmware easier by abstracting away the initialization and peripheral interactions. Now your firmware code can just focus on the logic and behavior of the app, and keep the low level code neatly tucked away in this library.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
You can flash the firmware to your module using the [Web Installer](https://awonak.github.io/alt-gravity/). This website also provides demo videos and documentation for each firmware version.
|
||||||
|
|
||||||
|
https://awonak.github.io/alt-gravity/
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Download or git clone this repository into your Arduino > libraries folder.
|
Download or git clone this repository into your Arduino > libraries folder.
|
||||||
|
|||||||
435
firmware/Comparator/Comparator.ino
Normal file
435
firmware/Comparator/Comparator.ino
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
#include <EEPROM.h>
|
||||||
|
#include <libGravity.h>
|
||||||
|
|
||||||
|
// EEPROM addrs
|
||||||
|
const int EEPROM_INIT_ADDR = 0;
|
||||||
|
const int EEPROM_CV1_LOW = 1;
|
||||||
|
const int EEPROM_CV1_HIGH = 3;
|
||||||
|
const int EEPROM_CV1_OFFSET = 5;
|
||||||
|
const int EEPROM_CV2_LOW = 7;
|
||||||
|
const int EEPROM_CV2_HIGH = 9;
|
||||||
|
const int EEPROM_CV2_OFFSET = 11;
|
||||||
|
const int EEPROM_COMP1_SHIFT = 13;
|
||||||
|
const int EEPROM_COMP1_SIZE = 15;
|
||||||
|
const int EEPROM_COMP2_SHIFT = 17;
|
||||||
|
const int EEPROM_COMP2_SIZE = 19;
|
||||||
|
const byte EEPROM_INIT_FLAG = 0xAB; // Update flag to re-init
|
||||||
|
|
||||||
|
// EEPROM Delay Save
|
||||||
|
const unsigned long SAVE_DELAY_MS = 5000;
|
||||||
|
bool eeprom_needs_save = false;
|
||||||
|
unsigned long last_param_change = 0;
|
||||||
|
|
||||||
|
enum AppMode { MODE_COMPARATOR, MODE_CALIBRATION };
|
||||||
|
AppMode current_mode = MODE_COMPARATOR;
|
||||||
|
byte cal_selected_param = 0; // 0=CV1 Low, 1=CV1 Offset, 2=CV1 High, 3=CV2 Low,
|
||||||
|
// 4=CV2 Offset, 5=CV2 High
|
||||||
|
|
||||||
|
// UI Parameters
|
||||||
|
enum Parameter { COMP1_SHIFT, COMP1_SIZE, COMP2_SHIFT, COMP2_SIZE };
|
||||||
|
|
||||||
|
Parameter selected_param = COMP1_SHIFT;
|
||||||
|
|
||||||
|
int comp1_shift = 0; // Range: -512 to 512
|
||||||
|
int comp1_size = 512; // Range: 0 to 1024
|
||||||
|
|
||||||
|
int comp2_shift = 0; // Range: -512 to 512
|
||||||
|
int comp2_size = 512; // Range: 0 to 1024
|
||||||
|
|
||||||
|
bool prev_gate1 = false;
|
||||||
|
bool prev_gate2 = false;
|
||||||
|
bool ff_state = false;
|
||||||
|
bool needs_redraw = true;
|
||||||
|
int last_cv1_draw = -1000;
|
||||||
|
int last_cv2_draw = -1000;
|
||||||
|
|
||||||
|
unsigned long last_redraw = 0;
|
||||||
|
bool prev_both_buttons = false;
|
||||||
|
|
||||||
|
// Calibration Methods
|
||||||
|
void LoadCalibration() {
|
||||||
|
if (EEPROM.read(EEPROM_INIT_ADDR) == EEPROM_INIT_FLAG) {
|
||||||
|
int val = 0;
|
||||||
|
EEPROM.get(EEPROM_CV1_LOW, val);
|
||||||
|
gravity.cv1.SetCalibrationLow(val);
|
||||||
|
EEPROM.get(EEPROM_CV1_HIGH, val);
|
||||||
|
gravity.cv1.SetCalibrationHigh(val);
|
||||||
|
EEPROM.get(EEPROM_CV1_OFFSET, val);
|
||||||
|
gravity.cv1.AdjustOffset(val - gravity.cv1.GetOffset());
|
||||||
|
EEPROM.get(EEPROM_CV2_LOW, val);
|
||||||
|
gravity.cv2.SetCalibrationLow(val);
|
||||||
|
EEPROM.get(EEPROM_CV2_HIGH, val);
|
||||||
|
gravity.cv2.SetCalibrationHigh(val);
|
||||||
|
EEPROM.get(EEPROM_CV2_OFFSET, val);
|
||||||
|
gravity.cv2.AdjustOffset(val - gravity.cv2.GetOffset());
|
||||||
|
|
||||||
|
EEPROM.get(EEPROM_COMP1_SHIFT, comp1_shift);
|
||||||
|
EEPROM.get(EEPROM_COMP1_SIZE, comp1_size);
|
||||||
|
EEPROM.get(EEPROM_COMP2_SHIFT, comp2_shift);
|
||||||
|
EEPROM.get(EEPROM_COMP2_SIZE, comp2_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveCalibration() {
|
||||||
|
EEPROM.update(EEPROM_INIT_ADDR, EEPROM_INIT_FLAG);
|
||||||
|
int val;
|
||||||
|
val = gravity.cv1.GetCalibrationLow();
|
||||||
|
EEPROM.put(EEPROM_CV1_LOW, val);
|
||||||
|
val = gravity.cv1.GetCalibrationHigh();
|
||||||
|
EEPROM.put(EEPROM_CV1_HIGH, val);
|
||||||
|
val = gravity.cv1.GetOffset();
|
||||||
|
EEPROM.put(EEPROM_CV1_OFFSET, val);
|
||||||
|
val = gravity.cv2.GetCalibrationLow();
|
||||||
|
EEPROM.put(EEPROM_CV2_LOW, val);
|
||||||
|
val = gravity.cv2.GetCalibrationHigh();
|
||||||
|
EEPROM.put(EEPROM_CV2_HIGH, val);
|
||||||
|
val = gravity.cv2.GetOffset();
|
||||||
|
EEPROM.put(EEPROM_CV2_OFFSET, val);
|
||||||
|
|
||||||
|
EEPROM.put(EEPROM_COMP1_SHIFT, comp1_shift);
|
||||||
|
EEPROM.put(EEPROM_COMP1_SIZE, comp1_size);
|
||||||
|
EEPROM.put(EEPROM_COMP2_SHIFT, comp2_shift);
|
||||||
|
EEPROM.put(EEPROM_COMP2_SIZE, comp2_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
void OnPlayPress() {
|
||||||
|
if (gravity.shift_button.On())
|
||||||
|
return; // ignore if holding both
|
||||||
|
if (current_mode == MODE_CALIBRATION) {
|
||||||
|
cal_selected_param = (cal_selected_param < 3) ? cal_selected_param + 3
|
||||||
|
: cal_selected_param - 3;
|
||||||
|
needs_redraw = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selected_param == COMP1_SHIFT)
|
||||||
|
selected_param = COMP2_SHIFT;
|
||||||
|
else if (selected_param == COMP1_SIZE)
|
||||||
|
selected_param = COMP2_SIZE;
|
||||||
|
else if (selected_param == COMP2_SHIFT)
|
||||||
|
selected_param = COMP1_SHIFT;
|
||||||
|
else if (selected_param == COMP2_SIZE)
|
||||||
|
selected_param = COMP1_SIZE;
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnShiftPress() {
|
||||||
|
if (gravity.play_button.On())
|
||||||
|
return; // ignore if holding both
|
||||||
|
if (current_mode == MODE_CALIBRATION) {
|
||||||
|
cal_selected_param =
|
||||||
|
(cal_selected_param / 3) * 3 + ((cal_selected_param + 1) % 3);
|
||||||
|
needs_redraw = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selected_param == COMP1_SHIFT)
|
||||||
|
selected_param = COMP1_SIZE;
|
||||||
|
else if (selected_param == COMP1_SIZE)
|
||||||
|
selected_param = COMP1_SHIFT;
|
||||||
|
else if (selected_param == COMP2_SHIFT)
|
||||||
|
selected_param = COMP2_SIZE;
|
||||||
|
else if (selected_param == COMP2_SIZE)
|
||||||
|
selected_param = COMP2_SHIFT;
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnEncoderRotate(int val) {
|
||||||
|
if (current_mode == MODE_CALIBRATION) {
|
||||||
|
AnalogInput *cv = (cal_selected_param > 2) ? &gravity.cv2 : &gravity.cv1;
|
||||||
|
// Scale val up so tuning is practical without excessive encoder interrupts
|
||||||
|
int cal_adj = val * 8;
|
||||||
|
switch (cal_selected_param % 3) {
|
||||||
|
case 0:
|
||||||
|
cv->AdjustCalibrationLow(cal_adj);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
cv->AdjustOffset(cal_adj);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
cv->AdjustCalibrationHigh(cal_adj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
needs_redraw = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int amount = val * 16;
|
||||||
|
switch (selected_param) {
|
||||||
|
case COMP1_SHIFT:
|
||||||
|
comp1_shift = constrain(comp1_shift + amount, -512, 512);
|
||||||
|
break;
|
||||||
|
case COMP1_SIZE:
|
||||||
|
comp1_size = constrain(comp1_size + amount, 0, 1024);
|
||||||
|
break;
|
||||||
|
case COMP2_SHIFT:
|
||||||
|
comp2_shift = constrain(comp2_shift + amount, -512, 512);
|
||||||
|
break;
|
||||||
|
case COMP2_SIZE:
|
||||||
|
comp2_size = constrain(comp2_size + amount, 0, 1024);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
eeprom_needs_save = true;
|
||||||
|
last_param_change = millis();
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayCalibrationPoint(AnalogInput *cv, const char *title, int index) {
|
||||||
|
int barWidth = 100, barHeight = 10, textHeight = 10;
|
||||||
|
int half = barWidth / 2;
|
||||||
|
int offsetX = 16, offsetY = (32 * index);
|
||||||
|
|
||||||
|
gravity.display.setDrawColor(1);
|
||||||
|
int value = cv->Read();
|
||||||
|
|
||||||
|
gravity.display.setCursor(0, offsetY + textHeight);
|
||||||
|
gravity.display.print(title);
|
||||||
|
if (value >= 0)
|
||||||
|
gravity.display.print(" ");
|
||||||
|
gravity.display.print(value);
|
||||||
|
|
||||||
|
gravity.display.setCursor(92, offsetY + textHeight);
|
||||||
|
if (cv->Voltage() >= 0)
|
||||||
|
gravity.display.print(" ");
|
||||||
|
gravity.display.print(cv->Voltage(), 1);
|
||||||
|
gravity.display.print(F("V"));
|
||||||
|
|
||||||
|
gravity.display.drawFrame(offsetX, textHeight + offsetY + 2, barWidth,
|
||||||
|
barHeight);
|
||||||
|
if (value > 0) {
|
||||||
|
int x = constrain(map(value, 0, 512, 0, half), 0, half);
|
||||||
|
gravity.display.drawBox(half + offsetX, textHeight + offsetY + 2, x,
|
||||||
|
barHeight);
|
||||||
|
} else {
|
||||||
|
int x = constrain(map(abs(value), 0, 512, 0, half), 0, half);
|
||||||
|
gravity.display.drawBox((half + offsetX) - x, textHeight + offsetY + 2, x,
|
||||||
|
barHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cal_selected_param / 3 == index) {
|
||||||
|
int left = offsetX + (half * (cal_selected_param % 3) - 2);
|
||||||
|
int top = barHeight + textHeight + offsetY + 12;
|
||||||
|
gravity.display.drawStr(left, top, "^");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateCalibrationDisplay() {
|
||||||
|
gravity.display.setFontMode(0);
|
||||||
|
gravity.display.setDrawColor(1);
|
||||||
|
gravity.display.setFont(u8g2_font_profont11_tf);
|
||||||
|
DisplayCalibrationPoint(&gravity.cv1, "CV1: ", 0);
|
||||||
|
DisplayCalibrationPoint(&gravity.cv2, "CV2: ", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDisplay
|
||||||
|
void UpdateDisplay(int cv1_val, int cv2_val) {
|
||||||
|
// Comp 1 graphics (Left)
|
||||||
|
int c1_h = max((comp1_size * 3) / 64, 1);
|
||||||
|
int c1_center = 26 - ((comp1_shift * 3) / 64);
|
||||||
|
int c1_y = c1_center - (c1_h / 2);
|
||||||
|
gravity.display.drawFrame(20, c1_y, 44, c1_h);
|
||||||
|
|
||||||
|
// CV 1 Indicator (Filled Box, 50% width, from 0V center)
|
||||||
|
int cv1_y = constrain(26 - ((cv1_val * 3) / 64), 2, 50);
|
||||||
|
if (cv1_val >= 0) {
|
||||||
|
gravity.display.drawBox(31, cv1_y, 22, 26 - cv1_y);
|
||||||
|
} else {
|
||||||
|
gravity.display.drawBox(31, 26, 22, cv1_y - 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comp 2 graphics (Right)
|
||||||
|
int c2_h = max((comp2_size * 3) / 64, 1);
|
||||||
|
int c2_center = 26 - ((comp2_shift * 3) / 64);
|
||||||
|
int c2_y = c2_center - (c2_h / 2);
|
||||||
|
gravity.display.drawFrame(74, c2_y, 44, c2_h);
|
||||||
|
|
||||||
|
// CV 2 Indicator (Filled Box, 50% width, from 0V center)
|
||||||
|
int cv2_y = constrain(26 - ((cv2_val * 3) / 64), 2, 50);
|
||||||
|
if (cv2_val >= 0) {
|
||||||
|
gravity.display.drawBox(85, cv2_y, 22, 26 - cv2_y);
|
||||||
|
} else {
|
||||||
|
gravity.display.drawBox(85, 26, 22, cv2_y - 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore solid drawing for labels
|
||||||
|
gravity.display.setDrawColor(1);
|
||||||
|
gravity.display.setFont(u8g2_font_5x7_tf);
|
||||||
|
gravity.display.setCursor(0, 7);
|
||||||
|
gravity.display.print("+5V");
|
||||||
|
gravity.display.setCursor(6, 29);
|
||||||
|
gravity.display.print("0V");
|
||||||
|
gravity.display.setCursor(0, 51);
|
||||||
|
gravity.display.print("-5V");
|
||||||
|
|
||||||
|
gravity.display.setDrawColor(2); // XOR mode
|
||||||
|
|
||||||
|
// Draw center divider and dotted lines in XOR
|
||||||
|
for (int x = 20; x < 128; x += 4) {
|
||||||
|
gravity.display.drawPixel(x, 2); // +5V
|
||||||
|
gravity.display.drawPixel(x, 26); // 0V
|
||||||
|
gravity.display.drawPixel(x, 50); // -5V
|
||||||
|
}
|
||||||
|
for (int y = 0; y <= 50; y += 4) {
|
||||||
|
gravity.display.drawPixel(69, y); // Center divider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore draw color to default (solid)
|
||||||
|
gravity.display.setDrawColor(1);
|
||||||
|
|
||||||
|
// Bottom text area
|
||||||
|
gravity.display.setDrawColor(0);
|
||||||
|
gravity.display.drawBox(0, 52, 128, 12);
|
||||||
|
gravity.display.setDrawColor(1);
|
||||||
|
gravity.display.drawHLine(0, 52, 128);
|
||||||
|
|
||||||
|
gravity.display.setFont(u8g2_font_6x10_tf);
|
||||||
|
gravity.display.setCursor(2, 62);
|
||||||
|
|
||||||
|
char text[32];
|
||||||
|
switch (selected_param) {
|
||||||
|
case COMP1_SHIFT:
|
||||||
|
snprintf(text, sizeof(text), "> Comp 1 Shift: %d", comp1_shift);
|
||||||
|
break;
|
||||||
|
case COMP1_SIZE:
|
||||||
|
snprintf(text, sizeof(text), "> Comp 1 Size: %d", comp1_size);
|
||||||
|
break;
|
||||||
|
case COMP2_SHIFT:
|
||||||
|
snprintf(text, sizeof(text), "> Comp 2 Shift: %d", comp2_shift);
|
||||||
|
break;
|
||||||
|
case COMP2_SIZE:
|
||||||
|
snprintf(text, sizeof(text), "> Comp 2 Size: %d", comp2_size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
gravity.display.print(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
gravity.Init();
|
||||||
|
LoadCalibration();
|
||||||
|
|
||||||
|
// Speed up ADC conversions
|
||||||
|
ADCSRA &= ~(bit(ADPS2) | bit(ADPS1) | bit(ADPS0));
|
||||||
|
ADCSRA |= bit(ADPS2);
|
||||||
|
|
||||||
|
gravity.play_button.AttachPressHandler(OnPlayPress);
|
||||||
|
gravity.shift_button.AttachPressHandler(OnShiftPress);
|
||||||
|
gravity.encoder.AttachRotateHandler(OnEncoderRotate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
gravity.Process();
|
||||||
|
|
||||||
|
bool both_pressed = gravity.play_button.On() && gravity.shift_button.On();
|
||||||
|
if (both_pressed && !prev_both_buttons) {
|
||||||
|
if (current_mode == MODE_COMPARATOR) {
|
||||||
|
current_mode = MODE_CALIBRATION;
|
||||||
|
cal_selected_param = 0;
|
||||||
|
|
||||||
|
// Turn off all outputs to prevent phantom gates while tuning
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
gravity.outputs[i].Update(LOW);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SaveCalibration();
|
||||||
|
current_mode = MODE_COMPARATOR;
|
||||||
|
}
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
prev_both_buttons = both_pressed;
|
||||||
|
|
||||||
|
int cv1_val = gravity.cv1.Read();
|
||||||
|
int cv2_val = gravity.cv2.Read();
|
||||||
|
|
||||||
|
if (current_mode == MODE_COMPARATOR) {
|
||||||
|
int c1_lower = comp1_shift - (comp1_size / 2);
|
||||||
|
int c1_upper = comp1_shift + (comp1_size / 2);
|
||||||
|
|
||||||
|
int c2_lower = comp2_shift - (comp2_size / 2);
|
||||||
|
int c2_upper = comp2_shift + (comp2_size / 2);
|
||||||
|
|
||||||
|
const int HYSTERESIS = 4; // Margin to prevent noise bouncing at threshold
|
||||||
|
|
||||||
|
bool gate1 = prev_gate1;
|
||||||
|
if (gate1) {
|
||||||
|
if (cv1_val < c1_lower - HYSTERESIS || cv1_val > c1_upper + HYSTERESIS) {
|
||||||
|
gate1 = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (cv1_val >= c1_lower + HYSTERESIS &&
|
||||||
|
cv1_val <= c1_upper - HYSTERESIS) {
|
||||||
|
gate1 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gate2 = prev_gate2;
|
||||||
|
if (gate2) {
|
||||||
|
if (cv2_val < c2_lower - HYSTERESIS || cv2_val > c2_upper + HYSTERESIS) {
|
||||||
|
gate2 = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (cv2_val >= c2_lower + HYSTERESIS &&
|
||||||
|
cv2_val <= c2_upper - HYSTERESIS) {
|
||||||
|
gate2 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool logic_and = gate1 && gate2;
|
||||||
|
bool logic_or = gate1 || gate2;
|
||||||
|
bool logic_xor = gate1 ^ gate2;
|
||||||
|
|
||||||
|
static bool prev_logic_xor = false;
|
||||||
|
if (logic_xor && !prev_logic_xor) {
|
||||||
|
ff_state = !ff_state;
|
||||||
|
}
|
||||||
|
prev_logic_xor = logic_xor;
|
||||||
|
|
||||||
|
gravity.outputs[0].Update(gate1 ? HIGH : LOW);
|
||||||
|
gravity.outputs[1].Update(gate2 ? HIGH : LOW);
|
||||||
|
gravity.outputs[2].Update(logic_and ? HIGH : LOW);
|
||||||
|
gravity.outputs[3].Update(logic_or ? HIGH : LOW);
|
||||||
|
gravity.outputs[4].Update(logic_xor ? HIGH : LOW);
|
||||||
|
gravity.outputs[5].Update(ff_state ? HIGH : LOW);
|
||||||
|
|
||||||
|
prev_gate1 = gate1;
|
||||||
|
prev_gate2 = gate2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eeprom_needs_save && (millis() - last_param_change > SAVE_DELAY_MS)) {
|
||||||
|
SaveCalibration();
|
||||||
|
eeprom_needs_save = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_mode == MODE_COMPARATOR) {
|
||||||
|
if (abs(cv1_val - last_cv1_draw) > 12 ||
|
||||||
|
abs(cv2_val - last_cv2_draw) > 12) {
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
} else if (current_mode == MODE_CALIBRATION) {
|
||||||
|
// Need frequent redraws in calibration to see the live target input
|
||||||
|
if (abs(cv1_val - last_cv1_draw) >= 2 ||
|
||||||
|
abs(cv2_val - last_cv2_draw) >= 2) {
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unroll the display loop so it doesn't block the logic loop
|
||||||
|
static bool is_drawing = false;
|
||||||
|
|
||||||
|
if (needs_redraw && !is_drawing && (millis() - last_redraw >= 30)) {
|
||||||
|
needs_redraw = false;
|
||||||
|
is_drawing = true;
|
||||||
|
last_redraw = millis();
|
||||||
|
gravity.display.firstPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_drawing) {
|
||||||
|
if (current_mode == MODE_COMPARATOR) {
|
||||||
|
last_cv1_draw = cv1_val;
|
||||||
|
last_cv2_draw = cv2_val;
|
||||||
|
UpdateDisplay(cv1_val, cv2_val);
|
||||||
|
} else {
|
||||||
|
UpdateCalibrationDisplay();
|
||||||
|
}
|
||||||
|
is_drawing = gravity.display.nextPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,8 +18,8 @@
|
|||||||
// Define the constants for the current firmware.
|
// Define the constants for the current firmware.
|
||||||
const char StateManager::SKETCH_NAME[] = "ALT EUCLIDEAN";
|
const char StateManager::SKETCH_NAME[] = "ALT EUCLIDEAN";
|
||||||
const char StateManager::SEMANTIC_VERSION[] =
|
const char StateManager::SEMANTIC_VERSION[] =
|
||||||
"V2.0.1BETA1"; // NOTE: This should match the version in the
|
"V2.0.1"; // NOTE: This should match the version in the
|
||||||
// library.properties file.
|
// library.properties file.
|
||||||
|
|
||||||
// Number of available save slots.
|
// Number of available save slots.
|
||||||
const byte StateManager::MAX_SAVE_SLOTS = 10;
|
const byte StateManager::MAX_SAVE_SLOTS = 10;
|
||||||
|
|||||||
@ -18,8 +18,8 @@
|
|||||||
// Define the constants for the current firmware.
|
// Define the constants for the current firmware.
|
||||||
const char StateManager::SKETCH_NAME[] = "ALT GRAVITY";
|
const char StateManager::SKETCH_NAME[] = "ALT GRAVITY";
|
||||||
const char StateManager::SEMANTIC_VERSION[] =
|
const char StateManager::SEMANTIC_VERSION[] =
|
||||||
"V2.0.1BETA1"; // NOTE: This should match the version in the
|
"V2.0.1"; // NOTE: This should match the version in the
|
||||||
// library.properties file.
|
// library.properties file.
|
||||||
|
|
||||||
// Number of available save slots.
|
// Number of available save slots.
|
||||||
const byte StateManager::MAX_SAVE_SLOTS = 10;
|
const byte StateManager::MAX_SAVE_SLOTS = 10;
|
||||||
|
|||||||
371
firmware/Rhythm/Rhythm.ino
Normal file
371
firmware/Rhythm/Rhythm.ino
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
#include "maps.h"
|
||||||
|
#include <EEPROM.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <libGravity.h>
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
const int MAX_DENSITY = 255;
|
||||||
|
const int MAP_RESOLUTION = 255;
|
||||||
|
const int MAX_CHAOS = 255;
|
||||||
|
|
||||||
|
// EEPROM addrs
|
||||||
|
const int EEPROM_INIT_ADDR = 0;
|
||||||
|
const int EEPROM_DENS_K = 1;
|
||||||
|
const int EEPROM_DENS_S = 3;
|
||||||
|
const int EEPROM_DENS_H = 5;
|
||||||
|
const int EEPROM_MAP_X = 7;
|
||||||
|
const int EEPROM_MAP_Y = 9;
|
||||||
|
const int EEPROM_CHAOS = 11;
|
||||||
|
const byte EEPROM_INIT_FLAG = 0xAC; // Update flag to re-init
|
||||||
|
|
||||||
|
// EEPROM Delay Save
|
||||||
|
const unsigned long SAVE_DELAY_MS = 5000;
|
||||||
|
bool eeprom_needs_save = false;
|
||||||
|
unsigned long last_param_change = 0;
|
||||||
|
|
||||||
|
// UI & Navigation
|
||||||
|
enum SelectedParam {
|
||||||
|
PARAM_KICK_DENS = 0,
|
||||||
|
PARAM_SNARE_DENS = 1,
|
||||||
|
PARAM_HIHAT_DENS = 2,
|
||||||
|
PARAM_CHAOS = 3,
|
||||||
|
PARAM_MAP_X = 4,
|
||||||
|
PARAM_MAP_Y = 5,
|
||||||
|
PARAM_LAST = 6
|
||||||
|
};
|
||||||
|
|
||||||
|
SelectedParam current_param = PARAM_KICK_DENS;
|
||||||
|
bool editing_param = false;
|
||||||
|
bool needs_redraw = true;
|
||||||
|
unsigned long last_redraw = 0;
|
||||||
|
const unsigned long REDRAW_DELAY_MS = 30; // ~33fps limit
|
||||||
|
|
||||||
|
// Sequencer State
|
||||||
|
int current_step = 0;
|
||||||
|
bool is_playing = false;
|
||||||
|
|
||||||
|
// Engine Parameters (0-255)
|
||||||
|
int inst_density[3] = {128, 128,
|
||||||
|
128}; // Default 50% density for kick, snare, hihat
|
||||||
|
int map_x = 0; // 0 to 255 (0 = House, 127 = Breakbeat, 255 = Hiphop)
|
||||||
|
int map_y = 127; // 0 to 255 (0 = Sparse, 127 = Standard, 255 = Busy)
|
||||||
|
int chaos_amount = 0; // 0 to 255
|
||||||
|
|
||||||
|
volatile int cv1_val = 0;
|
||||||
|
volatile int cv2_val = 0;
|
||||||
|
|
||||||
|
// LFSR State for Chaos
|
||||||
|
uint16_t lfsr = 0xACE1;
|
||||||
|
|
||||||
|
// Math Helper: 1D Linear Interpolation between two bytes
|
||||||
|
uint8_t lerp(uint8_t a, uint8_t b, uint8_t t) {
|
||||||
|
// t is 0-255. returns a if t=0, b if t=255
|
||||||
|
return a + (((b - a) * t) >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Math Helper: Get threshold from 2D map via interpolation
|
||||||
|
uint8_t GetThreshold(int inst, int step, int x_pos, int y_pos) {
|
||||||
|
// x_pos is 0-255 mapped across 4 nodes (0, 1, 2, 3). Distance is 85 (255 / 3)
|
||||||
|
// y_pos is 0-255 mapped across 4 nodes (0, 1, 2, 3). Distance is 85 (255 / 3)
|
||||||
|
|
||||||
|
int x_idx = x_pos / 85;
|
||||||
|
int y_idx = y_pos / 85;
|
||||||
|
|
||||||
|
uint8_t x_frac = (x_pos % 85) * 3; // scale remainder 0-84 up to 0-255
|
||||||
|
uint8_t y_frac = (y_pos % 85) * 3;
|
||||||
|
|
||||||
|
// Guard against out of bounds if exactly 255
|
||||||
|
if (x_idx >= 3) {
|
||||||
|
x_idx = 2;
|
||||||
|
x_frac = 255;
|
||||||
|
}
|
||||||
|
if (y_idx >= 3) {
|
||||||
|
y_idx = 2;
|
||||||
|
y_frac = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read 4 corners from PROGMEM
|
||||||
|
uint8_t p00 = pgm_read_byte(&PATTERN_MAPS[x_idx][y_idx][inst][step]);
|
||||||
|
uint8_t p10 = pgm_read_byte(&PATTERN_MAPS[x_idx + 1][y_idx][inst][step]);
|
||||||
|
uint8_t p01 = pgm_read_byte(&PATTERN_MAPS[x_idx][y_idx + 1][inst][step]);
|
||||||
|
uint8_t p11 = pgm_read_byte(&PATTERN_MAPS[x_idx + 1][y_idx + 1][inst][step]);
|
||||||
|
|
||||||
|
// Bilinear interpolation
|
||||||
|
uint8_t lerp_top = lerp(p00, p10, x_frac);
|
||||||
|
uint8_t lerp_bottom = lerp(p01, p11, x_frac);
|
||||||
|
return lerp(lerp_top, lerp_bottom, y_frac);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadState() {
|
||||||
|
if (EEPROM.read(EEPROM_INIT_ADDR) == EEPROM_INIT_FLAG) {
|
||||||
|
EEPROM.get(EEPROM_DENS_K, inst_density[0]);
|
||||||
|
EEPROM.get(EEPROM_DENS_S, inst_density[1]);
|
||||||
|
EEPROM.get(EEPROM_DENS_H, inst_density[2]);
|
||||||
|
EEPROM.get(EEPROM_MAP_X, map_x);
|
||||||
|
EEPROM.get(EEPROM_MAP_Y, map_y);
|
||||||
|
EEPROM.get(EEPROM_CHAOS, chaos_amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveState() {
|
||||||
|
EEPROM.update(EEPROM_INIT_ADDR, EEPROM_INIT_FLAG);
|
||||||
|
EEPROM.put(EEPROM_DENS_K, inst_density[0]);
|
||||||
|
EEPROM.put(EEPROM_DENS_S, inst_density[1]);
|
||||||
|
EEPROM.put(EEPROM_DENS_H, inst_density[2]);
|
||||||
|
EEPROM.put(EEPROM_MAP_X, map_x);
|
||||||
|
EEPROM.put(EEPROM_MAP_Y, map_y);
|
||||||
|
EEPROM.put(EEPROM_CHAOS, chaos_amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LFSR random bit generator (returns 0 or 1, fast)
|
||||||
|
uint8_t GetRandomBit() {
|
||||||
|
uint8_t bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1;
|
||||||
|
lfsr = (lfsr >> 1) | (bit << 15);
|
||||||
|
return bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 8-bit pseudo-random number
|
||||||
|
uint8_t GetRandomByte() {
|
||||||
|
uint8_t r = 0;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
r = (r << 1) | GetRandomBit();
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessSequencerTick(uint32_t tick) {
|
||||||
|
// Assuming 96 PPQN clock. We want 16th notes.
|
||||||
|
// 96 pulses per quarter note / 4 = 24 pulses per 16th note.
|
||||||
|
const int PULSES_PER_16TH = 24;
|
||||||
|
|
||||||
|
// Pulse logic outputs low halfway through the 16th note (12 pulses)
|
||||||
|
if (tick % PULSES_PER_16TH == 12) {
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
gravity.outputs[i].Low();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle new 16th note step
|
||||||
|
if (tick % PULSES_PER_16TH == 0) {
|
||||||
|
|
||||||
|
int mod_map_x = constrain(map_x + (cv1_val / 2), 0, 255);
|
||||||
|
int active_chaos = constrain(chaos_amount + (cv2_val / 2), 0, 255);
|
||||||
|
|
||||||
|
// Evaluate hits for Kick, Snare, HiHats
|
||||||
|
for (int inst = 0; inst < 3; inst++) {
|
||||||
|
uint8_t threshold = GetThreshold(inst, current_step, mod_map_x, map_y);
|
||||||
|
int active_density = inst_density[inst];
|
||||||
|
|
||||||
|
// Inject chaos
|
||||||
|
if (active_chaos > 0) {
|
||||||
|
// Chaos randomly adds or subtracts from density.
|
||||||
|
int r = GetRandomByte();
|
||||||
|
int chaos_variance = map(active_chaos, 0, 255, 0, 128);
|
||||||
|
if (GetRandomBit()) {
|
||||||
|
active_density += map(r, 0, 255, 0, chaos_variance);
|
||||||
|
} else {
|
||||||
|
active_density -= map(r, 0, 255, 0, chaos_variance);
|
||||||
|
}
|
||||||
|
active_density = constrain(active_density, 0, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire Trigger?
|
||||||
|
if (active_density > threshold) {
|
||||||
|
// Output 1-3
|
||||||
|
gravity.outputs[inst].High();
|
||||||
|
|
||||||
|
// Fire Accent Trigger? (If density greatly exceeds threshold)
|
||||||
|
if (active_density > threshold + 60) {
|
||||||
|
// Output 4-6
|
||||||
|
gravity.outputs[inst + 3].High();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_step = (current_step + 1) % 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnPlayPress() {
|
||||||
|
if (is_playing) {
|
||||||
|
gravity.clock.Stop();
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
gravity.outputs[i].Low();
|
||||||
|
} else {
|
||||||
|
gravity.clock.Start();
|
||||||
|
}
|
||||||
|
is_playing = !is_playing;
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnEncoderPress() {
|
||||||
|
editing_param = !editing_param;
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnEncoderRotate(int val) {
|
||||||
|
if (!editing_param) {
|
||||||
|
// Navigate menu (clamp to edges, do not wrap)
|
||||||
|
int next_param = (int)current_param + val;
|
||||||
|
next_param = constrain(next_param, 0, PARAM_LAST - 1);
|
||||||
|
current_param = (SelectedParam)next_param;
|
||||||
|
} else {
|
||||||
|
// Edit parameter
|
||||||
|
int amt = val * 8; // Adjust by 8 values at a time for speed mapping
|
||||||
|
|
||||||
|
switch (current_param) {
|
||||||
|
case PARAM_KICK_DENS:
|
||||||
|
inst_density[0] = constrain(inst_density[0] + amt, 0, 255);
|
||||||
|
break;
|
||||||
|
case PARAM_SNARE_DENS:
|
||||||
|
inst_density[1] = constrain(inst_density[1] + amt, 0, 255);
|
||||||
|
break;
|
||||||
|
case PARAM_HIHAT_DENS:
|
||||||
|
inst_density[2] = constrain(inst_density[2] + amt, 0, 255);
|
||||||
|
break;
|
||||||
|
case PARAM_CHAOS:
|
||||||
|
chaos_amount = constrain(chaos_amount + amt, 0, 255);
|
||||||
|
break;
|
||||||
|
case PARAM_MAP_X:
|
||||||
|
map_x = constrain(map_x + amt, 0, 255);
|
||||||
|
break;
|
||||||
|
case PARAM_MAP_Y:
|
||||||
|
map_y = constrain(map_y + amt, 0, 255);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
eeprom_needs_save = true;
|
||||||
|
last_param_change = millis();
|
||||||
|
}
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawBarGraph(int y, const char *label, int value, bool is_selected) {
|
||||||
|
// Reset draw color to default foreground
|
||||||
|
gravity.display.setDrawColor(1);
|
||||||
|
gravity.display.setCursor(0, y);
|
||||||
|
|
||||||
|
if (is_selected) {
|
||||||
|
gravity.display.print(">");
|
||||||
|
if (editing_param) {
|
||||||
|
// Draw solid white box behind the label
|
||||||
|
gravity.display.drawBox(6, y - 8, 26, 10);
|
||||||
|
// Switch to black text to 'cut out' the label from the box
|
||||||
|
gravity.display.setDrawColor(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gravity.display.print(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
gravity.display.setCursor(6, y);
|
||||||
|
gravity.display.print(label);
|
||||||
|
|
||||||
|
// Restore draw color to white for the bar and text
|
||||||
|
gravity.display.setDrawColor(1);
|
||||||
|
|
||||||
|
// Draw Bar
|
||||||
|
int barLen = map(value, 0, 255, 0, 60);
|
||||||
|
gravity.display.drawFrame(34, y - 8, 60, 8);
|
||||||
|
gravity.display.drawBox(34, y - 8, barLen, 8);
|
||||||
|
|
||||||
|
// Draw value percentage
|
||||||
|
gravity.display.setCursor(98, y);
|
||||||
|
int pct = map(value, 0, 255, 0, 100);
|
||||||
|
gravity.display.print(pct);
|
||||||
|
gravity.display.print("%");
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDisplay() {
|
||||||
|
gravity.display.setFontMode(1);
|
||||||
|
gravity.display.setDrawColor(1);
|
||||||
|
gravity.display.setFont(u8g2_font_5x7_tf);
|
||||||
|
|
||||||
|
// Header
|
||||||
|
gravity.display.setCursor(0, 7);
|
||||||
|
if (is_playing)
|
||||||
|
gravity.display.print("[>] PLAY");
|
||||||
|
else
|
||||||
|
gravity.display.print("[||] PAUS");
|
||||||
|
|
||||||
|
gravity.display.setCursor(55, 7);
|
||||||
|
gravity.display.print("BPM:");
|
||||||
|
gravity.display.print(gravity.clock.Tempo());
|
||||||
|
|
||||||
|
gravity.display.drawHLine(0, 10, 128);
|
||||||
|
|
||||||
|
// Parameters List (Scrollable window of 5 items)
|
||||||
|
int y_start = 20;
|
||||||
|
int y_spacing = 9;
|
||||||
|
|
||||||
|
// Calculate window start index
|
||||||
|
int window_start = max(0, min((int)current_param - 2, PARAM_LAST - 5));
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
int param_idx = window_start + i;
|
||||||
|
int y_pos = y_start + (y_spacing * i);
|
||||||
|
bool is_sel = (current_param == param_idx);
|
||||||
|
|
||||||
|
switch (param_idx) {
|
||||||
|
case PARAM_KICK_DENS:
|
||||||
|
DrawBarGraph(y_pos, "KICK", inst_density[0], is_sel);
|
||||||
|
break;
|
||||||
|
case PARAM_SNARE_DENS:
|
||||||
|
DrawBarGraph(y_pos, "SNAR", inst_density[1], is_sel);
|
||||||
|
break;
|
||||||
|
case PARAM_HIHAT_DENS:
|
||||||
|
DrawBarGraph(y_pos, "HHAT", inst_density[2], is_sel);
|
||||||
|
break;
|
||||||
|
case PARAM_CHAOS:
|
||||||
|
DrawBarGraph(y_pos, "CHAO", chaos_amount, is_sel);
|
||||||
|
break;
|
||||||
|
case PARAM_MAP_X:
|
||||||
|
DrawBarGraph(y_pos, "MAPX", map_x, is_sel);
|
||||||
|
break;
|
||||||
|
case PARAM_MAP_Y:
|
||||||
|
DrawBarGraph(y_pos, "MAPY", map_y, is_sel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
gravity.Init();
|
||||||
|
LoadState();
|
||||||
|
|
||||||
|
gravity.play_button.AttachPressHandler(OnPlayPress);
|
||||||
|
gravity.encoder.AttachPressHandler(OnEncoderPress);
|
||||||
|
gravity.encoder.AttachRotateHandler(OnEncoderRotate);
|
||||||
|
|
||||||
|
gravity.clock.AttachIntHandler(ProcessSequencerTick);
|
||||||
|
// Default to 120 BPM internal
|
||||||
|
gravity.clock.SetTempo(120);
|
||||||
|
gravity.clock.SetSource(Clock::SOURCE_INTERNAL);
|
||||||
|
|
||||||
|
// Speed up I2C for faster OLED refreshing
|
||||||
|
Wire.setClock(400000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
gravity.Process();
|
||||||
|
|
||||||
|
// Apply CV modulation
|
||||||
|
// CV1 modulates Map X, CV2 modulates Chaos
|
||||||
|
cv1_val = gravity.cv1.Read(); // -512 to 512
|
||||||
|
cv2_val = gravity.cv2.Read();
|
||||||
|
|
||||||
|
if (eeprom_needs_save && (millis() - last_param_change > SAVE_DELAY_MS)) {
|
||||||
|
SaveState();
|
||||||
|
eeprom_needs_save = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_redraw && (millis() - last_redraw > REDRAW_DELAY_MS)) {
|
||||||
|
needs_redraw = false;
|
||||||
|
last_redraw = millis();
|
||||||
|
gravity.display.firstPage();
|
||||||
|
do {
|
||||||
|
UpdateDisplay();
|
||||||
|
} while (gravity.display.nextPage());
|
||||||
|
}
|
||||||
|
}
|
||||||
136
firmware/Rhythm/maps.h
Normal file
136
firmware/Rhythm/maps.h
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#ifndef MAPS_H
|
||||||
|
#define MAPS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <avr/pgmspace.h>
|
||||||
|
|
||||||
|
// 4x4 Grid of Patterns (X = Genre, Y = Variation)
|
||||||
|
// X=0: House/Techno (4 on floor)
|
||||||
|
// X=1: Breakbeat/Drum&Bass (Syncopated)
|
||||||
|
// X=2: HipHop/Trap (Half-time feel)
|
||||||
|
// X=3: Dem Bow / Reggaeton (Tresillo rhythm)
|
||||||
|
// Y=0: Sparse/Basic
|
||||||
|
// Y=1: Standard
|
||||||
|
// Y=2: Syncopated/Groove
|
||||||
|
// Y=3: Busy/Fills
|
||||||
|
|
||||||
|
// 3 Instruments (Kick=0, Snare=1, HiHat=2)
|
||||||
|
// 16 Steps per pattern.
|
||||||
|
// Values are "Thresholds" (0-255). A step fires if the channel's Density setting > Threshold.
|
||||||
|
// A Threshold of 255 means it NEVER fires. 0 means it ALWAYS fires (even at minimum density).
|
||||||
|
|
||||||
|
const uint8_t PATTERN_MAPS[4][4][3][16] PROGMEM = {
|
||||||
|
// X = 0 (House)
|
||||||
|
{
|
||||||
|
// Y = 0 (Sparse)
|
||||||
|
{
|
||||||
|
{ 20, 255, 255, 255, 20, 255, 255, 255, 20, 255, 255, 255, 20, 255, 255, 255 }, // Kick
|
||||||
|
{ 255, 255, 255, 255, 50, 255, 255, 255, 255, 255, 255, 255, 50, 255, 255, 255 }, // Snare
|
||||||
|
{ 255, 255, 60, 255, 255, 255, 60, 255, 255, 255, 60, 255, 255, 255, 60, 255 } // HiHat (Offbeats)
|
||||||
|
},
|
||||||
|
// Y = 1 (Standard)
|
||||||
|
{
|
||||||
|
{ 10, 255, 200, 255, 10, 255, 200, 255, 10, 255, 200, 255, 10, 255, 200, 255 }, // Kick
|
||||||
|
{ 255, 255, 255, 255, 30, 255, 255, 200, 255, 255, 255, 255, 30, 255, 255, 200 }, // Snare
|
||||||
|
{ 150, 80, 40, 80, 150, 80, 40, 80, 150, 80, 40, 80, 150, 80, 40, 80 } // HiHat
|
||||||
|
},
|
||||||
|
// Y = 2 (Syncopated Break/Groove)
|
||||||
|
{
|
||||||
|
{ 10, 255, 180, 255, 255, 180, 150, 255, 10, 255, 180, 200, 255, 180, 150, 255 }, // Kick (pushing beats)
|
||||||
|
{ 255, 200, 255, 200, 20, 200, 255, 150, 255, 200, 255, 200, 20, 200, 255, 150 }, // Snare (ghost notes)
|
||||||
|
{ 60, 150, 20, 150, 80, 150, 20, 150, 60, 150, 20, 150, 80, 150, 20, 150 } // HiHat (dynamic 16ths)
|
||||||
|
},
|
||||||
|
// Y = 3 (Busy / Driving)
|
||||||
|
{
|
||||||
|
{ 5, 180, 150, 200, 5, 180, 150, 200, 5, 180, 150, 200, 5, 180, 150, 200 }, // Kick
|
||||||
|
{ 255, 200, 200, 255, 20, 200, 200, 180, 255, 200, 200, 255, 20, 200, 200, 180 }, // Snare
|
||||||
|
{ 20, 60, 10, 60, 20, 60, 10, 60, 20, 60, 10, 60, 20, 60, 10, 60 } // HiHat (accented 16ths)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// X = 1 (Breakbeat)
|
||||||
|
{
|
||||||
|
// Y = 0 (Sparse)
|
||||||
|
{
|
||||||
|
{ 20, 255, 255, 255, 255, 255, 100, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, // Kick (1, 3& half)
|
||||||
|
{ 255, 255, 255, 255, 50, 255, 255, 255, 255, 255, 255, 255, 50, 255, 255, 255 }, // Snare (2, 4)
|
||||||
|
{ 60, 255, 150, 255, 60, 255, 150, 255, 60, 255, 150, 255, 60, 255, 150, 255 } // HiHat (8ths)
|
||||||
|
},
|
||||||
|
// Y = 1 (Standard)
|
||||||
|
{
|
||||||
|
{ 10, 255, 150, 255, 255, 180, 80, 200, 255, 150, 255, 200, 255, 255, 150, 255 }, // Kick
|
||||||
|
{ 255, 255, 200, 255, 30, 255, 255, 150, 255, 255, 180, 255, 30, 255, 200, 180 }, // Snare (syncopated ghosts)
|
||||||
|
{ 40, 120, 80, 120, 40, 120, 80, 120, 40, 120, 80, 120, 40, 120, 80, 120 } // HiHat
|
||||||
|
},
|
||||||
|
// Y = 2 (Jungle / Fast Syncopation)
|
||||||
|
{
|
||||||
|
{ 10, 200, 255, 150, 255, 200, 80, 255, 10, 200, 255, 150, 255, 200, 80, 200 }, // Kick
|
||||||
|
{ 255, 150, 200, 180, 20, 200, 150, 100, 255, 150, 200, 180, 20, 200, 150, 100 }, // Snare (heavy ghosts & rolls)
|
||||||
|
{ 50, 150, 150, 100, 50, 150, 150, 100, 50, 150, 150, 100, 50, 150, 150, 100 } // HiHat (dynamic 16ths)
|
||||||
|
},
|
||||||
|
// Y = 3 (Busy Breakcore)
|
||||||
|
{
|
||||||
|
{ 5, 150, 120, 150, 180, 100, 50, 120, 150, 80, 150, 100, 200, 80, 100, 150 }, // Kick
|
||||||
|
{ 200, 180, 150, 200, 20, 150, 180, 100, 180, 120, 100, 150, 20, 100, 120, 80 }, // Snare
|
||||||
|
{ 20, 50, 40, 50, 20, 50, 40, 50, 20, 50, 40, 50, 20, 50, 40, 50 } // HiHat (16ths + dynamic)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// X = 2 (HipHop)
|
||||||
|
{
|
||||||
|
// Y = 0 (Sparse)
|
||||||
|
{
|
||||||
|
{ 20, 255, 255, 255, 255, 255, 255, 255, 100, 255, 255, 255, 255, 255, 255, 255 }, // Kick (1, 3)
|
||||||
|
{ 255, 255, 255, 255, 60, 255, 255, 255, 255, 255, 255, 255, 60, 255, 255, 255 }, // Snare (2, 4)
|
||||||
|
{ 80, 255, 255, 255, 80, 255, 255, 255, 80, 255, 255, 255, 80, 255, 255, 255 } // HiHat (Quarters)
|
||||||
|
},
|
||||||
|
// Y = 1 (Standard)
|
||||||
|
{
|
||||||
|
{ 10, 255, 255, 150, 255, 255, 255, 200, 80, 255, 150, 255, 255, 255, 255, 200 }, // Kick
|
||||||
|
{ 255, 255, 255, 255, 40, 255, 255, 255, 255, 255, 255, 255, 40, 255, 200, 255 }, // Snare
|
||||||
|
{ 50, 150, 50, 150, 50, 150, 50, 150, 50, 150, 50, 150, 50, 150, 50, 150 } // HiHat (8ths)
|
||||||
|
},
|
||||||
|
// Y = 2 (Boom Bap / Swing)
|
||||||
|
{
|
||||||
|
{ 10, 255, 150, 200, 255, 255, 150, 200, 50, 255, 100, 255, 255, 200, 150, 255 }, // Kick (Swung 16ths)
|
||||||
|
{ 255, 255, 200, 255, 30, 255, 200, 150, 255, 255, 200, 255, 30, 200, 255, 150 }, // Snare (Ghost notes)
|
||||||
|
{ 30, 200, 80, 150, 30, 200, 80, 150, 30, 200, 80, 150, 30, 200, 80, 150 } // HiHat (Heavy swing feel)
|
||||||
|
},
|
||||||
|
// Y = 3 (Busy / Trap)
|
||||||
|
{
|
||||||
|
{ 5, 200, 150, 80, 200, 180, 150, 120, 40, 150, 80, 200, 150, 180, 120, 100 }, // Kick
|
||||||
|
{ 255, 255, 200, 255, 20, 200, 255, 150, 255, 255, 180, 255, 20, 150, 180, 100 }, // Snare (with trap rolls)
|
||||||
|
{ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 } // HiHat (rapid 16ths)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// X = 3 (Dem Bow / Reggaeton - Tresillo Rhythm)
|
||||||
|
{
|
||||||
|
// Y = 0 (Sparse)
|
||||||
|
{
|
||||||
|
{ 20, 255, 255, 255, 20, 255, 255, 255, 20, 255, 255, 255, 20, 255, 255, 255 }, // Kick (4 on floor)
|
||||||
|
{ 255, 255, 255, 60, 255, 255, 60, 255, 255, 255, 255, 60, 255, 255, 60, 255 }, // Snare (Tresillo off-beats)
|
||||||
|
{ 255, 255, 150, 255, 255, 255, 150, 255, 255, 255, 150, 255, 255, 255, 150, 255 } // HiHat (basic offbeats)
|
||||||
|
},
|
||||||
|
// Y = 1 (Standard)
|
||||||
|
{
|
||||||
|
{ 10, 255, 200, 255, 10, 255, 200, 255, 10, 255, 200, 255, 10, 255, 200, 255 }, // Kick
|
||||||
|
{ 255, 255, 255, 30, 255, 255, 30, 200, 255, 255, 255, 30, 255, 255, 30, 200 }, // Snare (Strong Tresillo + syncopation)
|
||||||
|
{ 100, 200, 50, 200, 100, 200, 50, 200, 100, 200, 50, 200, 100, 200, 50, 200 } // HiHat (8th note swing)
|
||||||
|
},
|
||||||
|
// Y = 2 (Syncopated Tresillo)
|
||||||
|
{
|
||||||
|
{ 10, 255, 150, 200, 10, 255, 150, 200, 10, 255, 150, 200, 10, 255, 150, 200 }, // Kick (Driving but syncopated)
|
||||||
|
{ 255, 200, 255, 20, 200, 255, 20, 150, 255, 200, 255, 20, 200, 255, 20, 150 }, // Snare (Pushing the backbeat)
|
||||||
|
{ 50, 150, 100, 150, 50, 150, 100, 150, 50, 150, 100, 150, 50, 150, 100, 150 } // HiHat (Syncopated ride)
|
||||||
|
},
|
||||||
|
// Y = 3 (Busy / Rolls)
|
||||||
|
{
|
||||||
|
{ 5, 200, 150, 120, 5, 200, 150, 120, 5, 200, 150, 120, 5, 200, 150, 120 }, // Kick (driving)
|
||||||
|
{ 255, 180, 200, 20, 180, 200, 20, 120, 255, 180, 200, 20, 180, 200, 20, 80 }, // Snare (Dem Bow rolls)
|
||||||
|
{ 30, 80, 40, 80, 30, 80, 40, 80, 30, 80, 40, 80, 30, 80, 40, 80 } // HiHat (16ths riding on the tresillo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,4 +1,4 @@
|
|||||||
version=2.0.1beta1
|
version=2.0.1
|
||||||
author=Adam Wonak
|
author=Adam Wonak
|
||||||
maintainer=awonak <github.com/awonak>
|
maintainer=awonak <github.com/awonak>
|
||||||
sentence=Hardware abstraction library for Sitka Instruments Gravity eurorack module
|
sentence=Hardware abstraction library for Sitka Instruments Gravity eurorack module
|
||||||
|
|||||||
@ -42,7 +42,8 @@ public:
|
|||||||
old_read_ = read_;
|
old_read_ = read_;
|
||||||
int raw = analogRead(pin_);
|
int raw = analogRead(pin_);
|
||||||
read_ = map(raw, 0, MAX_INPUT, low_, high_);
|
read_ = map(raw, 0, MAX_INPUT, low_, high_);
|
||||||
read_ = constrain(read_ - offset_, -512, 512);
|
// Cast to long to avoid AVR 16-bit integer overflow prior to constraining
|
||||||
|
read_ = constrain((long)read_ - (long)offset_, -512, 512);
|
||||||
if (inverted_)
|
if (inverted_)
|
||||||
read_ = -read_;
|
read_ = -read_;
|
||||||
}
|
}
|
||||||
@ -53,8 +54,20 @@ public:
|
|||||||
|
|
||||||
void AdjustCalibrationHigh(int amount) { high_ += amount; }
|
void AdjustCalibrationHigh(int amount) { high_ += amount; }
|
||||||
|
|
||||||
|
void SetCalibrationLow(int low) { low_ = low; }
|
||||||
|
|
||||||
|
void SetCalibrationHigh(int high) { high_ = high; }
|
||||||
|
|
||||||
|
int GetCalibrationLow() const { return low_; }
|
||||||
|
|
||||||
|
int GetCalibrationHigh() const { return high_; }
|
||||||
|
|
||||||
void SetOffset(float percent) { offset_ = -(percent) * 512; }
|
void SetOffset(float percent) { offset_ = -(percent) * 512; }
|
||||||
|
|
||||||
|
void AdjustOffset(int amount) { offset_ += amount; }
|
||||||
|
|
||||||
|
int GetOffset() const { return offset_; }
|
||||||
|
|
||||||
void SetAttenuation(float percent) {
|
void SetAttenuation(float percent) {
|
||||||
low_ = abs(percent) * CALIBRATED_LOW;
|
low_ = abs(percent) * CALIBRATED_LOW;
|
||||||
high_ = abs(percent) * CALIBRATED_HIGH;
|
high_ = abs(percent) * CALIBRATED_HIGH;
|
||||||
|
|||||||
Reference in New Issue
Block a user