Compare commits
No commits in common. "master" and "update-encoder-logic" have entirely different histories.
master
...
update-enc
17 changed files with 788 additions and 1867 deletions
|
|
@ -16,10 +16,7 @@ include_directories(include)
|
|||
|
||||
add_executable(clock
|
||||
src/main.cpp
|
||||
src/Output.cpp
|
||||
src/Gate.cpp
|
||||
src/Mod.cpp
|
||||
src/Settings.cpp
|
||||
src/DisplayHandler.cpp
|
||||
src/EncoderHandler.cpp
|
||||
)
|
||||
|
|
@ -37,8 +34,6 @@ pico_generate_pio_header(clock ${CMAKE_CURRENT_LIST_DIR}/quadrature_encoder.pio)
|
|||
# Pull in standard library and hardware abstraction
|
||||
target_link_libraries(clock
|
||||
pico_stdlib
|
||||
hardware_adc
|
||||
hardware_pwm
|
||||
hardware_gpio
|
||||
hardware_pio
|
||||
hardware_i2c
|
||||
|
|
|
|||
|
|
@ -13,50 +13,10 @@ class DisplayHandler {
|
|||
char buffer[32];
|
||||
uint8_t currentScreen;
|
||||
std::string screens[7];
|
||||
std::array<std::string, 19> out_pages = {
|
||||
"Exit",
|
||||
"Mod",
|
||||
"Shape",
|
||||
"Level",
|
||||
"Width",
|
||||
"Swing",
|
||||
"Prob",
|
||||
"Sticky",
|
||||
"CV1 ON",
|
||||
"CV1 SRC",
|
||||
"CV1 TO",
|
||||
"CV1 AMT",
|
||||
"CV1 INV",
|
||||
"CV2 ON",
|
||||
"CV2 SRC",
|
||||
"CV2 TO",
|
||||
"CV2 AMT",
|
||||
"CV2 INV",
|
||||
"Mute"
|
||||
};
|
||||
|
||||
bool onOutScreen = false;
|
||||
bool onMainScreen = true;
|
||||
bool onSettingsScreen = false;
|
||||
bool engageBPM = false;
|
||||
|
||||
std::array<std::string, 6> settings = {
|
||||
"Save",
|
||||
"Load",
|
||||
"Reset",
|
||||
"PPQN",
|
||||
"Run",
|
||||
"Exit"
|
||||
};
|
||||
|
||||
std::array<std::string, 4> out_pages = {"Exit", "Mod", "Width", "Prob"};
|
||||
bool onOutScreen = 0;
|
||||
void renderMainPage();
|
||||
void renderOutPage();
|
||||
void renderSettingsPage();
|
||||
void handleCursorOnOutputScreen(bool dir);
|
||||
void handleClickOnOutputScreen();
|
||||
void handleCursorOnMainScreen(bool dir);
|
||||
void handleClickOnMainScreen();
|
||||
void handleClickOnSettingsScreen();
|
||||
|
||||
|
||||
public:
|
||||
|
|
@ -69,11 +29,8 @@ class DisplayHandler {
|
|||
int8_t currentOut;
|
||||
bool cursorClick;
|
||||
void setup();
|
||||
uint8_t modifyParameter(uint8_t dir, int8_t target, uint8_t clampMax, uint8_t clampMin = 0, bool overflow = true);
|
||||
void render();
|
||||
void handleClick();
|
||||
void exitSettingsScreen();
|
||||
void flashMessage(std::string msg);
|
||||
void moveCursor(bool dir = 1);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,28 +2,30 @@
|
|||
#ifndef EncoderHandler_h
|
||||
#define EncoderHandler_h
|
||||
|
||||
#include "DisplayHandler.h"
|
||||
#include "pico/multicore.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include "pico/multicore.h"
|
||||
#include "DisplayHandler.h"
|
||||
|
||||
|
||||
class EncoderHandler {
|
||||
private:
|
||||
uint sm;
|
||||
uint last_count;
|
||||
private:
|
||||
uint sm;
|
||||
uint last_count;
|
||||
|
||||
public:
|
||||
EncoderHandler(DisplayHandler *display_handler);
|
||||
|
||||
DisplayHandler *display_handler;
|
||||
uint8_t encoder_pos;
|
||||
bool button_pressed;
|
||||
uint16_t clk_last_state;
|
||||
public:
|
||||
EncoderHandler(DisplayHandler* display_handler);
|
||||
|
||||
void setup();
|
||||
// static void gpio_callback(uint gpio, uint32_t events);
|
||||
void moveCursor(bool dir = 1);
|
||||
void update();
|
||||
DisplayHandler* display_handler;
|
||||
uint8_t encoder_pos;
|
||||
bool button_pressed;
|
||||
uint16_t clk_last_state;
|
||||
|
||||
void setup();
|
||||
static void gpio_callback(uint gpio, uint32_t events);
|
||||
void moveCursor(bool dir = 1);
|
||||
void update();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -2,85 +2,33 @@
|
|||
#ifndef Gate_h
|
||||
#define Gate_h
|
||||
|
||||
#include "Output.h"
|
||||
#include "Settings.h"
|
||||
#include "globals.h"
|
||||
#include <cstdint>
|
||||
|
||||
struct GateSettings {
|
||||
uint32_t dur;
|
||||
uint32_t len;
|
||||
uint16_t modifier;
|
||||
|
||||
int8_t modifierSelectionIndex;
|
||||
uint8_t divideMode;
|
||||
uint8_t width;
|
||||
uint8_t p;
|
||||
uint8_t level;
|
||||
uint8_t shape;
|
||||
};
|
||||
class Gate {
|
||||
private:
|
||||
bool state;
|
||||
uint32_t dur;
|
||||
uint32_t len;
|
||||
uint32_t lastTriggerTick = 0xFFFFFFFF;
|
||||
|
||||
class Gate : public Output {
|
||||
private:
|
||||
float currentRandomVal;
|
||||
uint32_t len;
|
||||
uint64_t startTimeUs;
|
||||
uint32_t pulseDurationUs;
|
||||
public:
|
||||
Gate(uint8_t pin);
|
||||
uint8_t pin;
|
||||
uint8_t editing;
|
||||
int8_t modifierSelectionIndex;
|
||||
uint8_t divideMode;
|
||||
uint16_t modifier;
|
||||
uint16_t tickInterval;
|
||||
bool isEnabled;
|
||||
uint8_t width;
|
||||
uint8_t p;
|
||||
|
||||
public:
|
||||
uint32_t dur;
|
||||
Gate(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2);
|
||||
|
||||
uint8_t modDest1;
|
||||
uint8_t modDest2;
|
||||
|
||||
uint8_t level;
|
||||
float levelMod = 0.0f;
|
||||
|
||||
uint8_t width;
|
||||
float widthMod = 0.0f;
|
||||
|
||||
uint8_t p;
|
||||
float pMod = 0.0f;
|
||||
|
||||
uint8_t swing = 50;
|
||||
float swingMod = 0.0f;
|
||||
|
||||
void resetMods() {
|
||||
levelMod = 0.0f;
|
||||
pMod = 0.0f;
|
||||
widthMod = 0.0f;
|
||||
swingMod = 0.0f;
|
||||
}
|
||||
|
||||
float lastOutVal = 0.0f;
|
||||
uint32_t triggerCount;
|
||||
uint32_t scheduledTick;
|
||||
uint32_t lastTriggerTick = 0xFFFFFFFF;
|
||||
|
||||
WaveShape shape = SQUARE;
|
||||
uint32_t startTick = 0;
|
||||
uint32_t stopTick = 0;
|
||||
uint32_t pulseWidthTicks = 0;
|
||||
bool sticky = false;
|
||||
int8_t modifierSelectionIndex;
|
||||
uint8_t divideMode;
|
||||
uint16_t modifier;
|
||||
uint16_t tickInterval;
|
||||
|
||||
void turnOn() override;
|
||||
void update();
|
||||
void turnOff() override;
|
||||
|
||||
void calculatePulseWidth();
|
||||
void setupPatches();
|
||||
void writeAnalog(uint16_t val);
|
||||
void setLen(uint32_t currentPeriod);
|
||||
void setDiv(uint8_t modifier_selection_index);
|
||||
void setWidth(uint16_t newWidth);
|
||||
|
||||
void pack(OutputConfig &cfg);
|
||||
void unpack(const OutputConfig &cfg);
|
||||
void turnOn();
|
||||
void turnOff();
|
||||
void setLen(uint32_t currentPeriod);
|
||||
void setDiv(uint8_t modifier_selection_index);
|
||||
void setWidth(uint16_t newWidth);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
// Mod.h
|
||||
#ifndef Mod_h
|
||||
#define Mod_h
|
||||
#include <cstdint>
|
||||
|
||||
class Gate;
|
||||
|
||||
enum ModDest {
|
||||
DEST_LEVEL = 0,
|
||||
DEST_PROBABILITY = 1,
|
||||
DEST_WIDTH = 2,
|
||||
DEST_COUNT = 3
|
||||
};
|
||||
|
||||
struct ModSlot {
|
||||
uint8_t sourceIdx;
|
||||
uint8_t destIdx;
|
||||
uint8_t destParam;
|
||||
uint8_t amount;
|
||||
uint8_t active;
|
||||
uint8_t inverted;
|
||||
uint8_t reserved1;
|
||||
uint8_t reserved2;
|
||||
};
|
||||
|
||||
class ModMatrix {
|
||||
public:
|
||||
ModSlot slots[16];
|
||||
|
||||
void patch(uint8_t slotIdx, uint8_t src, uint8_t dest, ModDest param,
|
||||
float amt, bool active);
|
||||
|
||||
void process(Gate **gates, uint8_t gateCount);
|
||||
|
||||
void clearPatch(uint8_t slotIdx);
|
||||
;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
// Output.h
|
||||
#ifndef Output_h
|
||||
#define Output_h
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class Output {
|
||||
private:
|
||||
public:
|
||||
Output(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2);
|
||||
bool state;
|
||||
uint8_t pin;
|
||||
uint8_t idx;
|
||||
uint8_t slotIdx1;
|
||||
uint8_t slotIdx2;
|
||||
uint8_t editing;
|
||||
bool isEnabled;
|
||||
|
||||
virtual ~Output() {};
|
||||
virtual void turnOn();
|
||||
virtual void turnOff();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// Settings.h
|
||||
#ifndef Settings_h
|
||||
#define Settings_h
|
||||
|
||||
#include "Mod.h"
|
||||
#include <cstdint>
|
||||
|
||||
#define MAX_OUTPUTS 8
|
||||
#define DATA_PER_OUTPUT 64
|
||||
|
||||
enum OutputType : uint8_t { TYPE_GATE };
|
||||
|
||||
struct OutputConfig {
|
||||
uint8_t type;
|
||||
uint8_t _padding[3];
|
||||
alignas(4) uint8_t data[DATA_PER_OUTPUT];
|
||||
};
|
||||
|
||||
struct DeviceSettings {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
OutputConfig configs[MAX_OUTPUTS];
|
||||
ModSlot slots[16];
|
||||
bool play;
|
||||
uint8_t bpm;
|
||||
bool run;
|
||||
uint8_t ppqnidx;
|
||||
};
|
||||
|
||||
void save();
|
||||
bool load();
|
||||
void load_default();
|
||||
|
||||
extern DeviceSettings globalSettings;
|
||||
|
||||
#endif
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
#define GLOBALS_H
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
#include "Mod.h"
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
|
@ -18,15 +17,6 @@ static constexpr uint8_t OUT_6_PIN = 10;
|
|||
static constexpr uint8_t OUT_7_PIN = 12;
|
||||
static constexpr uint8_t OUT_8_PIN = 14;
|
||||
|
||||
static constexpr uint8_t IN_CLK_PIN = 16;
|
||||
static constexpr uint8_t IN_RUN_PIN = 17;
|
||||
|
||||
static constexpr uint8_t IN_CV_1_ADC_PIN = 0;
|
||||
static constexpr uint8_t IN_CV_2_ADC_PIN = 1;
|
||||
|
||||
extern volatile float cv_values[2];
|
||||
extern const float conversion_factor;
|
||||
|
||||
static constexpr uint8_t SCREEN_SCL_PIN = 18;
|
||||
static constexpr uint8_t SCREEN_SDA_PIN = 19;
|
||||
|
||||
|
|
@ -37,53 +27,12 @@ static constexpr uint8_t ENCODER_CLK_PIN = 20;
|
|||
static constexpr uint8_t ENCODER_DT_PIN = 21;
|
||||
static constexpr uint8_t ENCODER_SW_PIN = 22;
|
||||
|
||||
void gpio_callback(uint gpio, uint32_t events);
|
||||
|
||||
// TIME BASED
|
||||
extern volatile bool PLAY;
|
||||
extern volatile float BPM;
|
||||
extern volatile uint8_t BPM;
|
||||
static constexpr uint32_t MINUTE_US = 60000000;
|
||||
static constexpr uint8_t PPQN = 96;
|
||||
extern volatile uint32_t MASTER_TICK;
|
||||
extern volatile float filteredBPM;
|
||||
extern volatile uint64_t last_external_pulse_us;
|
||||
extern const uint64_t CLOCK_TIMEOUT_US;
|
||||
|
||||
extern volatile bool RUN;
|
||||
|
||||
extern volatile uint8_t EXTPPQNIdx;
|
||||
#define PPQN_OPTS_LEN 5
|
||||
extern const uint16_t PPQNOPTS[PPQN_OPTS_LEN];
|
||||
extern volatile uint64_t last_clk_us;
|
||||
extern volatile uint64_t last_valid_clk_us;
|
||||
extern volatile bool EXTERNAL_CLOCK;
|
||||
|
||||
#define AVG_SAMPLES 12
|
||||
extern uint64_t pulse_intervals[AVG_SAMPLES];
|
||||
extern uint8_t pulse_idx;
|
||||
|
||||
enum WaveShape { SQUARE, TRIANGLE, SAW, RAMP, EXP, HALFSINE, REXP, LOG, SINE, BOUNCE, SIGMO, WOBBLE, STEPDW, STEPUP, SH, SHAPE_COUNT};
|
||||
|
||||
inline const char* waveShapeToString(WaveShape shape) {
|
||||
switch (shape) {
|
||||
case SQUARE: return "SQR";
|
||||
case SINE: return "SINE";
|
||||
case TRIANGLE: return "TRI";
|
||||
case SAW: return "SAW";
|
||||
case EXP: return "EXP";
|
||||
case REXP: return "REXP";
|
||||
case RAMP: return "RAMP";
|
||||
case LOG: return "LOG";
|
||||
case BOUNCE: return "BNC";
|
||||
case WOBBLE: return "WOB";
|
||||
case SIGMO: return "SIG";
|
||||
case STEPDW: return "STPD";
|
||||
case STEPUP: return "STPU";
|
||||
case SH: return "S&H";
|
||||
case HALFSINE: return "HUMP";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
// Modifiers in UI order
|
||||
static std::array<uint8_t, 17> MODIFIERS = {32, 16, 12, 8, 6, 4, 3, 2, 0, 1, 2, 3, 4, 6, 8, 16, 32};
|
||||
|
|
@ -93,17 +42,6 @@ static std::array<uint8_t, 17> MOD_TYPES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
|||
static std::array<std::string, 17> MODIFIER_STRINGS = {"x32", "x16", "x12", "x8", "x6", "x4", "x3", "x2", "x0", "/1", "/2", "/3", "/4", "/6", "/8", "/16", "/32"};
|
||||
|
||||
|
||||
extern ModMatrix matrix;
|
||||
|
||||
inline const char* modDestToString(ModDest modType) {
|
||||
switch (modType) {
|
||||
case DEST_LEVEL: return "LVL";
|
||||
case DEST_PROBABILITY: return "PROB";
|
||||
case DEST_WIDTH: return "WIDTH";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t millis() {
|
||||
return to_ms_since_boot(get_absolute_time());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,153 +0,0 @@
|
|||
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36" version="29.5.2">
|
||||
<diagram name="Page-1" id="-HQHJa_EQfKjo97XYqHl">
|
||||
<mxGraphModel dx="2038" dy="736" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="kG_PlTG_f92uMrFnHzwW-1" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="<font face="Lucida Console">&nbsp; BPM: 120&nbsp;&nbsp;</font><div><font face="Lucida Console">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;*<br></font><div><font face="Lucida Console">&nbsp; 1&nbsp; &nbsp; 2&nbsp; &nbsp; 3&nbsp; &nbsp; 4&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</font></div><div><font face="Lucida Console">&nbsp; 5&nbsp; &nbsp; 6&nbsp; &nbsp; 7&nbsp; &nbsp; 8&nbsp;</font></div></div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="180" y="170" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-16" edge="1" parent="1" source="kG_PlTG_f92uMrFnHzwW-3" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" target="7CsQrmOhY3FsypGgTNEZ-15" value="">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="kG_PlTG_f92uMrFnHzwW-3" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: Division<div><br></div><div>&nbsp; /4<span style="background-color: transparent; color: light-dark(rgb(255, 255, 255), rgb(18, 18, 18));">&nbsp;</span></div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="330" y="170" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="kG_PlTG_f92uMrFnHzwW-4" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: Width<div><br></div><div>&nbsp; 50%</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="330" y="260" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="kG_PlTG_f92uMrFnHzwW-5" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: Shape<div><br></div><div>&nbsp; Pulse</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="330" y="350" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="kG_PlTG_f92uMrFnHzwW-6" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: Level<div><br></div><div>&nbsp; 90%</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="330" y="440" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="kG_PlTG_f92uMrFnHzwW-7" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: Probability<div><br></div><div>&nbsp; 100%</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="330" y="530" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="kG_PlTG_f92uMrFnHzwW-10" parent="1" style="triangle;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="10" width="10" x="280" y="178" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-11" parent="1" style="pointerEvents=1;verticalLabelPosition=bottom;shadow=0;dashed=0;align=center;html=1;verticalAlign=top;shape=mxgraph.electrical.waveforms.sine_wave;" value="" vertex="1">
|
||||
<mxGeometry height="6.18" width="10" x="280" y="210" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-12" connectable="0" parent="1" style="group" value="" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="-120" y="170" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-1" parent="7CsQrmOhY3FsypGgTNEZ-12" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="<font size="1">&nbsp; 1</font>" vertex="1">
|
||||
<mxGeometry height="70" width="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-2" parent="7CsQrmOhY3FsypGgTNEZ-12" style="pointerEvents=1;verticalLabelPosition=bottom;shadow=0;dashed=0;align=center;html=1;verticalAlign=top;shape=mxgraph.electrical.waveforms.sine_wave;" value="" vertex="1">
|
||||
<mxGeometry height="40" width="100" x="10" y="15" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-3" parent="7CsQrmOhY3FsypGgTNEZ-12" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;" value="" vertex="1">
|
||||
<mxGeometry height="10" width="10" x="20" y="55" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-4" parent="7CsQrmOhY3FsypGgTNEZ-12" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="10" width="10" x="30" y="55" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-5" parent="7CsQrmOhY3FsypGgTNEZ-12" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;" value="" vertex="1">
|
||||
<mxGeometry height="10" width="10" x="40" y="55" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-6" parent="7CsQrmOhY3FsypGgTNEZ-12" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;" value="" vertex="1">
|
||||
<mxGeometry height="10" width="10" x="50" y="55" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-7" parent="7CsQrmOhY3FsypGgTNEZ-12" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;" value="" vertex="1">
|
||||
<mxGeometry height="10" width="10" x="60" y="55" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-8" parent="7CsQrmOhY3FsypGgTNEZ-12" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="10" width="10" x="70" y="55" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-9" parent="7CsQrmOhY3FsypGgTNEZ-12" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;" value="" vertex="1">
|
||||
<mxGeometry height="10" width="10" x="80" y="55" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-10" parent="7CsQrmOhY3FsypGgTNEZ-12" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
|
||||
<mxGeometry height="10" width="10" x="90" y="55" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-13" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="scope mode,&nbsp;<div>long press to main<div>scroll thru channels</div><div>squares at bottom light up when signal is high</div></div>" vertex="1">
|
||||
<mxGeometry height="30" width="120" x="-120" y="280" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-18" edge="1" parent="1" source="7CsQrmOhY3FsypGgTNEZ-15" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" target="7CsQrmOhY3FsypGgTNEZ-17" value="">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-15" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="long press anywhere to change output mode" vertex="1">
|
||||
<mxGeometry height="30" width="120" x="480" y="190" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-20" edge="1" parent="1" source="7CsQrmOhY3FsypGgTNEZ-17" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="540" y="360" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-17" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: MODE<div><br></div><div>GATE, LFO, STRANGE</div><div>TURING, ETC</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="480" y="260" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-19" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="On Select, go back to settings, they will change based on mode" vertex="1">
|
||||
<mxGeometry height="30" width="150" x="470" y="370" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-21" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="GATE MODE" vertex="1">
|
||||
<mxGeometry height="30" width="120" x="330" y="130" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-22" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="HOME PAGE" vertex="1">
|
||||
<mxGeometry height="30" width="120" x="180" y="130" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-23" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="SCOPE" vertex="1">
|
||||
<mxGeometry height="30" width="120" x="-120" y="130" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-24" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="MODE SELECT" vertex="1">
|
||||
<mxGeometry height="30" width="120" x="480" y="130" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-25" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="STRANGE ATTRACTOR" vertex="1">
|
||||
<mxGeometry height="30" width="120" x="630" y="130" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-26" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: TYPE<div><br></div><div>&nbsp; LORENZ</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="630" y="170" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-27" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: SPEED<div><br></div><div>&nbsp; SLOW</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="630" y="260" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-28" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: OUTPUT<div><br></div><div>&nbsp; X, Y, Z</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="630" y="350" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-29" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: RAND TARGET<div><br></div><div>&nbsp; X, Y, Z</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="630" y="440" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-30" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: RAND AMT<div><br></div><div>&nbsp; 100%</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="630" y="530" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-43" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="SETTINGS" vertex="1">
|
||||
<mxGeometry height="30" width="120" x="30" y="130" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-44" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; SAVE/LOAD<div><br></div><div>&nbsp; SAVE</div><div><div><br></div><div>&nbsp;&nbsp;</div></div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="30" y="170" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-46" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; PPQN<div><br></div><div>&nbsp; 24</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="30" y="260" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-47" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; RESET<div><br></div><div>&nbsp; YES</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="30" y="350" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-48" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; EXIT<div><br></div><div>&nbsp; &lt;--</div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="30" y="440" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-49" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: CV SOURCE<div><br><div>&nbsp; EXT 1, EXT 2, INT 1</div></div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="330" y="620" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-50" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: CV TARGET<div><br><div>&nbsp; Width</div></div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="330" y="710" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-51" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: CV LEVEL<div><br><div>&nbsp; 100%</div></div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="330" y="800" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-52" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: CV SOURCE<div><br><div>&nbsp; EXT 1, EXT 2, INT 1</div></div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="630" y="620" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-53" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: CV TARGET<div><br><div>&nbsp; Width</div></div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="630" y="710" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="7CsQrmOhY3FsypGgTNEZ-54" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;align=left;verticalAlign=top;" value="&nbsp; 1: CV LEVEL<div><br><div>&nbsp; 100%</div></div>" vertex="1">
|
||||
<mxGeometry height="70" width="120" x="630" y="800" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
263
master_clock.ino
Normal file
263
master_clock.ino
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <U8g2lib.h>
|
||||
#include <TimerOne.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "Out.h"
|
||||
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
#define OLED_RESET -1
|
||||
#define SCREEN_ADDRESS 0x3C
|
||||
#define CLK 2
|
||||
#define DT 3
|
||||
|
||||
|
||||
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
|
||||
|
||||
byte play = 1;
|
||||
byte BPM = 100;
|
||||
|
||||
unsigned long minute = 60000000L;
|
||||
byte ppqn = 96;
|
||||
unsigned long period = (minute / BPM) / ppqn;
|
||||
|
||||
volatile boolean beatToggle = false;
|
||||
|
||||
Out out1(4);
|
||||
Out out2(5);
|
||||
Out out3(6);
|
||||
Out out4(7);
|
||||
Out out5(8);
|
||||
Out out6(9);
|
||||
Out out7(10);
|
||||
Out out8(11);
|
||||
|
||||
byte btnPin = 13;
|
||||
byte btnState = 0;
|
||||
|
||||
volatile long rPotPos = 0;
|
||||
unsigned long rPotLastUpdate = 0;
|
||||
const unsigned long rPotThrottle = 50;
|
||||
|
||||
unsigned long screenLastUpdate = 0;
|
||||
const unsigned long screenThrottle = 200;
|
||||
byte forceScreenUpdate = 1;
|
||||
|
||||
byte currentScreen = 0;
|
||||
byte editMode = 0;
|
||||
int mainScreenCursorPos = 0;
|
||||
byte maxMainScreenPos = 8;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
|
||||
pinMode(btnPin, INPUT_PULLUP);
|
||||
|
||||
// rotary encoder set up
|
||||
pinMode(CLK, INPUT_PULLUP);
|
||||
pinMode(DT, INPUT_PULLUP);
|
||||
attachInterrupt(digitalPinToInterrupt(CLK), updateEncoder, FALLING);
|
||||
|
||||
// screen setup
|
||||
u8g2.begin();
|
||||
u8g2.setFontMode(1);
|
||||
|
||||
// display.clearDisplay();
|
||||
// display.setTextSize(2);
|
||||
// display.setTextColor(WHITE);
|
||||
// display.setCursor(0, 0);
|
||||
//
|
||||
// String bpmString = "BPM: ";
|
||||
// String bpmCalculatedString = bpmString + BPM;
|
||||
// display.println(bpmCalculatedString);
|
||||
//
|
||||
// display.display();
|
||||
|
||||
// init timer
|
||||
Timer1.initialize(1000000);
|
||||
Timer1.attachInterrupt(clock);
|
||||
Timer1.setPeriod(period);
|
||||
|
||||
// init outputs
|
||||
out1.setLen(period);
|
||||
out2.setLen(period);
|
||||
out3.setLen(period);
|
||||
out4.setLen(period);
|
||||
out5.setLen(period);
|
||||
out6.setLen(period);
|
||||
out7.setLen(period);
|
||||
out8.setLen(period);
|
||||
|
||||
// manual setup
|
||||
out1.setDiv(8, 0);
|
||||
out1.setWidth(50);
|
||||
|
||||
out2.setDiv(4, 0);
|
||||
out2.setWidth(50);
|
||||
|
||||
out3.setDiv(2, 0);
|
||||
out3.setWidth(50);
|
||||
|
||||
out4.setDiv(1);
|
||||
out4.setWidth(50);
|
||||
|
||||
out5.setDiv(2);
|
||||
out5.setWidth(50);
|
||||
|
||||
out6.setDiv(4);
|
||||
out6.setWidth(50);
|
||||
|
||||
out7.setDiv(8);
|
||||
out7.setWidth(50);
|
||||
|
||||
out8.setDiv(16);
|
||||
out8.setWidth(50);
|
||||
}
|
||||
|
||||
void clock(void) {
|
||||
if (play == 1) {
|
||||
beatToggle = true;
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (beatToggle) {
|
||||
out1.turnOn();
|
||||
out2.turnOn();
|
||||
out3.turnOn();
|
||||
out4.turnOn();
|
||||
out5.turnOn();
|
||||
out6.turnOn();
|
||||
out7.turnOn();
|
||||
out8.turnOn();
|
||||
|
||||
beatToggle = false;
|
||||
}
|
||||
|
||||
out1.turnOff();
|
||||
out2.turnOff();
|
||||
out3.turnOff();
|
||||
out4.turnOff();
|
||||
out5.turnOff();
|
||||
out6.turnOff();
|
||||
out7.turnOff();
|
||||
out8.turnOff();
|
||||
|
||||
checkBtn();
|
||||
checkRPot();
|
||||
updateScreen();
|
||||
}
|
||||
|
||||
void updateScreen() {
|
||||
unsigned long currentTime = millis();
|
||||
if (currentTime - screenLastUpdate >= screenThrottle && forceScreenUpdate == 1) {
|
||||
screenLastUpdate = currentTime;
|
||||
forceScreenUpdate = 0;
|
||||
u8g2.firstPage();
|
||||
do {
|
||||
// 12px - nonbold
|
||||
if (mainScreenCursorPos == 0) {
|
||||
u8g2.setFont(u8g2_font_7x14B_mf);
|
||||
} else {
|
||||
u8g2.setFont(u8g2_font_7x14_mf);
|
||||
}
|
||||
|
||||
u8g2.setCursor(0, 15);
|
||||
u8g2.print(F("BPM: "));
|
||||
u8g2.print(BPM);
|
||||
|
||||
u8g2.setFont(u8g2_font_7x14_mf);
|
||||
u8g2.setCursor(0, 45);
|
||||
|
||||
char buffer[5];
|
||||
for (byte i = 1; i < 9; i++) {
|
||||
if (i == 5) {
|
||||
u8g2.setCursor(0, 60);
|
||||
}
|
||||
|
||||
if (mainScreenCursorPos == i) {
|
||||
u8g2.setFont(u8g2_font_7x14B_mf);
|
||||
}
|
||||
|
||||
sprintf(buffer, "[%d] ", i);
|
||||
u8g2.print(buffer);
|
||||
|
||||
if (mainScreenCursorPos == i) {
|
||||
u8g2.setFont(u8g2_font_7x14_mf);
|
||||
}
|
||||
};
|
||||
} while (u8g2.nextPage());
|
||||
}
|
||||
}
|
||||
|
||||
void checkBtn() {
|
||||
byte currentBtnState = digitalRead(btnPin);
|
||||
if (currentBtnState != btnState) {
|
||||
if (currentBtnState == 1) {
|
||||
play = (play == 1) ? 0 : 1;
|
||||
};
|
||||
}
|
||||
btnState = currentBtnState;
|
||||
};
|
||||
|
||||
void checkRPot() {
|
||||
if (millis() - rPotLastUpdate >= rPotThrottle) {
|
||||
|
||||
noInterrupts();
|
||||
long currentPos = rPotPos;
|
||||
interrupts();
|
||||
|
||||
// Print position only if it has changed
|
||||
static long previousPos = -999;
|
||||
if (currentPos != previousPos) {
|
||||
Serial.print("Encoder Position: ");
|
||||
Serial.println(currentPos);
|
||||
|
||||
// on home screen
|
||||
if (currentScreen == 0) {
|
||||
if (editMode == 0) {
|
||||
if (currentPos > previousPos) {
|
||||
mainScreenCursorPos++;
|
||||
if (mainScreenCursorPos > maxMainScreenPos) {
|
||||
mainScreenCursorPos = 0;
|
||||
}
|
||||
} else {
|
||||
mainScreenCursorPos--;
|
||||
if (mainScreenCursorPos < 0) {
|
||||
mainScreenCursorPos = maxMainScreenPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forceScreenUpdate = 1;
|
||||
|
||||
previousPos = currentPos;
|
||||
}
|
||||
|
||||
rPotLastUpdate = millis(); // Reset the timer for throttling the output
|
||||
}
|
||||
}
|
||||
|
||||
void handleBPMChange(byte increase) {
|
||||
if (increase == 1) {
|
||||
BPM++;
|
||||
} else {
|
||||
BPM--;
|
||||
}
|
||||
|
||||
period = (minute / BPM) / ppqn;
|
||||
Timer1.setPeriod(period);
|
||||
}
|
||||
|
||||
void updateEncoder() {
|
||||
// The ISR should be as short and fast as possible
|
||||
// Check the state of the DT pin to determine direction
|
||||
if (digitalRead(3) == LOW) {
|
||||
rPotPos++; // Clockwise
|
||||
} else {
|
||||
rPotPos--; // Counter-clockwise
|
||||
}
|
||||
}
|
||||
|
|
@ -1,588 +1,262 @@
|
|||
// DisplayHandler.cpp
|
||||
#include "DisplayHandler.h"
|
||||
#include "Mod.h"
|
||||
#include "Settings.h"
|
||||
#include "globals.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <pico/time.h>
|
||||
#include "DisplayHandler.h"
|
||||
#include "globals.h"
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "hardware/i2c.h"
|
||||
#include "pico-ssd1306/shapeRenderer/ShapeRenderer.h"
|
||||
#include "pico-ssd1306/ssd1306.h"
|
||||
#include "pico-ssd1306/shapeRenderer/ShapeRenderer.h"
|
||||
#include "pico-ssd1306/textRenderer/TextRenderer.h"
|
||||
#include "textRenderer/12x16_font.h"
|
||||
#include "textRenderer/8x8_font.h"
|
||||
|
||||
pico_ssd1306::SSD1306 *display = nullptr;
|
||||
|
||||
pico_ssd1306::SSD1306* display = nullptr;
|
||||
extern void update_BPM(bool up);
|
||||
|
||||
DisplayHandler::DisplayHandler(Gate *outputs[]) {
|
||||
this->outputs = outputs;
|
||||
currentScreen = 0;
|
||||
currentOut = -1;
|
||||
updateScreen = 1;
|
||||
cursorPosition = 0;
|
||||
mainMaxCursorPosition = 10;
|
||||
outMaxCursorPosition = std::size(out_pages) - 1;
|
||||
cursorClick = 0;
|
||||
|
||||
DisplayHandler::DisplayHandler(Gate* outputs[]) {
|
||||
this->outputs = outputs;
|
||||
currentScreen = 0;
|
||||
currentOut = -1;
|
||||
updateScreen = 1;
|
||||
cursorPosition = 0;
|
||||
mainMaxCursorPosition = 8;
|
||||
outMaxCursorPosition = std::size(out_pages) - 1;
|
||||
cursorClick = 0;
|
||||
}
|
||||
|
||||
|
||||
void DisplayHandler::setup() {
|
||||
i2c_init(i2c1, 400 * 1000);
|
||||
i2c_init(i2c1, 400 * 1000);
|
||||
|
||||
gpio_set_function(SCREEN_SDA_PIN, GPIO_FUNC_I2C);
|
||||
gpio_set_function(SCREEN_SCL_PIN, GPIO_FUNC_I2C);
|
||||
gpio_set_function(SCREEN_SDA_PIN, GPIO_FUNC_I2C);
|
||||
gpio_set_function(SCREEN_SCL_PIN, GPIO_FUNC_I2C);
|
||||
|
||||
gpio_pull_up(SCREEN_SDA_PIN);
|
||||
gpio_pull_up(SCREEN_SCL_PIN);
|
||||
gpio_pull_up(SCREEN_SDA_PIN);
|
||||
gpio_pull_up(SCREEN_SCL_PIN);
|
||||
|
||||
display = new pico_ssd1306::SSD1306(i2c1, 0x3C, pico_ssd1306::Size::W128xH64);
|
||||
display->setOrientation(0);
|
||||
display = new pico_ssd1306::SSD1306(i2c1, 0x3C, pico_ssd1306::Size::W128xH64);
|
||||
display->setOrientation(0);
|
||||
}
|
||||
|
||||
uint8_t DisplayHandler::modifyParameter(uint8_t dir, int8_t target, uint8_t clampMax, uint8_t clampMin, bool overflow) {
|
||||
|
||||
if (dir == 1) {
|
||||
target++;
|
||||
} else {
|
||||
target--;
|
||||
}
|
||||
|
||||
if (target > clampMax) {
|
||||
if (overflow) {
|
||||
target = clampMin;
|
||||
} else {
|
||||
target = clampMax;
|
||||
}
|
||||
}
|
||||
|
||||
if (target < clampMin) {
|
||||
if (overflow) {
|
||||
target = clampMax;
|
||||
} else {
|
||||
target = clampMin;
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
void DisplayHandler::handleCursorOnMainScreen(bool dir) {
|
||||
if (cursorPosition == 0 && cursorClick == 1) { // Engage BPM on Main Screen
|
||||
update_BPM(dir);
|
||||
} else {
|
||||
cursorPosition = modifyParameter(dir, cursorPosition, mainMaxCursorPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayHandler::handleCursorOnOutputScreen(bool dir) {
|
||||
if (cursorClick == 0) { // No click, scroll through options
|
||||
currentScreen = modifyParameter(dir, cursorPosition, outMaxCursorPosition, 0);
|
||||
cursorPosition = currentScreen;
|
||||
} else {
|
||||
if (currentScreen == 1) { // mod screen
|
||||
outputs[currentOut]->editing = 1;
|
||||
|
||||
outputs[currentOut]->modifierSelectionIndex = modifyParameter(dir, outputs[currentOut]->modifierSelectionIndex, std::size(MOD_TYPES) - 1);
|
||||
|
||||
} else if (currentScreen == 2) { // shape control
|
||||
int8_t currentShape = (int8_t)outputs[currentOut]->shape;
|
||||
|
||||
currentShape = modifyParameter(dir, currentShape, SHAPE_COUNT -1, 0);
|
||||
|
||||
outputs[currentOut]->shape = (WaveShape)currentShape;
|
||||
outputs[currentOut]->writeAnalog(0);
|
||||
|
||||
} else if (currentScreen == 3) { // level control
|
||||
outputs[currentOut]->editing = 1;
|
||||
outputs[currentOut]->level = modifyParameter(dir, outputs[currentOut]->level, 100, 1, false);
|
||||
|
||||
} else if (currentScreen == 4) { // width control
|
||||
outputs[currentOut]->editing = 1;
|
||||
outputs[currentOut]->width = modifyParameter(dir, outputs[currentOut]->width, 100, 1, false);
|
||||
outputs[currentOut]->setWidth(outputs[currentOut]->width);
|
||||
|
||||
} else if (currentScreen == 5) { // SWING
|
||||
|
||||
outputs[currentOut]->editing = 1;
|
||||
outputs[currentOut]->swing = modifyParameter(dir, outputs[currentOut]->swing, 75, 50, false);
|
||||
} else if (currentScreen == 6) { // PROBABILITY
|
||||
outputs[currentOut]->editing = 1;
|
||||
outputs[currentOut]->p = modifyParameter(dir, outputs[currentOut]->p, 100, 0, false);
|
||||
} else if (currentScreen == 7) { // STICKY
|
||||
outputs[currentOut]->sticky ^= true;
|
||||
|
||||
} else if (currentScreen == 8) { // CV1 Active
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].active ^= true;
|
||||
|
||||
} else if (currentScreen == 9) { // CV1 Source
|
||||
|
||||
if (dir == 1) {
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx++;
|
||||
if (matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx ==
|
||||
currentOut) {
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx++;
|
||||
}
|
||||
} else {
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx--;
|
||||
if (matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx ==
|
||||
currentOut) {
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx--;
|
||||
}
|
||||
}
|
||||
|
||||
if (matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx > 9) {
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx = 0;
|
||||
}
|
||||
|
||||
if (matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx < 0) {
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx = 9;
|
||||
}
|
||||
|
||||
} else if (currentScreen == 10) { // CV1 DEST
|
||||
int currentDest =
|
||||
(int)matrix.slots[outputs[currentOut]->slotIdx1].destParam;
|
||||
|
||||
if (dir == 1) {
|
||||
currentDest++;
|
||||
} else {
|
||||
currentDest--;
|
||||
}
|
||||
|
||||
if (currentDest >= DEST_COUNT) {
|
||||
currentDest = 0;
|
||||
}
|
||||
|
||||
if (currentDest < 0) {
|
||||
currentDest = DEST_COUNT - 1;
|
||||
}
|
||||
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].destParam = (ModDest)currentDest;
|
||||
|
||||
} else if (currentScreen == 11) { // CV1 AMT
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].amount = modifyParameter(dir, matrix.slots[outputs[currentOut]->slotIdx1].amount, 100, 0, false);
|
||||
|
||||
} else if (currentScreen == 12) { // CV1 INV
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].inverted ^= true;
|
||||
|
||||
} else if (currentScreen == 13) { // CV2 Active
|
||||
matrix.slots[outputs[currentOut]->slotIdx2].active ^= true;
|
||||
|
||||
} else if (currentScreen == 14) { // CV2 Source
|
||||
|
||||
if (dir == 1) {
|
||||
matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx++;
|
||||
|
||||
} else {
|
||||
matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx--;
|
||||
}
|
||||
|
||||
if (matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx > 7) {
|
||||
matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx = 0;
|
||||
}
|
||||
|
||||
if (matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx < 0) {
|
||||
matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx = 7;
|
||||
}
|
||||
|
||||
} else if (currentScreen == 15) { // CV2 DEST
|
||||
int currentDest =
|
||||
(int)matrix.slots[outputs[currentOut]->slotIdx2].destParam;
|
||||
|
||||
if (dir == 1) {
|
||||
currentDest++;
|
||||
} else {
|
||||
currentDest--;
|
||||
}
|
||||
|
||||
if (currentDest >= DEST_COUNT) {
|
||||
currentDest = 0;
|
||||
}
|
||||
|
||||
if (currentDest < 0) {
|
||||
currentDest = DEST_COUNT - 1;
|
||||
}
|
||||
|
||||
matrix.slots[outputs[currentOut]->slotIdx2].destParam =
|
||||
(ModDest)currentDest;
|
||||
|
||||
} else if (currentScreen == 16) { // CV2 AMT
|
||||
matrix.slots[outputs[currentOut]->slotIdx2].amount = modifyParameter(dir, matrix.slots[outputs[currentOut]->slotIdx2].amount, 100, 0, false);
|
||||
|
||||
} else if (currentScreen == 17) { // CV2 INV
|
||||
matrix.slots[outputs[currentOut]->slotIdx2].inverted ^= true;
|
||||
|
||||
} else if (currentScreen == 18) { // MUTE
|
||||
outputs[currentOut]->isEnabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayHandler::moveCursor(bool dir) {
|
||||
if (onOutScreen) {
|
||||
handleCursorOnOutputScreen(dir);
|
||||
}
|
||||
if (onOutScreen) {
|
||||
if (cursorClick == 0) {
|
||||
if (dir == 1) {
|
||||
cursorPosition++;
|
||||
currentScreen++;
|
||||
} else {
|
||||
cursorPosition--;
|
||||
currentScreen--;
|
||||
}
|
||||
|
||||
if (onMainScreen) {
|
||||
handleCursorOnMainScreen(dir);
|
||||
}
|
||||
if (cursorPosition > outMaxCursorPosition) {
|
||||
cursorPosition = 0;
|
||||
currentScreen = 0;
|
||||
}
|
||||
|
||||
if (onSettingsScreen) {
|
||||
cursorPosition = modifyParameter(dir, cursorPosition, 5, 0);
|
||||
}
|
||||
updateScreen = 1;
|
||||
}
|
||||
if (cursorPosition < 0) {
|
||||
cursorPosition = outMaxCursorPosition;
|
||||
currentScreen = outMaxCursorPosition;
|
||||
}
|
||||
} else { // click = 1
|
||||
if (currentScreen == 1) { // mod screen
|
||||
outputs[currentOut]->editing = 1;
|
||||
if (dir == 1) {
|
||||
outputs[currentOut]->modifierSelectionIndex++;
|
||||
} else {
|
||||
outputs[currentOut]->modifierSelectionIndex--;
|
||||
}
|
||||
|
||||
void DisplayHandler::handleClickOnOutputScreen() {
|
||||
if (currentScreen == 0) { // exit screen
|
||||
cursorPosition = currentOut + 1;
|
||||
onMainScreen = true;
|
||||
onOutScreen = false;
|
||||
onSettingsScreen = false;
|
||||
currentOut = -1;
|
||||
currentScreen = 0;
|
||||
cursorClick = false;
|
||||
if (outputs[currentOut]->modifierSelectionIndex < 0) {
|
||||
outputs[currentOut]->modifierSelectionIndex = 0;
|
||||
}
|
||||
|
||||
if (outputs[currentOut]->modifierSelectionIndex > std::size(MOD_TYPES) - 1) {
|
||||
outputs[currentOut]->modifierSelectionIndex = std::size(MOD_TYPES) - 1;
|
||||
}
|
||||
|
||||
} else if (currentScreen == 2) { // width control
|
||||
outputs[currentOut]->editing = 1;
|
||||
if (dir == 1) {
|
||||
outputs[currentOut]->width++;
|
||||
} else {
|
||||
outputs[currentOut]->width--;
|
||||
}
|
||||
|
||||
if (outputs[currentOut]->width > 100) {
|
||||
outputs[currentOut]->width = 100;
|
||||
}
|
||||
|
||||
if (outputs[currentOut]->width < 1) {
|
||||
outputs[currentOut]->width = 1;
|
||||
}
|
||||
|
||||
outputs[currentOut]->setWidth(outputs[currentOut]->width);
|
||||
|
||||
} else if (currentScreen == 3) {
|
||||
|
||||
outputs[currentOut]->editing = 1;
|
||||
if (dir == 1) {
|
||||
outputs[currentOut]->p++;
|
||||
} else {
|
||||
outputs[currentOut]->p--;
|
||||
}
|
||||
|
||||
if (outputs[currentOut]->p > 100) {
|
||||
outputs[currentOut]->p = 100;
|
||||
}
|
||||
|
||||
if (outputs[currentOut]->p < 0) {
|
||||
outputs[currentOut]->p = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (cursorPosition == 0 && cursorClick == 1) { // Engage BPM on Main Screen
|
||||
update_BPM(dir);
|
||||
} else {
|
||||
if (dir == 1) {
|
||||
cursorPosition++;
|
||||
} else {
|
||||
cursorPosition--;
|
||||
}
|
||||
|
||||
if (cursorPosition > mainMaxCursorPosition) {
|
||||
cursorPosition = 0;
|
||||
}
|
||||
|
||||
if (cursorPosition < 0) {
|
||||
cursorPosition = mainMaxCursorPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move this out to the other group
|
||||
if (currentScreen == 1 && outputs[currentOut]->editing == 1) {
|
||||
outputs[currentOut]->setDiv(outputs[currentOut]->modifierSelectionIndex);
|
||||
outputs[currentOut]->editing = 0;
|
||||
cursorClick = false;
|
||||
}
|
||||
updateScreen = 1;
|
||||
}
|
||||
|
||||
|
||||
void DisplayHandler::handleClickOnMainScreen() {
|
||||
if (cursorPosition == 0) {
|
||||
if (engageBPM) {
|
||||
cursorClick = 0;
|
||||
engageBPM = false;
|
||||
} else {
|
||||
engageBPM = true;
|
||||
cursorClick = 1;
|
||||
}
|
||||
} if (cursorPosition > 0 && cursorPosition < 9) { // GO TO OUTPUT
|
||||
currentOut = cursorPosition - 1;
|
||||
cursorPosition = 1;
|
||||
currentScreen = 1;
|
||||
onOutScreen = true;
|
||||
onMainScreen = false;
|
||||
onSettingsScreen = false;
|
||||
cursorClick = 0;
|
||||
} else if (cursorPosition == 9) { // PLAY/PAUSE BUTTON
|
||||
PLAY ^= true;
|
||||
cursorClick = 0;
|
||||
} else if (cursorPosition == 10) { // GLOBAL SETTINGS
|
||||
cursorPosition = 0;
|
||||
onSettingsScreen = true;
|
||||
onMainScreen = false;
|
||||
onOutScreen = false;
|
||||
cursorClick = 0;
|
||||
}
|
||||
}
|
||||
void DisplayHandler::handleClick() {
|
||||
cursorClick ^= true;
|
||||
|
||||
|
||||
void DisplayHandler::handleClickOnSettingsScreen() {
|
||||
if (cursorPosition == 0) { // SAVE
|
||||
flashMessage("Saving...");
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
outputs[i]->pack(globalSettings.configs[i]);
|
||||
}
|
||||
|
||||
save();
|
||||
|
||||
exitSettingsScreen();
|
||||
} else if (cursorPosition == 1) { // LOAD
|
||||
flashMessage("Loading...");
|
||||
PLAY = false;
|
||||
bool loaded_defaults = load();
|
||||
|
||||
if (!loaded_defaults) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
matrix.slots[i] = globalSettings.slots[i];
|
||||
if (onOutScreen) {
|
||||
if (currentScreen == 0) { // exit screen
|
||||
cursorPosition = currentOut + 1;
|
||||
currentOut = -1;
|
||||
currentScreen = 0;
|
||||
onOutScreen = 0;
|
||||
cursorClick = false;
|
||||
}
|
||||
|
||||
BPM = globalSettings.bpm;
|
||||
PLAY = globalSettings.play;
|
||||
RUN = globalSettings.run;
|
||||
EXTPPQNIdx = globalSettings.ppqnidx;
|
||||
if (currentScreen == 1 && outputs[currentOut]->editing == 1) {
|
||||
outputs[currentOut]->setDiv(outputs[currentOut]->modifierSelectionIndex);
|
||||
outputs[currentOut]->editing = 0;
|
||||
cursorClick = false;
|
||||
}
|
||||
} else {
|
||||
|
||||
if (currentScreen == 0) { // on main screen
|
||||
if (cursorPosition == 0) { // Change BPM
|
||||
|
||||
} else if (cursorPosition > 0 && cursorPosition < 9) { // go to out screen
|
||||
currentOut = cursorPosition - 1;
|
||||
cursorPosition = 1;
|
||||
currentScreen = 1;
|
||||
onOutScreen = 1;
|
||||
cursorClick = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
outputs[i]->unpack(globalSettings.configs[i]);
|
||||
}
|
||||
|
||||
exitSettingsScreen();
|
||||
} else if (cursorPosition == 2) { // RESET
|
||||
flashMessage("Resetting...");
|
||||
PLAY = false;
|
||||
load_default();
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
outputs[i]->unpack(globalSettings.configs[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizeof(outputs); i++) {
|
||||
outputs[i]->setupPatches();
|
||||
}
|
||||
|
||||
PLAY = true;
|
||||
exitSettingsScreen();
|
||||
} else if (cursorPosition == 3) { // PPQN
|
||||
EXTPPQNIdx++;
|
||||
|
||||
if (EXTPPQNIdx >= PPQN_OPTS_LEN) {
|
||||
EXTPPQNIdx = 0;
|
||||
}
|
||||
|
||||
} else if (cursorPosition == 4) { // RUN
|
||||
RUN ^= true;
|
||||
|
||||
if (RUN) {
|
||||
PLAY = false;
|
||||
}
|
||||
|
||||
} else if (cursorPosition == 5) { // EXIT
|
||||
exitSettingsScreen();
|
||||
}
|
||||
cursorClick = 0;
|
||||
updateScreen = 1;
|
||||
}
|
||||
|
||||
void DisplayHandler::exitSettingsScreen() {
|
||||
onOutScreen = false;
|
||||
onSettingsScreen = false;
|
||||
onMainScreen = true;
|
||||
|
||||
cursorPosition = 10;
|
||||
cursorClick = 0;
|
||||
currentScreen = 0;
|
||||
}
|
||||
|
||||
void DisplayHandler::handleClick() {
|
||||
cursorClick ^= true;
|
||||
|
||||
if (onOutScreen) {
|
||||
handleClickOnOutputScreen();
|
||||
} else if (onMainScreen) { // on main screen
|
||||
handleClickOnMainScreen();
|
||||
} else if (onSettingsScreen) {
|
||||
handleClickOnSettingsScreen();
|
||||
}
|
||||
|
||||
updateScreen = 1;
|
||||
}
|
||||
|
||||
void DisplayHandler::render() {
|
||||
if (updateScreen) {
|
||||
display->clear();
|
||||
if (updateScreen) {
|
||||
display->clear();
|
||||
|
||||
if (onMainScreen) { // main screen
|
||||
renderMainPage();
|
||||
} else if (onOutScreen) {
|
||||
renderOutPage();
|
||||
} else if (onSettingsScreen) {
|
||||
renderSettingsPage();
|
||||
}
|
||||
if (currentScreen == 0 && currentOut == -1) { // main screen
|
||||
renderMainPage();
|
||||
} else if (currentOut != -1) {
|
||||
renderOutPage();
|
||||
}
|
||||
|
||||
display->sendBuffer();
|
||||
display->sendBuffer();
|
||||
|
||||
updateScreen = 0;
|
||||
}
|
||||
updateScreen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DisplayHandler::renderMainPage() {
|
||||
char bpm_buffer[16];
|
||||
snprintf(bpm_buffer, sizeof(bpm_buffer), "BPM %.1f", BPM);
|
||||
if (cursorPosition == 0) {
|
||||
if (cursorClick == 1) {
|
||||
pico_ssd1306::fillRect(display, 0, 0, 110, 18);
|
||||
pico_ssd1306::drawText(display, font_12x16, bpm_buffer, 1, 2,
|
||||
pico_ssd1306::WriteMode::SUBTRACT);
|
||||
std::string bpm_string = "BPM: " + std::to_string(BPM);
|
||||
|
||||
} else {
|
||||
pico_ssd1306::drawRect(display, 0, 0, 110, 18,
|
||||
pico_ssd1306::WriteMode::ADD);
|
||||
pico_ssd1306::drawText(display, font_12x16, bpm_buffer, 1, 2);
|
||||
}
|
||||
} else {
|
||||
pico_ssd1306::drawText(display, font_12x16, bpm_buffer, 1, 2);
|
||||
}
|
||||
if (cursorPosition == 0) {
|
||||
if (cursorClick == 1) {
|
||||
pico_ssd1306::fillRect(display, 0, 0, 100, 18);
|
||||
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2, pico_ssd1306::WriteMode::SUBTRACT);
|
||||
|
||||
uint8_t cursor_x = 2;
|
||||
uint8_t cursor_y = 25;
|
||||
} else {
|
||||
pico_ssd1306::drawRect(display, 0, 0, 100, 18, pico_ssd1306::WriteMode::ADD);
|
||||
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2);
|
||||
|
||||
for (uint8_t i = 1; i < 9; i++) {
|
||||
if (i == 5) {
|
||||
cursor_x = 2;
|
||||
cursor_y += 20;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2);
|
||||
|
||||
if (cursorPosition == i) {
|
||||
pico_ssd1306::drawRect(display, cursor_x - 2, cursor_y - 4, cursor_x + 14,
|
||||
cursor_y + 16, pico_ssd1306::WriteMode::ADD);
|
||||
}
|
||||
pico_ssd1306::drawText(display, font_12x16, std::to_string(i).c_str(),
|
||||
cursor_x, cursor_y);
|
||||
cursor_x += 30;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursorPosition == 9) { // PLAY BUTTON
|
||||
pico_ssd1306::fillRect(display, 120, 8, 130, 18,
|
||||
pico_ssd1306::WriteMode::ADD);
|
||||
pico_ssd1306::drawText(display, font_8x8, PLAY ? ">" : "#", 120, 10,
|
||||
pico_ssd1306::WriteMode::SUBTRACT);
|
||||
} else {
|
||||
pico_ssd1306::drawText(display, font_8x8, PLAY ? ">" : "#", 120, 10);
|
||||
}
|
||||
uint8_t cursor_x = 2;
|
||||
uint8_t cursor_y = 25;
|
||||
|
||||
if (cursorPosition == 10) { // OPTIONS BUTTON
|
||||
pico_ssd1306::fillRect(display, 120, 28, 130, 38,
|
||||
pico_ssd1306::WriteMode::ADD);
|
||||
pico_ssd1306::drawText(display, font_8x8, "*", 120, 30,
|
||||
pico_ssd1306::WriteMode::SUBTRACT);
|
||||
} else {
|
||||
pico_ssd1306::drawText(display, font_8x8, "*", 120, 30);
|
||||
}
|
||||
for (uint8_t i = 1; i < 9; i++) {
|
||||
if (i == 5) {
|
||||
cursor_x = 2;
|
||||
cursor_y += 20;
|
||||
}
|
||||
|
||||
if (cursorPosition == i) {
|
||||
pico_ssd1306::drawRect(display, cursor_x - 2, cursor_y - 4, cursor_x + 14, cursor_y + 16, pico_ssd1306::WriteMode::ADD);
|
||||
|
||||
}
|
||||
pico_ssd1306::drawText(display, font_12x16, std::to_string(i).c_str(), cursor_x, cursor_y);
|
||||
cursor_x += 30;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DisplayHandler::renderOutPage() {
|
||||
uint8_t visualOut = currentOut + 1;
|
||||
std::string title =
|
||||
std::to_string(visualOut) + "| " + out_pages[currentScreen];
|
||||
pico_ssd1306::drawText(display, font_12x16, title.c_str(), 1, 2);
|
||||
uint8_t visualOut = currentOut + 1;
|
||||
std::string title = std::to_string(visualOut) + "| " + out_pages[currentScreen];
|
||||
pico_ssd1306::drawText(display, font_12x16, title.c_str(), 1, 2);
|
||||
|
||||
std::string param_string;
|
||||
std::string param_string;
|
||||
|
||||
if (currentScreen == 0) { // exit screen
|
||||
param_string = "<--";
|
||||
if (currentScreen == 0) { // exit screen
|
||||
param_string = "<--";
|
||||
|
||||
} else if (currentScreen == 1) { // modifier screen
|
||||
uint8_t modifier_selection_index =
|
||||
outputs[currentOut]->modifierSelectionIndex;
|
||||
param_string = MODIFIER_STRINGS[modifier_selection_index];
|
||||
} else if (currentScreen == 1) { // modifier screen
|
||||
uint8_t modifier_selection_index = outputs[currentOut]->modifierSelectionIndex;
|
||||
param_string = MODIFIER_STRINGS[modifier_selection_index];
|
||||
|
||||
} else if (currentScreen == 2) { // shape screen
|
||||
param_string = waveShapeToString(outputs[currentOut]->shape);
|
||||
} else if (currentScreen == 2) { // Width screen
|
||||
param_string = std::to_string(outputs[currentOut]->width) + "%";
|
||||
|
||||
} else if (currentScreen == 3) { // level screen
|
||||
param_string = std::to_string(outputs[currentOut]->level) + "%";
|
||||
} else if (currentScreen == 3) { // Probability screen
|
||||
param_string = std::to_string(outputs[currentOut]->p) + "%";
|
||||
}
|
||||
|
||||
} else if (currentScreen == 4) { // Width screen
|
||||
param_string = std::to_string(outputs[currentOut]->width) + "%";
|
||||
if (cursorClick) {
|
||||
pico_ssd1306::fillRect(display, 1, 42, 50, 60);
|
||||
pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45, pico_ssd1306::WriteMode::SUBTRACT);
|
||||
} else {
|
||||
pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45);
|
||||
}
|
||||
|
||||
} else if (currentScreen == 5) { // Swing screen
|
||||
param_string = std::to_string(outputs[currentOut]->swing) + "%";
|
||||
|
||||
} else if (currentScreen == 6) { // Probability screen
|
||||
param_string = std::to_string(outputs[currentOut]->p) + "%";
|
||||
|
||||
} else if (currentScreen == 7) { // STICKY Screen
|
||||
param_string = outputs[currentOut]->sticky ? "ON" : "OFF";
|
||||
|
||||
} else if (currentScreen == 8) { // CV1 Active Screen
|
||||
param_string =
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].active ? "ON" : "OFF";
|
||||
|
||||
} else if (currentScreen == 9) { // CV1 Source Chan Screen
|
||||
uint8_t sourceIdx = matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx + 1;
|
||||
|
||||
if (sourceIdx <= 7) {
|
||||
param_string = std::to_string(sourceIdx);
|
||||
} else {
|
||||
param_string = "Ext" + std::to_string(sourceIdx - 7);
|
||||
}
|
||||
|
||||
} else if (currentScreen == 10) { // CV1 DEST Screen
|
||||
param_string = modDestToString(
|
||||
(ModDest)matrix.slots[outputs[currentOut]->slotIdx1].destParam);
|
||||
|
||||
} else if (currentScreen == 11) { // CV1 AMT Screen
|
||||
param_string =
|
||||
std::to_string(
|
||||
(int)matrix.slots[outputs[currentOut]->slotIdx1].amount) +
|
||||
"%";
|
||||
|
||||
} else if (currentScreen == 12) { // CV1 INV Screen
|
||||
param_string =
|
||||
matrix.slots[outputs[currentOut]->slotIdx1].inverted ? "ON" : "OFF";
|
||||
|
||||
} else if (currentScreen == 13) { // CV2 Active Screen
|
||||
param_string =
|
||||
matrix.slots[outputs[currentOut]->slotIdx2].active ? "ON" : "OFF";
|
||||
|
||||
} else if (currentScreen == 14) { // CV2 Source Chan Screen
|
||||
uint8_t sourceIdx = matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx + 1;
|
||||
|
||||
if (sourceIdx <= 7) {
|
||||
param_string = std::to_string(sourceIdx);
|
||||
} else {
|
||||
param_string = "Ext" + std::to_string(sourceIdx - 7);
|
||||
}
|
||||
|
||||
} else if (currentScreen == 15) { // CV2 DEST Screen
|
||||
param_string = modDestToString(
|
||||
(ModDest)matrix.slots[outputs[currentOut]->slotIdx2].destParam);
|
||||
|
||||
} else if (currentScreen == 16) { // CV2 AMT Screen
|
||||
param_string =
|
||||
std::to_string(
|
||||
(int)matrix.slots[outputs[currentOut]->slotIdx2].amount) +
|
||||
"%";
|
||||
|
||||
} else if (currentScreen == 17) { // CV1 INV Screen
|
||||
param_string =
|
||||
matrix.slots[outputs[currentOut]->slotIdx2].inverted ? "ON" : "OFF";
|
||||
|
||||
} else if (currentScreen == 18) { // Mute Screen
|
||||
param_string = outputs[currentOut]->isEnabled ? "ON" : "OFF";
|
||||
}
|
||||
|
||||
if (cursorClick) {
|
||||
pico_ssd1306::fillRect(display, 1, 42, 50, 60);
|
||||
pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45,
|
||||
pico_ssd1306::WriteMode::SUBTRACT);
|
||||
} else {
|
||||
pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayHandler::flashMessage(std::string msg) {
|
||||
|
||||
display->clear();
|
||||
|
||||
pico_ssd1306::drawText(display, font_12x16, msg.c_str(), 1, 1);
|
||||
|
||||
display->sendBuffer();
|
||||
}
|
||||
|
||||
void DisplayHandler::renderSettingsPage() {
|
||||
uint8_t y = 3;
|
||||
for (int i = 0; i < settings.size(); i++) {
|
||||
if (cursorPosition == i) {
|
||||
pico_ssd1306::fillRect(display, 0, y, 50, y + 8);
|
||||
pico_ssd1306::drawText(display, font_8x8, settings[i].c_str(), 1, y,
|
||||
pico_ssd1306::WriteMode::SUBTRACT);
|
||||
|
||||
if (settings[i] == "Run") {
|
||||
std::string param = RUN ? "ON" : "OFF";
|
||||
pico_ssd1306::drawText(display, font_12x16, param.c_str(), 70, 20);
|
||||
}
|
||||
|
||||
if (settings[i] == "PPQN") {
|
||||
std::string param = std::to_string(PPQNOPTS[EXTPPQNIdx]);
|
||||
pico_ssd1306::drawText(display, font_12x16, param.c_str(), 70, 20);
|
||||
}
|
||||
|
||||
} else {
|
||||
pico_ssd1306::drawText(display, font_8x8, settings[i].c_str(), 1, y);
|
||||
}
|
||||
|
||||
y += 10;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,73 @@
|
|||
// EncoderHandler.cpp
|
||||
#include "pico/stdlib.h"
|
||||
#include "EncoderHandler.h"
|
||||
#include "DisplayHandler.h"
|
||||
#include "globals.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include "quadrature_encoder.pio.h"
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include "hardware/pio.h"
|
||||
#include "quadrature_encoder.pio.h"
|
||||
|
||||
static EncoderHandler *self = nullptr;
|
||||
|
||||
EncoderHandler::EncoderHandler(DisplayHandler *display_handler) {
|
||||
this->display_handler = display_handler;
|
||||
self = this;
|
||||
encoder_pos = 0;
|
||||
button_pressed = 0;
|
||||
static EncoderHandler* self = nullptr;
|
||||
|
||||
|
||||
EncoderHandler::EncoderHandler(DisplayHandler* display_handler) {
|
||||
this->display_handler = display_handler;
|
||||
self = this;
|
||||
encoder_pos = 0;
|
||||
button_pressed = 0;
|
||||
}
|
||||
|
||||
|
||||
void EncoderHandler::gpio_callback(uint gpio, uint32_t events) {
|
||||
uint64_t now = to_us_since_boot(get_absolute_time());
|
||||
static uint64_t last_sw_time = 0;
|
||||
|
||||
if (gpio == ENCODER_SW_PIN) {
|
||||
if (now - last_sw_time > 200000) { // 200ms debounce
|
||||
self->display_handler->handleClick();
|
||||
last_sw_time = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EncoderHandler::setup() {
|
||||
self = this;
|
||||
self = this;
|
||||
|
||||
gpio_init(ENCODER_SW_PIN);
|
||||
gpio_set_dir(ENCODER_SW_PIN, GPIO_IN);
|
||||
gpio_pull_up(ENCODER_SW_PIN);
|
||||
gpio_init(ENCODER_SW_PIN);
|
||||
gpio_set_dir(ENCODER_SW_PIN, GPIO_IN);
|
||||
gpio_pull_up(ENCODER_SW_PIN);
|
||||
|
||||
gpio_set_irq_enabled_with_callback(ENCODER_SW_PIN, GPIO_IRQ_EDGE_FALL, true, &EncoderHandler::gpio_callback);
|
||||
|
||||
gpio_set_irq_enabled_with_callback(ENCODER_SW_PIN, GPIO_IRQ_EDGE_FALL, true, &gpio_callback);
|
||||
PIO pio = pio0;
|
||||
uint offset = pio_add_program(pio, &quadrature_encoder_program);
|
||||
this->sm = pio_claim_unused_sm(pio, true);
|
||||
|
||||
gpio_init(ENCODER_CLK_PIN);
|
||||
gpio_pull_up(ENCODER_CLK_PIN);
|
||||
gpio_init(ENCODER_DT_PIN);
|
||||
gpio_pull_up(ENCODER_DT_PIN);
|
||||
|
||||
PIO pio = pio0;
|
||||
uint offset = pio_add_program(pio, &quadrature_encoder_program);
|
||||
this->sm = pio_claim_unused_sm(pio, true);
|
||||
|
||||
gpio_init(ENCODER_CLK_PIN);
|
||||
gpio_pull_up(ENCODER_CLK_PIN);
|
||||
gpio_init(ENCODER_DT_PIN);
|
||||
gpio_pull_up(ENCODER_DT_PIN);
|
||||
|
||||
quadrature_encoder_program_init(pio, sm, ENCODER_CLK_PIN, 0);
|
||||
|
||||
this->last_count = 0;
|
||||
quadrature_encoder_program_init(pio, sm, ENCODER_CLK_PIN, 0);
|
||||
|
||||
this->last_count = 0;
|
||||
}
|
||||
|
||||
void EncoderHandler::update() {
|
||||
int32_t current_count = quadrature_encoder_get_count(pio0, this->sm);
|
||||
int32_t current_count = quadrature_encoder_get_count(pio0, this->sm);
|
||||
|
||||
int32_t delta = current_count - last_count;
|
||||
|
||||
int32_t delta = current_count - last_count;
|
||||
if (abs(delta) >= TICKS_PER_DETENT) {
|
||||
|
||||
if (delta < 0) {
|
||||
display_handler->moveCursor();
|
||||
} else {
|
||||
display_handler->moveCursor(0);
|
||||
}
|
||||
|
||||
if (abs(delta) >= TICKS_PER_DETENT) {
|
||||
|
||||
if (delta < 0) {
|
||||
display_handler->moveCursor();
|
||||
} else {
|
||||
display_handler->moveCursor(0);
|
||||
last_count = current_count - (delta % TICKS_PER_DETENT);
|
||||
}
|
||||
|
||||
last_count = current_count - (delta % TICKS_PER_DETENT);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
530
src/Gate.cpp
530
src/Gate.cpp
|
|
@ -1,412 +1,196 @@
|
|||
// Gate.cpp
|
||||
#include "Gate.h"
|
||||
#include "Mod.h"
|
||||
#include "Settings.h"
|
||||
#include "globals.h"
|
||||
#include "hardware/pwm.h"
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <math.h>
|
||||
#include <pico/types.h>
|
||||
|
||||
#ifndef max
|
||||
#define max(a, b) (((a) > (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
Gate::Gate(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2)
|
||||
: Output(pin, idx, slotIdx1, slotIdx2) {
|
||||
Gate::Gate(uint8_t pin) {
|
||||
this->pin = pin;
|
||||
this->idx = idx;
|
||||
this->slotIdx1 = slotIdx1;
|
||||
this->slotIdx2 = slotIdx2;
|
||||
|
||||
state = 0;
|
||||
|
||||
editing = 0;
|
||||
editing = 0;
|
||||
|
||||
modifierSelectionIndex = 8;
|
||||
divideMode = 0; // 1 divison | 0 multiplication
|
||||
modifier = 0; // divide mode modifier (4x, /32, etc)
|
||||
modifierSelectionIndex = 3;
|
||||
divideMode = 0; // 1 divison | 0 multiplication
|
||||
modifier = 0; // divide mode modifier (4x, /32, etc)
|
||||
|
||||
dur = 0; // how long pulse is on
|
||||
width = 50; // pulse width
|
||||
len = 0; // max len a pulse can be on, as determined by width
|
||||
dur = 0; // how long pulse is on
|
||||
width = 50; // pulse width
|
||||
len = 0; // max len a pulse can be on, as determined by width
|
||||
|
||||
p = 100; // probability of a pulse
|
||||
level = 100;
|
||||
|
||||
modDest1 = (idx + 1) % 8;
|
||||
modDest2 = (idx + 2) % 8;
|
||||
p = 100; // probability of a pulse
|
||||
}
|
||||
|
||||
void Gate::pack(OutputConfig &cfg) {
|
||||
cfg.type = TYPE_GATE;
|
||||
|
||||
GateSettings *s = (GateSettings *)cfg.data;
|
||||
|
||||
s->modifierSelectionIndex = this->modifierSelectionIndex;
|
||||
s->divideMode = this->divideMode;
|
||||
s->modifier = this->modifier;
|
||||
s->width = this->width;
|
||||
s->p = this->p;
|
||||
s->level = this->level;
|
||||
s->shape = (uint8_t)this->shape;
|
||||
}
|
||||
|
||||
void Gate::unpack(const OutputConfig &cfg) {
|
||||
if (cfg.type != TYPE_GATE)
|
||||
return;
|
||||
|
||||
GateSettings s;
|
||||
memcpy(&s, cfg.data, sizeof(GateSettings));
|
||||
|
||||
this->modifierSelectionIndex = s.modifierSelectionIndex;
|
||||
this->divideMode = s.divideMode;
|
||||
this->modifier = s.modifier;
|
||||
this->width = s.width;
|
||||
this->p = s.p;
|
||||
this->level = s.level;
|
||||
this->shape = (WaveShape)s.shape;
|
||||
|
||||
setDiv(this->modifierSelectionIndex);
|
||||
setWidth(this->width);
|
||||
}
|
||||
|
||||
void Gate::setupPatches() {
|
||||
matrix.patch(this->slotIdx1, this->modDest1, this->idx, DEST_LEVEL, 100,
|
||||
false);
|
||||
matrix.patch(this->slotIdx2, this->modDest2, this->idx, DEST_LEVEL, 100,
|
||||
false);
|
||||
}
|
||||
|
||||
void Gate::setLen(uint32_t currentPeriod) {
|
||||
len = (uint32_t)((double)currentPeriod * (width / 100.0) / 1000.0);
|
||||
len = (uint32_t)((double)currentPeriod * (width / 100.0) / 1000.0);
|
||||
}
|
||||
|
||||
|
||||
void Gate::setDiv(uint8_t modifier_selecton_index) {
|
||||
switch (modifier_selecton_index) {
|
||||
case 0: // x32
|
||||
tickInterval = 3;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 32;
|
||||
break;
|
||||
case 1: // x16
|
||||
tickInterval = 6;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 16;
|
||||
break;
|
||||
case 2: // x12
|
||||
tickInterval = 8;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 12;
|
||||
break;
|
||||
case 3: // x8
|
||||
tickInterval = 12;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 8;
|
||||
break;
|
||||
case 4: // x6
|
||||
tickInterval = 16;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 6;
|
||||
break;
|
||||
case 5: // x4
|
||||
tickInterval = 24;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 4;
|
||||
break;
|
||||
case 6: // x3
|
||||
tickInterval = 32;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 3;
|
||||
break;
|
||||
case 7: // x2
|
||||
tickInterval = 48;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 2;
|
||||
break;
|
||||
case 8: // 0 (OFF)
|
||||
tickInterval = 0;
|
||||
isEnabled = false;
|
||||
divideMode = 0;
|
||||
modifier = 0;
|
||||
break;
|
||||
case 9: // /1
|
||||
tickInterval = 96;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 1;
|
||||
break;
|
||||
case 10: // /2
|
||||
tickInterval = 192;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 2;
|
||||
break;
|
||||
case 11: // /3
|
||||
tickInterval = 288;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 3;
|
||||
break;
|
||||
case 12: // /4
|
||||
tickInterval = 384;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 4;
|
||||
break;
|
||||
case 13: // /6
|
||||
tickInterval = 576;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 4;
|
||||
break;
|
||||
case 14: // /8
|
||||
tickInterval = 768;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 8;
|
||||
break;
|
||||
case 15: // /16
|
||||
tickInterval = 1536;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 16;
|
||||
break;
|
||||
case 16: // /32
|
||||
tickInterval = 3072;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 16;
|
||||
break;
|
||||
default:
|
||||
tickInterval = 96;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
}
|
||||
switch(modifier_selecton_index) {
|
||||
case 0: // x32
|
||||
tickInterval = 3;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 32;
|
||||
break;
|
||||
case 1: // x16
|
||||
tickInterval = 6;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 16;
|
||||
break;
|
||||
case 2: // x12
|
||||
tickInterval = 8;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 12;
|
||||
break;
|
||||
case 3: // x8
|
||||
tickInterval = 12;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 8;
|
||||
break;
|
||||
case 4: // x6
|
||||
tickInterval = 16;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 6;
|
||||
break;
|
||||
case 5: // x4
|
||||
tickInterval = 24;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 4;
|
||||
break;
|
||||
case 6: // x3
|
||||
tickInterval = 32;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 3;
|
||||
break;
|
||||
case 7: // x2
|
||||
tickInterval = 48;
|
||||
isEnabled = true;
|
||||
divideMode = 0;
|
||||
modifier = 2;
|
||||
break;
|
||||
case 8: // 0 (OFF)
|
||||
tickInterval = 0;
|
||||
isEnabled = false;
|
||||
divideMode = 0;
|
||||
modifier = 0;
|
||||
break;
|
||||
case 9: // /1
|
||||
tickInterval = 96;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 1;
|
||||
break;
|
||||
case 10: // /2
|
||||
tickInterval = 192;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 2;
|
||||
break;
|
||||
case 11: // /3
|
||||
tickInterval = 288;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 3;
|
||||
break;
|
||||
case 12: // /4
|
||||
tickInterval = 384;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 4;
|
||||
break;
|
||||
case 13: // /6
|
||||
tickInterval = 576;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 4;
|
||||
break;
|
||||
case 14: // /8
|
||||
tickInterval = 768;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 8;
|
||||
break;
|
||||
case 15: // /16
|
||||
tickInterval = 1536;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 16;
|
||||
break;
|
||||
case 16: // /32
|
||||
tickInterval = 3072;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
modifier = 16;
|
||||
break;
|
||||
default:
|
||||
tickInterval = 96;
|
||||
isEnabled = true;
|
||||
divideMode = 1;
|
||||
}
|
||||
|
||||
this->triggerCount = 0;
|
||||
this->lastTriggerTick = 0xFFFFFFFF;
|
||||
|
||||
setWidth(this->width);
|
||||
// TODO: check if this is actually needed
|
||||
setWidth(this->width);
|
||||
};
|
||||
|
||||
|
||||
void Gate::setWidth(uint16_t newWidth) {
|
||||
this->width = newWidth;
|
||||
this->width = newWidth;
|
||||
|
||||
double ms_per_tick = (60000.0 / (double)BPM) / 96.0;
|
||||
|
||||
double ms_per_tick = (60000.0 / (double)BPM) / 96.0;
|
||||
double ms_between_triggers = ms_per_tick * (double)this->tickInterval;
|
||||
|
||||
double ms_between_triggers = ms_per_tick * (double)this->tickInterval;
|
||||
this->len = (uint32_t)(ms_between_triggers * (this->width / 100.0));
|
||||
|
||||
this->len = (uint32_t)(ms_between_triggers * (this->width / 100.0));
|
||||
uint32_t max_allowed_len = (ms_between_triggers > 5) ? (uint32_t)ms_between_triggers - 2 : 1;
|
||||
|
||||
if (this->len > max_allowed_len) {
|
||||
this->len = max_allowed_len;
|
||||
}
|
||||
|
||||
uint32_t max_allowed_len =
|
||||
(ms_between_triggers > 5) ? (uint32_t)ms_between_triggers - 2 : 1;
|
||||
|
||||
if (this->len > max_allowed_len) {
|
||||
this->len = max_allowed_len;
|
||||
}
|
||||
|
||||
if (this->len < 1)
|
||||
this->len = 1;
|
||||
|
||||
double us_per_tick = 625000.0 / (double)BPM;
|
||||
|
||||
calculatePulseWidth();
|
||||
|
||||
this->pulseDurationUs =
|
||||
(uint32_t)(us_per_tick * (double)this->pulseWidthTicks);
|
||||
|
||||
if (this->pulseDurationUs < 100)
|
||||
this->pulseDurationUs = 100;
|
||||
if (this->len < 1) this->len = 1;
|
||||
}
|
||||
|
||||
void Gate::calculatePulseWidth() {
|
||||
if (tickInterval == 0 || !isEnabled) {
|
||||
pulseWidthTicks = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->width > 0 && this->pulseWidthTicks == 0) {
|
||||
this->pulseWidthTicks = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Gate::turnOn() {
|
||||
if (!isEnabled || tickInterval == 0) {
|
||||
return;
|
||||
}
|
||||
if (!isEnabled || tickInterval == 0) return;
|
||||
|
||||
if (MASTER_TICK % tickInterval == 0 && MASTER_TICK != lastTriggerTick) {
|
||||
lastTriggerTick = MASTER_TICK;
|
||||
if (MASTER_TICK % tickInterval == 0) {
|
||||
if (MASTER_TICK != lastTriggerTick) {
|
||||
lastTriggerTick = MASTER_TICK;
|
||||
|
||||
// Probability
|
||||
float effectiveP = (float)this->p + (this->pMod * 100.0f);
|
||||
if ((rand() % 100) + 1 > (uint8_t)effectiveP) {
|
||||
scheduledTick = 0xFFFFFFFF;
|
||||
return;
|
||||
uint8_t pRes = 1;
|
||||
if (p < 100) {
|
||||
if ((rand() % 100) + 1 > p) pRes = 0;
|
||||
}
|
||||
|
||||
if (pRes == 1) {
|
||||
state = 1;
|
||||
gpio_put(pin, 1);
|
||||
dur = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
lastTriggerTick = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
// Swing
|
||||
triggerCount++;
|
||||
int32_t swingOffset =
|
||||
(int32_t)((float)tickInterval * ((float)swing - 50.0f) / 100.0f);
|
||||
|
||||
if (triggerCount % 2 == 0) {
|
||||
scheduledTick = MASTER_TICK + (uint32_t)max(0, swingOffset);
|
||||
} else {
|
||||
scheduledTick = MASTER_TICK;
|
||||
}
|
||||
}
|
||||
|
||||
if (MASTER_TICK >= scheduledTick && !state) {
|
||||
state = 1;
|
||||
startTick = MASTER_TICK;
|
||||
|
||||
calculatePulseWidth();
|
||||
stopTick = startTick + pulseWidthTicks;
|
||||
|
||||
scheduledTick = 0xFFFFFFFF;
|
||||
currentRandomVal = (float)rand() / (float)RAND_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
void Gate::update() {
|
||||
if (!state && !sticky) {
|
||||
lastOutVal = 0.0f;
|
||||
writeAnalog(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// width
|
||||
float effectiveWidth = (float)width + (widthMod * 100.0f);
|
||||
if (effectiveWidth > 100.0f)
|
||||
effectiveWidth = 100.0f;
|
||||
if (effectiveWidth < 0.0f)
|
||||
effectiveWidth = 0.0f;
|
||||
|
||||
uint32_t modulatedTicks =
|
||||
(uint32_t)((float)this->tickInterval * (effectiveWidth / 100.0f));
|
||||
this->stopTick = startTick + modulatedTicks;
|
||||
|
||||
if (effectiveWidth < 100.0f) {
|
||||
if (MASTER_TICK >= stopTick) {
|
||||
state = 0;
|
||||
if (!sticky) {
|
||||
lastOutVal = 0.0f;
|
||||
writeAnalog(0);
|
||||
}
|
||||
return;
|
||||
void Gate::turnOff() {
|
||||
if (state == 1) {
|
||||
if (millis() - dur >= len) {
|
||||
state = 0;
|
||||
gpio_put(pin, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t now = time_us_64();
|
||||
uint64_t usSinceLastTick = (now > last_clk_us) ? (now - last_clk_us) : 0;
|
||||
|
||||
double current_BPM_for_math = (double)filteredBPM;
|
||||
if (current_BPM_for_math < 1.0)
|
||||
current_BPM_for_math = 1.0;
|
||||
double us_per_tick = 60000000.0 / (current_BPM_for_math * (double)PPQN);
|
||||
|
||||
float subTick = (float)usSinceLastTick / (float)us_per_tick;
|
||||
if (subTick > 0.99f)
|
||||
subTick = 0.99f;
|
||||
|
||||
float elapsedTicks = (float)(MASTER_TICK - startTick) + subTick;
|
||||
float totalDurationTicks = (effectiveWidth >= 100.0f)
|
||||
? (float)tickInterval
|
||||
: (float)(stopTick - startTick);
|
||||
|
||||
if (totalDurationTicks < 1.0f)
|
||||
totalDurationTicks = 1.0f;
|
||||
|
||||
float phase = elapsedTicks / totalDurationTicks;
|
||||
|
||||
if (effectiveWidth >= 100.0f) {
|
||||
while (phase >= 1.0f)
|
||||
phase -= 1.0f;
|
||||
} else {
|
||||
if (phase > 1.0f)
|
||||
phase = 1.0f;
|
||||
}
|
||||
|
||||
if (phase < 0.0f)
|
||||
phase = 0.0f;
|
||||
|
||||
float outVal = 0;
|
||||
switch (shape) {
|
||||
case SINE:
|
||||
outVal = (sinf(phase * 2.0f * 3.14159265f) * 0.5f) + 0.5f;
|
||||
break;
|
||||
case HALFSINE:
|
||||
outVal = sinf(phase * 3.14159265f);
|
||||
break;
|
||||
case TRIANGLE:
|
||||
outVal = (phase < 0.5f) ? (phase * 2.0f) : (2.0f - (phase * 2.0f));
|
||||
break;
|
||||
case SAW:
|
||||
outVal = 1.0f - phase;
|
||||
break;
|
||||
case RAMP:
|
||||
outVal = phase;
|
||||
break;
|
||||
case EXP:
|
||||
outVal = expf(-5.0f * phase);
|
||||
break;
|
||||
case REXP:
|
||||
outVal = expf(5.0f * (phase - 1.0f));
|
||||
break;
|
||||
case LOG:
|
||||
outVal = 1.0f - expf(-5.0f * phase);
|
||||
break;
|
||||
case SQUARE:
|
||||
outVal = 1.0f;
|
||||
break;
|
||||
case BOUNCE:
|
||||
outVal = fabsf(sinf(phase * 3.14159265f * 2.0f));
|
||||
break;
|
||||
case SIGMO:
|
||||
outVal = phase * phase * (3.0f - 2.0f * phase);
|
||||
break;
|
||||
case WOBBLE:
|
||||
outVal = expf(-3.0f * phase) * cosf(phase * 3.14159265f * 4.0f);
|
||||
if (outVal < 0)
|
||||
outVal = 0;
|
||||
break;
|
||||
case STEPDW:
|
||||
outVal = 1.0f - (floorf(phase * 4.0f) / 3.0f);
|
||||
break;
|
||||
case STEPUP:
|
||||
outVal = floorf(phase * 4.0f) / 3.0f;
|
||||
break;
|
||||
case SH:
|
||||
outVal = currentRandomVal;
|
||||
break;
|
||||
default:
|
||||
outVal = 1.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
this->lastOutVal = outVal;
|
||||
|
||||
float finalLevel = ((float)this->level / 100.0f) + (this->levelMod);
|
||||
if (finalLevel > 1.0f)
|
||||
finalLevel = 1.0f;
|
||||
if (finalLevel < 0.0f)
|
||||
finalLevel = 0.0f;
|
||||
|
||||
writeAnalog((uint16_t)(outVal * 1023.0f * finalLevel));
|
||||
}
|
||||
|
||||
void Gate::writeAnalog(uint16_t val) { pwm_set_gpio_level(pin, val); }
|
||||
|
||||
void Gate::turnOff() { writeAnalog(0); }
|
||||
|
|
|
|||
61
src/Mod.cpp
61
src/Mod.cpp
|
|
@ -1,61 +0,0 @@
|
|||
#include "Mod.h"
|
||||
#include "Gate.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
void ModMatrix::patch(uint8_t slotIdx, uint8_t src, uint8_t dest, ModDest param,
|
||||
float amt, bool active) {
|
||||
slots[slotIdx].sourceIdx = src;
|
||||
slots[slotIdx].destIdx = dest;
|
||||
slots[slotIdx].destParam = (uint8_t)param;
|
||||
slots[slotIdx].amount = (uint8_t)amt;
|
||||
slots[slotIdx].active = active ? 1 : 0;
|
||||
slots[slotIdx].inverted = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
void ModMatrix::process(Gate **gates, uint8_t gateCount) {
|
||||
for (int i = 0; i < gateCount; i++) {
|
||||
gates[i]->resetMods();
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (slots[i].active == 0)
|
||||
continue;
|
||||
|
||||
float srcVal = 0.0f;
|
||||
|
||||
if (slots[i].sourceIdx < 8) {
|
||||
srcVal = gates[slots[i].sourceIdx]->lastOutVal;
|
||||
} else if (slots[i].sourceIdx == 8) {
|
||||
srcVal = cv_values[0];
|
||||
} else if (slots[i].sourceIdx == 9) {
|
||||
srcVal = cv_values[1];
|
||||
}
|
||||
|
||||
float amt = (float)slots[i].amount;
|
||||
float normalizedAmt = amt / 100.0f;
|
||||
|
||||
if (slots[i].inverted == 1) {
|
||||
normalizedAmt *= -1.0f;
|
||||
}
|
||||
|
||||
float modValue = srcVal * normalizedAmt;
|
||||
|
||||
Gate *dstGate = gates[slots[i].destIdx];
|
||||
|
||||
switch ((ModDest)slots[i].destParam) {
|
||||
case DEST_LEVEL:
|
||||
dstGate->levelMod += modValue;
|
||||
break;
|
||||
case DEST_PROBABILITY:
|
||||
dstGate->pMod += modValue;
|
||||
break;
|
||||
case DEST_WIDTH:
|
||||
dstGate->widthMod += modValue;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#include "Output.h"
|
||||
#include <cstdint>
|
||||
|
||||
Output::Output(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2) {
|
||||
this->pin = pin;
|
||||
this->idx = idx;
|
||||
state = 0;
|
||||
isEnabled = false;
|
||||
}
|
||||
|
||||
void Output::turnOn() {}
|
||||
void Output::turnOff() {}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
#include "Settings.h"
|
||||
#include "Gate.h"
|
||||
#include "hardware/flash.h"
|
||||
#include "hardware/sync.h"
|
||||
#include "pico/multicore.h"
|
||||
#include <string.h>
|
||||
|
||||
#define FLASH_TARGET_OFFSET (2048 * 1024 - FLASH_SECTOR_SIZE)
|
||||
|
||||
DeviceSettings globalSettings;
|
||||
|
||||
void save() {
|
||||
|
||||
globalSettings.bpm = BPM;
|
||||
globalSettings.play = PLAY;
|
||||
globalSettings.run = RUN;
|
||||
globalSettings.ppqnidx = EXTPPQNIdx;
|
||||
|
||||
memcpy(globalSettings.slots, matrix.slots, sizeof(ModSlot) * 16);
|
||||
|
||||
const uint32_t WRITE_SIZE = (sizeof(DeviceSettings) + 255) & ~255;
|
||||
|
||||
static uint8_t __attribute__((aligned(4))) write_buf[2048];
|
||||
|
||||
uint32_t copy_size =
|
||||
sizeof(DeviceSettings) > 2048 ? 2048 : sizeof(DeviceSettings);
|
||||
|
||||
memset(write_buf, 0, sizeof(write_buf));
|
||||
memcpy(write_buf, &globalSettings, copy_size);
|
||||
|
||||
multicore_lockout_start_blocking();
|
||||
uint32_t ints = save_and_disable_interrupts();
|
||||
|
||||
flash_range_erase(FLASH_TARGET_OFFSET, FLASH_SECTOR_SIZE);
|
||||
flash_range_program(FLASH_TARGET_OFFSET, write_buf, WRITE_SIZE);
|
||||
|
||||
restore_interrupts(ints);
|
||||
multicore_lockout_end_blocking();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void load_default() {
|
||||
memset(&globalSettings.slots, 0, sizeof(globalSettings.slots));
|
||||
|
||||
globalSettings.magic = 0x434C4F4B;
|
||||
globalSettings.version = 1;
|
||||
|
||||
for (int i = 0; i < MAX_OUTPUTS; i++) {
|
||||
globalSettings.configs[i].type = TYPE_GATE;
|
||||
|
||||
GateSettings *s = (GateSettings *)globalSettings.configs[i].data;
|
||||
|
||||
s->modifierSelectionIndex = 8;
|
||||
s->divideMode = 0;
|
||||
s->modifier = 0;
|
||||
s->width = 50;
|
||||
s->p = 100;
|
||||
s->level = 100;
|
||||
s->shape = 0;
|
||||
|
||||
BPM = 60;
|
||||
PLAY = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool load() {
|
||||
const uint8_t *flash_target_contents =
|
||||
(const uint8_t *)(XIP_BASE + FLASH_TARGET_OFFSET);
|
||||
DeviceSettings *stored = (DeviceSettings *)flash_target_contents;
|
||||
|
||||
if (stored->magic == 0x434C4F4B) {
|
||||
memcpy(&globalSettings, stored, sizeof(DeviceSettings));
|
||||
return false;
|
||||
} else {
|
||||
load_default();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
395
src/main.cpp
395
src/main.cpp
|
|
@ -1,380 +1,129 @@
|
|||
#include "Settings.h"
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <stdio.h>
|
||||
#include "hardware/structs/rosc.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/time.h"
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <hardware/gpio.h>
|
||||
#include <math.h>
|
||||
#include <pico/types.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
#include "globals.h"
|
||||
#include "Gate.h"
|
||||
#include "DisplayHandler.h"
|
||||
#include "EncoderHandler.h"
|
||||
#include "Gate.h"
|
||||
#include "Mod.h"
|
||||
#include "Settings.h"
|
||||
#include "globals.h"
|
||||
#include "hardware/adc.h"
|
||||
#include "hardware/pwm.h"
|
||||
|
||||
|
||||
// Time based operations
|
||||
struct repeating_timer bpm_timer = {0};
|
||||
volatile float BPM = 60;
|
||||
volatile bool PLAY = true;
|
||||
volatile uint8_t BPM = 60;
|
||||
volatile uint8_t PLAY = 1;
|
||||
volatile uint32_t period_us = 0;
|
||||
volatile uint32_t MASTER_TICK;
|
||||
volatile bool RUN = false;
|
||||
const uint16_t PPQNOPTS[] = {1, 2, 4, 24, 48};
|
||||
volatile uint8_t EXTPPQNIdx = 0;
|
||||
volatile uint64_t last_clk_us = 0;
|
||||
volatile uint64_t last_valid_clk_us;
|
||||
volatile bool EXTERNAL_CLOCK = false;
|
||||
volatile float filteredBPM = 60.0f;
|
||||
volatile uint64_t last_external_pulse_us = 0;
|
||||
const uint64_t CLOCK_TIMEOUT_US = 2000000;
|
||||
uint64_t pulse_intervals[AVG_SAMPLES] = {0};
|
||||
uint8_t pulse_idx = 0;
|
||||
uint64_t last_external_clk_us = 0;
|
||||
uint8_t BPM_UI_REFRESH = 0;
|
||||
|
||||
bool external_pulse_received = false;
|
||||
ModMatrix matrix;
|
||||
|
||||
// Initialize Outputs
|
||||
Gate out1(OUT_1_PIN, 0, 0, 1);
|
||||
Gate out2(OUT_2_PIN, 1, 2, 3);
|
||||
Gate out3(OUT_3_PIN, 2, 4, 5);
|
||||
Gate out4(OUT_4_PIN, 3, 6, 7);
|
||||
Gate out5(OUT_5_PIN, 4, 8, 9);
|
||||
Gate out6(OUT_6_PIN, 5, 10, 11);
|
||||
Gate out7(OUT_7_PIN, 6, 12, 13);
|
||||
Gate out8(OUT_8_PIN, 7, 14, 15);
|
||||
Gate out1(OUT_1_PIN);
|
||||
Gate out2(OUT_2_PIN);
|
||||
Gate out3(OUT_3_PIN);
|
||||
Gate out4(OUT_4_PIN);
|
||||
Gate out5(OUT_5_PIN);
|
||||
Gate out6(OUT_6_PIN);
|
||||
Gate out7(OUT_7_PIN);
|
||||
Gate out8(OUT_8_PIN);
|
||||
|
||||
static Gate *outputs[] = {&out1, &out2, &out3, &out4,
|
||||
&out5, &out6, &out7, &out8};
|
||||
static Gate* outputs[] = {&out1, &out2, &out3, &out4, &out5, &out6, &out7, &out8};
|
||||
|
||||
volatile float cv_values[2] = {0.0f, 0.0f};
|
||||
const float conversion_factor = 1.0f / (1 << 12);
|
||||
|
||||
// Initialize Handlers
|
||||
static DisplayHandler display_handler(outputs);
|
||||
static EncoderHandler encoder_handler(&display_handler);
|
||||
static EncoderHandler encoder_handler(&display_handler);
|
||||
|
||||
|
||||
|
||||
bool timer_callback(struct repeating_timer *t) {
|
||||
if (PLAY == 1) {
|
||||
last_clk_us = to_us_since_boot(get_absolute_time());
|
||||
MASTER_TICK += 1;
|
||||
}
|
||||
return true;
|
||||
if (PLAY == 1) {
|
||||
MASTER_TICK += 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void init_timer(uint32_t period_us) {
|
||||
cancel_repeating_timer(&bpm_timer);
|
||||
add_repeating_timer_us(-(int64_t)period_us, timer_callback, NULL, &bpm_timer);
|
||||
cancel_repeating_timer(&bpm_timer);
|
||||
add_repeating_timer_us(-(int64_t)period_us, timer_callback, NULL, &bpm_timer);
|
||||
}
|
||||
|
||||
|
||||
void update_period() {
|
||||
period_us = (uint32_t)(MINUTE_US / (float)BPM / PPQN);
|
||||
init_timer(period_us);
|
||||
period_us = (uint32_t)(MINUTE_US / (uint32_t)BPM / PPQN);
|
||||
init_timer(period_us);
|
||||
}
|
||||
|
||||
|
||||
void update_BPM(bool up) {
|
||||
if (up) {
|
||||
BPM += 0.5;
|
||||
} else {
|
||||
BPM -= 0.5;
|
||||
}
|
||||
if (up) {
|
||||
BPM++;
|
||||
} else {
|
||||
BPM--;
|
||||
}
|
||||
|
||||
update_period();
|
||||
|
||||
if (!EXTERNAL_CLOCK) {
|
||||
init_timer(period_us);
|
||||
} else {
|
||||
cancel_repeating_timer(&bpm_timer);
|
||||
}
|
||||
update_period();
|
||||
}
|
||||
|
||||
|
||||
void core1_entry() {
|
||||
multicore_fifo_pop_blocking();
|
||||
multicore_lockout_victim_init();
|
||||
multicore_fifo_pop_blocking();
|
||||
|
||||
char buffer[32];
|
||||
char buffer[32];
|
||||
|
||||
while (true) {
|
||||
display_handler.render();
|
||||
}
|
||||
while (true) {
|
||||
display_handler.render();
|
||||
}
|
||||
}
|
||||
|
||||
void full_save() {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
outputs[i]->pack(globalSettings.configs[i]);
|
||||
}
|
||||
|
||||
globalSettings.bpm = BPM;
|
||||
globalSettings.play = PLAY;
|
||||
globalSettings.run = RUN;
|
||||
globalSettings.ppqnidx = EXTPPQNIdx;
|
||||
|
||||
memcpy(globalSettings.slots, matrix.slots, sizeof(ModSlot) * 16);
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
void setup_outs() {
|
||||
|
||||
for (auto g : outputs) {
|
||||
gpio_init(g->pin);
|
||||
gpio_set_dir(g->pin, GPIO_OUT);
|
||||
for (auto g : outputs) {
|
||||
gpio_init(g->pin);
|
||||
gpio_set_dir(g->pin, GPIO_OUT);
|
||||
g->setLen(period_us);
|
||||
}
|
||||
|
||||
gpio_set_function(g->pin, GPIO_FUNC_PWM);
|
||||
uint slice_num = pwm_gpio_to_slice_num(g->pin);
|
||||
pwm_set_wrap(slice_num, 1023);
|
||||
pwm_set_clkdiv(slice_num, 1.0f);
|
||||
|
||||
pwm_set_gpio_level(g->pin, 0);
|
||||
pwm_set_enabled(slice_num, true);
|
||||
g->setLen(period_us);
|
||||
g->setupPatches();
|
||||
}
|
||||
// manual setup
|
||||
// out1.setDiv(5);
|
||||
// out1.setWidth(80);
|
||||
}
|
||||
|
||||
|
||||
void handle_outs() {
|
||||
matrix.process(outputs, 8);
|
||||
for (Gate *g : outputs) {
|
||||
g->turnOn();
|
||||
g->update();
|
||||
}
|
||||
for (Gate* g: outputs) {
|
||||
g->turnOn();
|
||||
g->turnOff();
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_callback(uint gpio, uint32_t events) {
|
||||
if (gpio == IN_CLK_PIN && (events & GPIO_IRQ_EDGE_RISE)) {
|
||||
uint64_t now = to_us_since_boot(get_absolute_time());
|
||||
|
||||
if (now - last_valid_clk_us < 1000)
|
||||
return;
|
||||
|
||||
if (last_external_clk_us > 0) {
|
||||
uint64_t latest_diff = now - last_external_clk_us;
|
||||
|
||||
pulse_intervals[pulse_idx] = latest_diff;
|
||||
pulse_idx = (pulse_idx + 1) % AVG_SAMPLES;
|
||||
|
||||
uint64_t sum = 0;
|
||||
uint8_t count = 0;
|
||||
for (int i = 0; i < AVG_SAMPLES; i++) {
|
||||
if (pulse_intervals[i] > 0) {
|
||||
sum += pulse_intervals[i];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
double avg_diff = (double)sum / (double)count;
|
||||
uint16_t incomingPPQN = PPQNOPTS[EXTPPQNIdx];
|
||||
|
||||
double calculatedBPM = 60000000.0 / (avg_diff * (double)incomingPPQN);
|
||||
|
||||
if (calculatedBPM > 20.0 && calculatedBPM < 300.0) {
|
||||
float diff = (float)calculatedBPM - filteredBPM;
|
||||
|
||||
if (fabsf(diff) > 5.0f || filteredBPM < 1.0f) {
|
||||
filteredBPM = (float)calculatedBPM;
|
||||
} else {
|
||||
filteredBPM += (0.3f * diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ticks_per_pulse = 96 / PPQNOPTS[EXTPPQNIdx];
|
||||
MASTER_TICK = ((MASTER_TICK + (ticks_per_pulse / 2)) / ticks_per_pulse) *
|
||||
ticks_per_pulse;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (outputs[i]->lastTriggerTick > MASTER_TICK) {
|
||||
outputs[i]->lastTriggerTick = 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
last_external_clk_us = now;
|
||||
last_clk_us = now;
|
||||
last_valid_clk_us = now;
|
||||
last_external_pulse_us = now;
|
||||
external_pulse_received = true;
|
||||
EXTERNAL_CLOCK = true;
|
||||
|
||||
BPM_UI_REFRESH += 1;
|
||||
if (BPM_UI_REFRESH % 4 == 0) {
|
||||
display_handler.updateScreen = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (gpio == IN_RUN_PIN) {
|
||||
if (RUN) {
|
||||
if (events & GPIO_IRQ_EDGE_RISE) {
|
||||
PLAY = true;
|
||||
} else if (events & GPIO_IRQ_EDGE_FALL) {
|
||||
PLAY = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gpio == ENCODER_SW_PIN) {
|
||||
uint64_t now = to_us_since_boot(get_absolute_time());
|
||||
static uint64_t last_sw_time = 0;
|
||||
if (now - last_sw_time > 200000) { // 200ms debounce
|
||||
display_handler.handleClick();
|
||||
last_sw_time = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup_ins() {
|
||||
// SETUP RUN
|
||||
gpio_init(IN_RUN_PIN);
|
||||
gpio_set_dir(IN_RUN_PIN, GPIO_IN);
|
||||
gpio_pull_down(IN_RUN_PIN);
|
||||
gpio_set_irq_enabled_with_callback(IN_RUN_PIN,
|
||||
GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL,
|
||||
true, &gpio_callback);
|
||||
|
||||
// SETUP CLOCK
|
||||
gpio_init(IN_CLK_PIN);
|
||||
gpio_set_dir(IN_CLK_PIN, GPIO_IN);
|
||||
gpio_pull_down(IN_CLK_PIN);
|
||||
gpio_set_irq_enabled(IN_CLK_PIN, GPIO_IRQ_EDGE_RISE, true);
|
||||
|
||||
// SETUP CV INS
|
||||
adc_init();
|
||||
adc_gpio_init(26);
|
||||
adc_gpio_init(27);
|
||||
}
|
||||
|
||||
float fmap(float x, float in_min, float in_max) {
|
||||
float result = (x - in_min) / (in_max - in_min);
|
||||
if (result < 0.0f)
|
||||
return 0.0f;
|
||||
if (result > 1.0f)
|
||||
return 1.0f;
|
||||
return result;
|
||||
}
|
||||
|
||||
void update_cv() {
|
||||
static uint64_t last_adc_read = 0;
|
||||
uint64_t now = to_us_since_boot(get_absolute_time());
|
||||
if (now - last_adc_read < 2000)
|
||||
return;
|
||||
last_adc_read = now;
|
||||
|
||||
const float raw_min = -0.19f;
|
||||
const float raw_max = 0.15f;
|
||||
const float offset_zero = 0.404f;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
adc_select_input(i);
|
||||
|
||||
adc_read();
|
||||
busy_wait_us(10);
|
||||
|
||||
float raw_val = (float)adc_read() * (1.0f / 4095.0f);
|
||||
float centered = offset_zero - raw_val;
|
||||
|
||||
float scaled = (centered - raw_min) / (raw_max - raw_min);
|
||||
|
||||
if (scaled < 0.01f)
|
||||
scaled = 0.0f;
|
||||
if (scaled > 1.0f)
|
||||
scaled = 1.0f;
|
||||
|
||||
cv_values[i] = scaled;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
sleep_ms(200);
|
||||
// initialize
|
||||
stdio_init_all();
|
||||
|
||||
bool loaded_defaults = load();
|
||||
// Seed random
|
||||
srand(rosc_hw->randombit);
|
||||
|
||||
display_handler.setup();
|
||||
encoder_handler.setup();
|
||||
// Initialize display and multicore
|
||||
display_handler.setup();
|
||||
multicore_launch_core1(core1_entry);
|
||||
multicore_fifo_push_blocking(1);
|
||||
|
||||
setup_outs();
|
||||
// Initialize Encoder
|
||||
encoder_handler.setup();
|
||||
|
||||
if (!loaded_defaults) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
matrix.slots[i] = globalSettings.slots[i];
|
||||
}
|
||||
setup_outs();
|
||||
|
||||
BPM = globalSettings.bpm;
|
||||
PLAY = globalSettings.play;
|
||||
RUN = globalSettings.run;
|
||||
EXTPPQNIdx = globalSettings.ppqnidx;
|
||||
}
|
||||
update_period();
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
outputs[i]->unpack(globalSettings.configs[i]);
|
||||
}
|
||||
|
||||
multicore_launch_core1(core1_entry);
|
||||
multicore_fifo_push_blocking(1);
|
||||
|
||||
update_period();
|
||||
srand(rosc_hw->randombit);
|
||||
|
||||
bool lastPlayState = false;
|
||||
|
||||
setup_ins();
|
||||
|
||||
if (RUN) {
|
||||
PLAY = false;
|
||||
}
|
||||
while (true) {
|
||||
uint64_t now = to_us_since_boot(get_absolute_time());
|
||||
|
||||
if (EXTERNAL_CLOCK && (now - last_external_pulse_us > CLOCK_TIMEOUT_US)) {
|
||||
EXTERNAL_CLOCK = false;
|
||||
BPM = globalSettings.bpm;
|
||||
filteredBPM = (float)BPM;
|
||||
update_period();
|
||||
printf("Clock Lost. Internal BPM Resumed.\n");
|
||||
}
|
||||
|
||||
if (external_pulse_received) {
|
||||
external_pulse_received = false;
|
||||
|
||||
static uint8_t last_ppqn_idx = 0xFF;
|
||||
if (EXTPPQNIdx != last_ppqn_idx) {
|
||||
MASTER_TICK = 0;
|
||||
for (Gate *g : outputs) {
|
||||
g->lastTriggerTick = 0xFFFFFFFF;
|
||||
g->state = 0;
|
||||
}
|
||||
last_ppqn_idx = EXTPPQNIdx;
|
||||
}
|
||||
|
||||
BPM = filteredBPM;
|
||||
|
||||
if (PLAY) {
|
||||
for (Gate *g : outputs) {
|
||||
g->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_cv();
|
||||
encoder_handler.update();
|
||||
|
||||
if (PLAY) {
|
||||
handle_outs();
|
||||
} else {
|
||||
for (Gate *g : outputs) {
|
||||
g->turnOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
encoder_handler.update();
|
||||
handle_outs();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue