mod matrix and load/save initial functionality
This commit is contained in:
parent
b7c5c27328
commit
5f762f0907
15 changed files with 999 additions and 479 deletions
|
|
@ -18,6 +18,8 @@ add_executable(clock
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/Output.cpp
|
src/Output.cpp
|
||||||
src/Gate.cpp
|
src/Gate.cpp
|
||||||
|
src/Mod.cpp
|
||||||
|
src/Settings.cpp
|
||||||
src/DisplayHandler.cpp
|
src/DisplayHandler.cpp
|
||||||
src/EncoderHandler.cpp
|
src/EncoderHandler.cpp
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,27 @@ class DisplayHandler {
|
||||||
char buffer[32];
|
char buffer[32];
|
||||||
uint8_t currentScreen;
|
uint8_t currentScreen;
|
||||||
std::string screens[7];
|
std::string screens[7];
|
||||||
std::array<std::string, 9> out_pages = {"Exit", "Mod", "Shape", "Level", "Width", "Swing", "Prob", "Sticky", "Mute"};
|
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 = 0;
|
bool onOutScreen = 0;
|
||||||
void renderMainPage();
|
void renderMainPage();
|
||||||
void renderOutPage();
|
void renderOutPage();
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,20 @@
|
||||||
#ifndef EncoderHandler_h
|
#ifndef EncoderHandler_h
|
||||||
#define EncoderHandler_h
|
#define EncoderHandler_h
|
||||||
|
|
||||||
|
#include "DisplayHandler.h"
|
||||||
|
#include "pico/multicore.h"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "pico/multicore.h"
|
|
||||||
#include "DisplayHandler.h"
|
|
||||||
|
|
||||||
|
|
||||||
class EncoderHandler {
|
class EncoderHandler {
|
||||||
private:
|
private:
|
||||||
uint sm;
|
uint sm;
|
||||||
uint last_count;
|
uint last_count;
|
||||||
|
|
||||||
|
public:
|
||||||
|
EncoderHandler(DisplayHandler *display_handler);
|
||||||
|
|
||||||
public:
|
DisplayHandler *display_handler;
|
||||||
EncoderHandler(DisplayHandler* display_handler);
|
|
||||||
|
|
||||||
DisplayHandler* display_handler;
|
|
||||||
uint8_t encoder_pos;
|
uint8_t encoder_pos;
|
||||||
bool button_pressed;
|
bool button_pressed;
|
||||||
uint16_t clk_last_state;
|
uint16_t clk_last_state;
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,61 @@
|
||||||
#ifndef Gate_h
|
#ifndef Gate_h
|
||||||
#define Gate_h
|
#define Gate_h
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include "Output.h"
|
#include "Output.h"
|
||||||
|
#include "Settings.h"
|
||||||
#include "globals.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 : public Output {
|
class Gate : public Output {
|
||||||
private:
|
private:
|
||||||
uint32_t dur;
|
|
||||||
uint32_t triggerCount;
|
|
||||||
uint32_t scheduledTick;
|
|
||||||
float currentRandomVal;
|
float currentRandomVal;
|
||||||
uint32_t len;
|
uint32_t len;
|
||||||
uint32_t lastTriggerTick = 0xFFFFFFFF;
|
|
||||||
uint64_t startTimeUs;
|
uint64_t startTimeUs;
|
||||||
uint32_t pulseDurationUs;
|
uint32_t pulseDurationUs;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Gate(uint8_t pin);
|
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;
|
WaveShape shape = SQUARE;
|
||||||
uint32_t startTick = 0;
|
uint32_t startTick = 0;
|
||||||
|
|
@ -28,22 +64,22 @@ class Gate : public Output {
|
||||||
bool sticky = false;
|
bool sticky = false;
|
||||||
int8_t modifierSelectionIndex;
|
int8_t modifierSelectionIndex;
|
||||||
uint8_t divideMode;
|
uint8_t divideMode;
|
||||||
uint8_t swing = 50;
|
|
||||||
uint16_t modifier;
|
uint16_t modifier;
|
||||||
uint16_t tickInterval;
|
uint16_t tickInterval;
|
||||||
uint8_t level;
|
|
||||||
uint8_t width;
|
|
||||||
uint8_t p;
|
|
||||||
|
|
||||||
void turnOn() override;
|
void turnOn() override;
|
||||||
void update();
|
void update();
|
||||||
void turnOff() override;
|
void turnOff() override;
|
||||||
|
|
||||||
void calculatePulseWidth();
|
void calculatePulseWidth();
|
||||||
|
void setupPatches();
|
||||||
void writeAnalog(uint16_t val);
|
void writeAnalog(uint16_t val);
|
||||||
void setLen(uint32_t currentPeriod);
|
void setLen(uint32_t currentPeriod);
|
||||||
void setDiv(uint8_t modifier_selection_index);
|
void setDiv(uint8_t modifier_selection_index);
|
||||||
void setWidth(uint16_t newWidth);
|
void setWidth(uint16_t newWidth);
|
||||||
|
|
||||||
|
void pack(OutputConfig &cfg);
|
||||||
|
void unpack(const OutputConfig &cfg);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
39
include/Mod.h
Normal file
39
include/Mod.h
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
// 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
|
||||||
|
|
@ -4,14 +4,15 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
class Output {
|
class Output {
|
||||||
private:
|
private:
|
||||||
|
public:
|
||||||
public:
|
Output(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2);
|
||||||
Output(uint8_t pin);
|
|
||||||
bool state;
|
bool state;
|
||||||
uint8_t pin;
|
uint8_t pin;
|
||||||
|
uint8_t idx;
|
||||||
|
uint8_t slotIdx1;
|
||||||
|
uint8_t slotIdx2;
|
||||||
uint8_t editing;
|
uint8_t editing;
|
||||||
bool isEnabled;
|
bool isEnabled;
|
||||||
|
|
||||||
|
|
|
||||||
32
include/Settings.h
Normal file
32
include/Settings.h
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
// 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];
|
||||||
|
};
|
||||||
|
|
||||||
|
void save();
|
||||||
|
bool load();
|
||||||
|
void load_default();
|
||||||
|
|
||||||
|
extern DeviceSettings globalSettings;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#define GLOBALS_H
|
#define GLOBALS_H
|
||||||
|
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
|
#include "Mod.h"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -66,6 +67,17 @@ 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"};
|
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() {
|
inline uint32_t millis() {
|
||||||
return to_ms_since_boot(get_absolute_time());
|
return to_ms_since_boot(get_absolute_time());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
// DisplayHandler.cpp
|
// DisplayHandler.cpp
|
||||||
#include "pico/stdlib.h"
|
|
||||||
#include "DisplayHandler.h"
|
#include "DisplayHandler.h"
|
||||||
|
#include "Mod.h"
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include <string>
|
#include "pico/stdlib.h"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "hardware/i2c.h"
|
#include "hardware/i2c.h"
|
||||||
#include "pico-ssd1306/ssd1306.h"
|
|
||||||
#include "pico-ssd1306/shapeRenderer/ShapeRenderer.h"
|
#include "pico-ssd1306/shapeRenderer/ShapeRenderer.h"
|
||||||
|
#include "pico-ssd1306/ssd1306.h"
|
||||||
#include "pico-ssd1306/textRenderer/TextRenderer.h"
|
#include "pico-ssd1306/textRenderer/TextRenderer.h"
|
||||||
|
|
||||||
|
pico_ssd1306::SSD1306 *display = nullptr;
|
||||||
pico_ssd1306::SSD1306* display = nullptr;
|
|
||||||
extern void update_BPM(bool up);
|
extern void update_BPM(bool up);
|
||||||
|
|
||||||
|
DisplayHandler::DisplayHandler(Gate *outputs[]) {
|
||||||
DisplayHandler::DisplayHandler(Gate* outputs[]) {
|
|
||||||
this->outputs = outputs;
|
this->outputs = outputs;
|
||||||
currentScreen = 0;
|
currentScreen = 0;
|
||||||
currentOut = -1;
|
currentOut = -1;
|
||||||
|
|
@ -26,7 +25,6 @@ DisplayHandler::DisplayHandler(Gate* outputs[]) {
|
||||||
cursorClick = 0;
|
cursorClick = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DisplayHandler::setup() {
|
void DisplayHandler::setup() {
|
||||||
i2c_init(i2c1, 400 * 1000);
|
i2c_init(i2c1, 400 * 1000);
|
||||||
|
|
||||||
|
|
@ -40,7 +38,6 @@ void DisplayHandler::setup() {
|
||||||
display->setOrientation(0);
|
display->setOrientation(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DisplayHandler::moveCursor(bool dir) {
|
void DisplayHandler::moveCursor(bool dir) {
|
||||||
if (onOutScreen) {
|
if (onOutScreen) {
|
||||||
if (cursorClick == 0) {
|
if (cursorClick == 0) {
|
||||||
|
|
@ -74,8 +71,10 @@ void DisplayHandler::moveCursor(bool dir) {
|
||||||
outputs[currentOut]->modifierSelectionIndex = 0;
|
outputs[currentOut]->modifierSelectionIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputs[currentOut]->modifierSelectionIndex > std::size(MOD_TYPES) - 1) {
|
if (outputs[currentOut]->modifierSelectionIndex >
|
||||||
outputs[currentOut]->modifierSelectionIndex = std::size(MOD_TYPES) - 1;
|
std::size(MOD_TYPES) - 1) {
|
||||||
|
outputs[currentOut]->modifierSelectionIndex =
|
||||||
|
std::size(MOD_TYPES) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (currentScreen == 2) { // shape control
|
} else if (currentScreen == 2) { // shape control
|
||||||
|
|
@ -167,11 +166,119 @@ void DisplayHandler::moveCursor(bool dir) {
|
||||||
} else if (currentScreen == 7) { // STICKY
|
} else if (currentScreen == 7) { // STICKY
|
||||||
outputs[currentOut]->sticky ^= true;
|
outputs[currentOut]->sticky ^= true;
|
||||||
|
|
||||||
} else if (currentScreen == 8) { // MUTE
|
} 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 > 7) {
|
||||||
|
matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx < 0) {
|
||||||
|
matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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
|
||||||
|
if (dir == 1) {
|
||||||
|
matrix.slots[outputs[currentOut]->slotIdx1].amount++;
|
||||||
|
} else {
|
||||||
|
matrix.slots[outputs[currentOut]->slotIdx1].amount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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
|
||||||
|
if (dir == 1) {
|
||||||
|
matrix.slots[outputs[currentOut]->slotIdx2].amount++;
|
||||||
|
} else {
|
||||||
|
matrix.slots[outputs[currentOut]->slotIdx2].amount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (currentScreen == 17) { // CV2 INV
|
||||||
|
matrix.slots[outputs[currentOut]->slotIdx2].inverted ^= true;
|
||||||
|
|
||||||
|
} else if (currentScreen == 18) { // MUTE
|
||||||
|
|
||||||
outputs[currentOut]->editing = 1;
|
outputs[currentOut]->editing = 1;
|
||||||
outputs[currentOut]->isEnabled ^= true;
|
outputs[currentOut]->isEnabled ^= true;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,7 +305,6 @@ void DisplayHandler::moveCursor(bool dir) {
|
||||||
updateScreen = 1;
|
updateScreen = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DisplayHandler::handleClick() {
|
void DisplayHandler::handleClick() {
|
||||||
cursorClick ^= true;
|
cursorClick ^= true;
|
||||||
|
|
||||||
|
|
@ -230,14 +336,12 @@ void DisplayHandler::handleClick() {
|
||||||
} else if (cursorPosition == 9) { // PLAY/PAUSE BUTTON
|
} else if (cursorPosition == 9) { // PLAY/PAUSE BUTTON
|
||||||
PLAY ^= true;
|
PLAY ^= true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScreen = 1;
|
updateScreen = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DisplayHandler::render() {
|
void DisplayHandler::render() {
|
||||||
if (updateScreen) {
|
if (updateScreen) {
|
||||||
display->clear();
|
display->clear();
|
||||||
|
|
@ -254,23 +358,22 @@ void DisplayHandler::render() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DisplayHandler::renderMainPage() {
|
void DisplayHandler::renderMainPage() {
|
||||||
std::string bpm_string = "BPM: " + std::to_string(BPM);
|
std::string bpm_string = "BPM: " + std::to_string(BPM);
|
||||||
|
|
||||||
if (cursorPosition == 0) {
|
if (cursorPosition == 0) {
|
||||||
if (cursorClick == 1) {
|
if (cursorClick == 1) {
|
||||||
pico_ssd1306::fillRect(display, 0, 0, 100, 18);
|
pico_ssd1306::fillRect(display, 0, 0, 100, 18);
|
||||||
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2, pico_ssd1306::WriteMode::SUBTRACT);
|
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2,
|
||||||
|
pico_ssd1306::WriteMode::SUBTRACT);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
pico_ssd1306::drawRect(display, 0, 0, 100, 18, pico_ssd1306::WriteMode::ADD);
|
pico_ssd1306::drawRect(display, 0, 0, 100, 18,
|
||||||
|
pico_ssd1306::WriteMode::ADD);
|
||||||
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2);
|
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2);
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2);
|
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t cursor_x = 2;
|
uint8_t cursor_x = 2;
|
||||||
|
|
@ -283,34 +386,37 @@ void DisplayHandler::renderMainPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursorPosition == i) {
|
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::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);
|
pico_ssd1306::drawText(display, font_12x16, std::to_string(i).c_str(),
|
||||||
|
cursor_x, cursor_y);
|
||||||
cursor_x += 30;
|
cursor_x += 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (cursorPosition == 9) { // PLAY BUTTON
|
if (cursorPosition == 9) { // PLAY BUTTON
|
||||||
pico_ssd1306::fillRect(display, 120, 8, 130, 18, pico_ssd1306::WriteMode::ADD);
|
pico_ssd1306::fillRect(display, 120, 8, 130, 18,
|
||||||
pico_ssd1306::drawText(display, font_8x8, PLAY ? ">" : "#", 120, 10, pico_ssd1306::WriteMode::SUBTRACT);
|
pico_ssd1306::WriteMode::ADD);
|
||||||
|
pico_ssd1306::drawText(display, font_8x8, PLAY ? ">" : "#", 120, 10,
|
||||||
|
pico_ssd1306::WriteMode::SUBTRACT);
|
||||||
} else {
|
} else {
|
||||||
pico_ssd1306::drawText(display, font_8x8, PLAY ? ">" : "#", 120, 10);
|
pico_ssd1306::drawText(display, font_8x8, PLAY ? ">" : "#", 120, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursorPosition == 10) { // OPTIONS BUTTON
|
if (cursorPosition == 10) { // OPTIONS BUTTON
|
||||||
pico_ssd1306::fillRect(display, 120, 28, 130, 38, pico_ssd1306::WriteMode::ADD);
|
pico_ssd1306::fillRect(display, 120, 28, 130, 38,
|
||||||
pico_ssd1306::drawText(display, font_8x8, "*", 120, 30, pico_ssd1306::WriteMode::SUBTRACT);
|
pico_ssd1306::WriteMode::ADD);
|
||||||
|
pico_ssd1306::drawText(display, font_8x8, "*", 120, 30,
|
||||||
|
pico_ssd1306::WriteMode::SUBTRACT);
|
||||||
} else {
|
} else {
|
||||||
pico_ssd1306::drawText(display, font_8x8, "*", 120, 30);
|
pico_ssd1306::drawText(display, font_8x8, "*", 120, 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DisplayHandler::renderOutPage() {
|
void DisplayHandler::renderOutPage() {
|
||||||
uint8_t visualOut = currentOut + 1;
|
uint8_t visualOut = currentOut + 1;
|
||||||
std::string title = std::to_string(visualOut) + "| " + out_pages[currentScreen];
|
std::string title =
|
||||||
|
std::to_string(visualOut) + "| " + out_pages[currentScreen];
|
||||||
pico_ssd1306::drawText(display, font_12x16, title.c_str(), 1, 2);
|
pico_ssd1306::drawText(display, font_12x16, title.c_str(), 1, 2);
|
||||||
|
|
||||||
std::string param_string;
|
std::string param_string;
|
||||||
|
|
@ -319,7 +425,8 @@ void DisplayHandler::renderOutPage() {
|
||||||
param_string = "<--";
|
param_string = "<--";
|
||||||
|
|
||||||
} else if (currentScreen == 1) { // modifier screen
|
} else if (currentScreen == 1) { // modifier screen
|
||||||
uint8_t modifier_selection_index = outputs[currentOut]->modifierSelectionIndex;
|
uint8_t modifier_selection_index =
|
||||||
|
outputs[currentOut]->modifierSelectionIndex;
|
||||||
param_string = MODIFIER_STRINGS[modifier_selection_index];
|
param_string = MODIFIER_STRINGS[modifier_selection_index];
|
||||||
|
|
||||||
} else if (currentScreen == 2) { // shape screen
|
} else if (currentScreen == 2) { // shape screen
|
||||||
|
|
@ -340,15 +447,59 @@ void DisplayHandler::renderOutPage() {
|
||||||
} else if (currentScreen == 7) { // STICKY Screen
|
} else if (currentScreen == 7) { // STICKY Screen
|
||||||
param_string = outputs[currentOut]->sticky ? "ON" : "OFF";
|
param_string = outputs[currentOut]->sticky ? "ON" : "OFF";
|
||||||
|
|
||||||
} else if (currentScreen == 8) { // Mute Screen
|
} 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
|
||||||
|
param_string = std::to_string(
|
||||||
|
matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx + 1);
|
||||||
|
|
||||||
|
} 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
|
||||||
|
param_string = std::to_string(
|
||||||
|
matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx + 1);
|
||||||
|
|
||||||
|
} 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";
|
param_string = outputs[currentOut]->isEnabled ? "ON" : "OFF";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursorClick) {
|
if (cursorClick) {
|
||||||
pico_ssd1306::fillRect(display, 1, 42, 50, 60);
|
pico_ssd1306::fillRect(display, 1, 42, 50, 60);
|
||||||
pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45, pico_ssd1306::WriteMode::SUBTRACT);
|
pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45,
|
||||||
|
pico_ssd1306::WriteMode::SUBTRACT);
|
||||||
} else {
|
} else {
|
||||||
pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45);
|
pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,22 @@
|
||||||
// EncoderHandler.cpp
|
// EncoderHandler.cpp
|
||||||
#include "pico/stdlib.h"
|
|
||||||
#include "EncoderHandler.h"
|
#include "EncoderHandler.h"
|
||||||
#include "DisplayHandler.h"
|
#include "DisplayHandler.h"
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include <string>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include "hardware/pio.h"
|
#include "hardware/pio.h"
|
||||||
|
#include "pico/stdlib.h"
|
||||||
#include "quadrature_encoder.pio.h"
|
#include "quadrature_encoder.pio.h"
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
static EncoderHandler *self = nullptr;
|
||||||
|
|
||||||
static EncoderHandler* self = nullptr;
|
EncoderHandler::EncoderHandler(DisplayHandler *display_handler) {
|
||||||
|
|
||||||
|
|
||||||
EncoderHandler::EncoderHandler(DisplayHandler* display_handler) {
|
|
||||||
this->display_handler = display_handler;
|
this->display_handler = display_handler;
|
||||||
self = this;
|
self = this;
|
||||||
encoder_pos = 0;
|
encoder_pos = 0;
|
||||||
button_pressed = 0;
|
button_pressed = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EncoderHandler::gpio_callback(uint gpio, uint32_t events) {
|
void EncoderHandler::gpio_callback(uint gpio, uint32_t events) {
|
||||||
uint64_t now = to_us_since_boot(get_absolute_time());
|
uint64_t now = to_us_since_boot(get_absolute_time());
|
||||||
static uint64_t last_sw_time = 0;
|
static uint64_t last_sw_time = 0;
|
||||||
|
|
@ -39,7 +36,8 @@ void EncoderHandler::setup() {
|
||||||
gpio_set_dir(ENCODER_SW_PIN, GPIO_IN);
|
gpio_set_dir(ENCODER_SW_PIN, GPIO_IN);
|
||||||
gpio_pull_up(ENCODER_SW_PIN);
|
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,
|
||||||
|
&EncoderHandler::gpio_callback);
|
||||||
|
|
||||||
PIO pio = pio0;
|
PIO pio = pio0;
|
||||||
uint offset = pio_add_program(pio, &quadrature_encoder_program);
|
uint offset = pio_add_program(pio, &quadrature_encoder_program);
|
||||||
|
|
|
||||||
105
src/Gate.cpp
105
src/Gate.cpp
|
|
@ -1,12 +1,20 @@
|
||||||
// Gate.cpp
|
// Gate.cpp
|
||||||
#include "Gate.h"
|
#include "Gate.h"
|
||||||
|
#include "Mod.h"
|
||||||
|
#include "Settings.h"
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include "hardware/pwm.h"
|
#include "hardware/pwm.h"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
Gate::Gate(uint8_t pin) : Output(pin) {
|
|
||||||
|
Gate::Gate(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2) : Output(pin, idx, slotIdx1, slotIdx2) {
|
||||||
this->pin = pin;
|
this->pin = pin;
|
||||||
|
this->idx = idx;
|
||||||
|
this->slotIdx1 = slotIdx1;
|
||||||
|
this->slotIdx2 = slotIdx2;
|
||||||
|
|
||||||
state = 0;
|
state = 0;
|
||||||
|
|
||||||
editing = 0;
|
editing = 0;
|
||||||
|
|
@ -21,6 +29,49 @@ Gate::Gate(uint8_t pin) : Output(pin) {
|
||||||
|
|
||||||
p = 100; // probability of a pulse
|
p = 100; // probability of a pulse
|
||||||
level = 100;
|
level = 100;
|
||||||
|
|
||||||
|
modDest1 = (idx + 1) % 8;
|
||||||
|
modDest2 = (idx + 2) % 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
void Gate::setLen(uint32_t currentPeriod) {
|
||||||
|
|
@ -137,6 +188,9 @@ void Gate::setDiv(uint8_t modifier_selecton_index) {
|
||||||
divideMode = 1;
|
divideMode = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->triggerCount = 0;
|
||||||
|
this->lastTriggerTick = 0xFFFFFFFF;
|
||||||
|
|
||||||
setWidth(this->width);
|
setWidth(this->width);
|
||||||
// this is called in width, check if needed still?
|
// this is called in width, check if needed still?
|
||||||
calculatePulseWidth();
|
calculatePulseWidth();
|
||||||
|
|
@ -177,11 +231,9 @@ void Gate::calculatePulseWidth() {
|
||||||
pulseWidthTicks = 0;
|
pulseWidthTicks = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If tickInterval is 96 and width is 50, pulseWidthTicks becomes 48
|
|
||||||
this->pulseWidthTicks =
|
this->pulseWidthTicks =
|
||||||
(uint32_t)((float)this->tickInterval * (this->width / 100.0f));
|
(uint32_t)((float)this->tickInterval * (this->width / 100.0f));
|
||||||
|
|
||||||
// Safety: ensure a pulse is at least 1 tick long if width > 0
|
|
||||||
if (this->width > 0 && this->pulseWidthTicks == 0) {
|
if (this->width > 0 && this->pulseWidthTicks == 0) {
|
||||||
this->pulseWidthTicks = 1;
|
this->pulseWidthTicks = 1;
|
||||||
}
|
}
|
||||||
|
|
@ -195,8 +247,15 @@ void Gate::turnOn() {
|
||||||
if (MASTER_TICK != lastTriggerTick) {
|
if (MASTER_TICK != lastTriggerTick) {
|
||||||
lastTriggerTick = MASTER_TICK;
|
lastTriggerTick = MASTER_TICK;
|
||||||
|
|
||||||
if (p < 100 && (rand() % 100) + 1 > p) {
|
float baseP = (float)this->p;
|
||||||
scheduledTick = 0xFFFFFFFF; // ignore interval
|
|
||||||
|
float effectiveP = baseP + (this->pMod * 100.0f);
|
||||||
|
|
||||||
|
if (effectiveP > 100.0f) effectiveP = 100.0f;
|
||||||
|
if (effectiveP < 0.0f) effectiveP = 0.0f;
|
||||||
|
|
||||||
|
if ((rand() % 100) + 1 > (uint8_t)effectiveP) {
|
||||||
|
scheduledTick = 0xFFFFFFFF;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,17 +273,20 @@ void Gate::turnOn() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MASTER_TICK == scheduledTick && !state) {
|
if (MASTER_TICK >= scheduledTick && !state) {
|
||||||
state = 1;
|
state = 1;
|
||||||
startTick = MASTER_TICK;
|
startTick = MASTER_TICK;
|
||||||
startTimeUs = time_us_64();
|
startTimeUs = time_us_64();
|
||||||
|
scheduledTick = 0xFFFFFFFF;
|
||||||
currentRandomVal = (float)rand() / (float)RAND_MAX;
|
currentRandomVal = (float)rand() / (float)RAND_MAX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gate::update() {
|
void Gate::update() {
|
||||||
if (!state && !sticky)
|
if (!state && !sticky) {
|
||||||
|
lastOutVal = 0.0f;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t now = time_us_64();
|
uint64_t now = time_us_64();
|
||||||
uint32_t elapsedUs = (uint32_t)(now - startTimeUs);
|
uint32_t elapsedUs = (uint32_t)(now - startTimeUs);
|
||||||
|
|
@ -289,8 +351,33 @@ void Gate::update() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeAnalog((outVal * 1023.0f) * ((float)level / 100));
|
this->lastOutVal = outVal;
|
||||||
}
|
|
||||||
|
// handle width mod
|
||||||
|
float effectiveWidth = (float)width + (widthMod * 100.0f);
|
||||||
|
|
||||||
|
if (effectiveWidth > 100.0f) effectiveWidth = 100.0f;
|
||||||
|
if (effectiveWidth < 1.0f) effectiveWidth = 1.0f;
|
||||||
|
|
||||||
|
double us_per_tick = 625000.0 / (double)BPM;
|
||||||
|
uint32_t modulatedTicks = (uint32_t)((float)this->tickInterval * (effectiveWidth / 100.0f));
|
||||||
|
if (modulatedTicks < 1) modulatedTicks = 1;
|
||||||
|
|
||||||
|
this->pulseDurationUs = (uint32_t)(us_per_tick * (double)modulatedTicks);
|
||||||
|
|
||||||
|
float baseLevel = (float)this->level / 100.0f;
|
||||||
|
|
||||||
|
float normalizedMod = this->levelMod;
|
||||||
|
if (normalizedMod > 1.0f || normalizedMod < -1.0f) {
|
||||||
|
normalizedMod /= 100.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float finalLevel = baseLevel + normalizedMod;
|
||||||
|
|
||||||
|
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::writeAnalog(uint16_t val) { pwm_set_gpio_level(pin, val); }
|
||||||
|
|
||||||
|
|
|
||||||
53
src/Mod.cpp
Normal file
53
src/Mod.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#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 = gates[slots[i].sourceIdx]->lastOutVal;
|
||||||
|
|
||||||
|
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,7 +1,9 @@
|
||||||
#include "Output.h"
|
#include "Output.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
Output::Output(uint8_t pin) {
|
Output::Output(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2) {
|
||||||
this->pin = pin;
|
this->pin = pin;
|
||||||
|
this->idx = idx;
|
||||||
state = 0;
|
state = 0;
|
||||||
isEnabled = false;
|
isEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
66
src/Settings.cpp
Normal file
66
src/Settings.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#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() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/main.cpp
97
src/main.cpp
|
|
@ -1,18 +1,21 @@
|
||||||
#include <cstdint>
|
#include "Settings.h"
|
||||||
#include <cstdio>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "hardware/structs/rosc.h"
|
#include "hardware/structs/rosc.h"
|
||||||
#include "pico/multicore.h"
|
#include "pico/multicore.h"
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/time.h"
|
#include "pico/time.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "globals.h"
|
|
||||||
#include "Gate.h"
|
|
||||||
#include "DisplayHandler.h"
|
#include "DisplayHandler.h"
|
||||||
#include "EncoderHandler.h"
|
#include "EncoderHandler.h"
|
||||||
|
#include "Gate.h"
|
||||||
|
#include "Mod.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
#include "globals.h"
|
||||||
#include "hardware/pwm.h"
|
#include "hardware/pwm.h"
|
||||||
|
|
||||||
|
|
||||||
// Time based operations
|
// Time based operations
|
||||||
struct repeating_timer bpm_timer = {0};
|
struct repeating_timer bpm_timer = {0};
|
||||||
volatile uint8_t BPM = 60;
|
volatile uint8_t BPM = 60;
|
||||||
|
|
@ -20,25 +23,25 @@ volatile bool PLAY = true;
|
||||||
volatile uint32_t period_us = 0;
|
volatile uint32_t period_us = 0;
|
||||||
volatile uint32_t MASTER_TICK;
|
volatile uint32_t MASTER_TICK;
|
||||||
|
|
||||||
|
ModMatrix matrix;
|
||||||
|
|
||||||
// Initialize Outputs
|
// Initialize Outputs
|
||||||
Gate out1(OUT_1_PIN);
|
Gate out1(OUT_1_PIN, 0, 0, 1);
|
||||||
Gate out2(OUT_2_PIN);
|
Gate out2(OUT_2_PIN, 1, 2, 3);
|
||||||
Gate out3(OUT_3_PIN);
|
Gate out3(OUT_3_PIN, 2, 4, 5);
|
||||||
Gate out4(OUT_4_PIN);
|
Gate out4(OUT_4_PIN, 3, 6, 7);
|
||||||
Gate out5(OUT_5_PIN);
|
Gate out5(OUT_5_PIN, 4, 8, 9);
|
||||||
Gate out6(OUT_6_PIN);
|
Gate out6(OUT_6_PIN, 5, 10, 11);
|
||||||
Gate out7(OUT_7_PIN);
|
Gate out7(OUT_7_PIN, 6, 12, 13);
|
||||||
Gate out8(OUT_8_PIN);
|
Gate out8(OUT_8_PIN, 7, 14, 15);
|
||||||
|
|
||||||
static Gate* outputs[] = {&out1, &out2, &out3, &out4, &out5, &out6, &out7, &out8};
|
|
||||||
|
|
||||||
|
static Gate *outputs[] = {&out1, &out2, &out3, &out4,
|
||||||
|
&out5, &out6, &out7, &out8};
|
||||||
|
|
||||||
// Initialize Handlers
|
// Initialize Handlers
|
||||||
static DisplayHandler display_handler(outputs);
|
static DisplayHandler display_handler(outputs);
|
||||||
static EncoderHandler encoder_handler(&display_handler);
|
static EncoderHandler encoder_handler(&display_handler);
|
||||||
|
|
||||||
|
|
||||||
bool timer_callback(struct repeating_timer *t) {
|
bool timer_callback(struct repeating_timer *t) {
|
||||||
if (PLAY == 1) {
|
if (PLAY == 1) {
|
||||||
MASTER_TICK += 1;
|
MASTER_TICK += 1;
|
||||||
|
|
@ -46,19 +49,16 @@ bool timer_callback(struct repeating_timer *t) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void init_timer(uint32_t period_us) {
|
void init_timer(uint32_t period_us) {
|
||||||
cancel_repeating_timer(&bpm_timer);
|
cancel_repeating_timer(&bpm_timer);
|
||||||
add_repeating_timer_us(-(int64_t)period_us, timer_callback, NULL, &bpm_timer);
|
add_repeating_timer_us(-(int64_t)period_us, timer_callback, NULL, &bpm_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void update_period() {
|
void update_period() {
|
||||||
period_us = (uint32_t)(MINUTE_US / (uint32_t)BPM / PPQN);
|
period_us = (uint32_t)(MINUTE_US / (uint32_t)BPM / PPQN);
|
||||||
init_timer(period_us);
|
init_timer(period_us);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void update_BPM(bool up) {
|
void update_BPM(bool up) {
|
||||||
if (up) {
|
if (up) {
|
||||||
BPM++;
|
BPM++;
|
||||||
|
|
@ -68,15 +68,14 @@ void update_BPM(bool up) {
|
||||||
|
|
||||||
update_period();
|
update_period();
|
||||||
|
|
||||||
|
|
||||||
for (auto g : outputs) {
|
for (auto g : outputs) {
|
||||||
g->setWidth(g->width);
|
g->setWidth(g->width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void core1_entry() {
|
void core1_entry() {
|
||||||
multicore_fifo_pop_blocking();
|
multicore_fifo_pop_blocking();
|
||||||
|
multicore_lockout_victim_init();
|
||||||
|
|
||||||
char buffer[32];
|
char buffer[32];
|
||||||
|
|
||||||
|
|
@ -85,6 +84,15 @@ void core1_entry() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void full_save() {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
outputs[i]->pack(globalSettings.configs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(globalSettings.slots, matrix.slots, sizeof(ModSlot) * 16);
|
||||||
|
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
void setup_outs() {
|
void setup_outs() {
|
||||||
|
|
||||||
|
|
@ -100,49 +108,64 @@ void setup_outs() {
|
||||||
pwm_set_gpio_level(g->pin, 0);
|
pwm_set_gpio_level(g->pin, 0);
|
||||||
pwm_set_enabled(slice_num, true);
|
pwm_set_enabled(slice_num, true);
|
||||||
g->setLen(period_us);
|
g->setLen(period_us);
|
||||||
|
g->setupPatches();
|
||||||
}
|
}
|
||||||
|
|
||||||
// manual setup
|
// // manual setup
|
||||||
// out1.setDiv(5);
|
// out1.shape = SQUARE;
|
||||||
// out1.setWidth(80);
|
// out1.setDiv(3);
|
||||||
|
// out1.setWidth(50);
|
||||||
|
// out1.level = 100;
|
||||||
|
//
|
||||||
|
// out2.shape = SINE;
|
||||||
|
// out2.setDiv(14);
|
||||||
|
// out2.setWidth(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void handle_outs() {
|
void handle_outs() {
|
||||||
for (Gate* g: outputs) {
|
matrix.process(outputs, 8);
|
||||||
|
for (Gate *g : outputs) {
|
||||||
g->turnOn();
|
g->turnOn();
|
||||||
g->update();
|
g->update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
// initialize
|
|
||||||
stdio_init_all();
|
stdio_init_all();
|
||||||
|
sleep_ms(200);
|
||||||
|
|
||||||
// Seed random
|
bool loaded_defaults = load();
|
||||||
srand(rosc_hw->randombit);
|
|
||||||
|
|
||||||
// Initialize display and multicore
|
|
||||||
display_handler.setup();
|
display_handler.setup();
|
||||||
multicore_launch_core1(core1_entry);
|
|
||||||
multicore_fifo_push_blocking(1);
|
|
||||||
|
|
||||||
// Initialize Encoder
|
|
||||||
encoder_handler.setup();
|
encoder_handler.setup();
|
||||||
|
|
||||||
setup_outs();
|
setup_outs();
|
||||||
|
|
||||||
|
if (!loaded_defaults) {
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
matrix.slots[i] = globalSettings.slots[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
update_period();
|
||||||
|
srand(rosc_hw->randombit);
|
||||||
|
|
||||||
bool lastPlayState = false;
|
bool lastPlayState = false;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
encoder_handler.update();
|
encoder_handler.update();
|
||||||
|
|
||||||
if (PLAY) {
|
if (PLAY) {
|
||||||
handle_outs();
|
handle_outs();
|
||||||
} else {
|
} else {
|
||||||
for (Gate* g: outputs) {
|
for (Gate *g : outputs) {
|
||||||
g->turnOff();
|
g->turnOff();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue