Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: awonak/HagiwoModulove
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.9.1
Choose a base ref
...
head repository: awonak/HagiwoModulove
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.10.0
Choose a head ref
  • 4 commits
  • 15 files changed
  • 1 contributor

Commits on Aug 1, 2024

  1. Update A-RYTH-MATIK to remove git submodules in favor of locally inst…

    …alled libmodulove. Update README.
    awonak committed Aug 1, 2024
    Copy the full SHA
    d91ede2 View commit details
  2. Copy the full SHA
    6279ae2 View commit details

Commits on Jan 22, 2025

  1. Euclidean Rhythm (#22)

    * Initial commit of Euclidean Rhythm mvp.
    
    * small optimizations and cleanup
    
    * Updated script to replace SimpleRotary with EncoderButton in experimental version of libModulove. Also fixed formatting.
    
    * Added padding functionality
    
    * Added ability to save state between power cycling the module.
    
    * Updated module UI docs.
    
    * Only call updatePattern() when chage actually modifies the pattern. Apply formatting.
    
    * Update documentation to include Euclidean firmware.
    awonak authored Jan 22, 2025

    Verified

    This commit was created on github.com and signed with GitHub’s verified signature.
    Copy the full SHA
    c87ccd4 View commit details
  2. Update Firmware for new EncoderButton library (#23)

    * Initial commit of Euclidean Rhythm mvp.
    
    * small optimizations and cleanup
    
    * Updated script to replace SimpleRotary with EncoderButton in experimental version of libModulove. Also fixed formatting.
    
    * Added padding functionality
    
    * Added ability to save state between power cycling the module.
    
    * Updated module UI docs.
    
    * Only call updatePattern() when chage actually modifies the pattern. Apply formatting.
    
    * Update documentation to include Euclidean firmware.
    
    * Update TimeBandit firmware for new EncoderButton lib.
    
    * Update Uncertainty for new EncoderButton lib.
    
    * Update BitGarden firmware for new EncoderButton lib.
    awonak authored Jan 22, 2025

    Verified

    This commit was created on github.com and signed with GitHub’s verified signature.
    Copy the full SHA
    d4a7061 View commit details
3 changes: 3 additions & 0 deletions .github/workflows/build_release.yml
Original file line number Diff line number Diff line change
@@ -34,6 +34,9 @@ jobs:
arduino-cli core update-index
arduino-cli core install arduino:avr
arduino-cli core install lgt8fx:avr --additional-urls https://raw.githubusercontent.com/dbuezas/lgt8fx/master/package_lgt8fx_index.json
arduino-cli config init
arduino-cli config set library.enable_unsafe_install true
arduino-cli lib install --git-url https://github.com/awonak/libModulove.git
arduino-cli lib install "Adafruit GFX Library"
arduino-cli lib install "Adafruit SSD1306"
arduino-cli lib install SimpleRotary
9 changes: 0 additions & 9 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
[submodule "pages/themes/terminal"]
path = pages/themes/terminal
url = https://github.com/panr/hugo-theme-terminal.git
[submodule "A-RYTH-MATIK/BitGarden/src/libmodulove"]
path = A-RYTH-MATIK/BitGarden/src/libmodulove
url = https://github.com/awonak/libmodulove.git
[submodule "A-RYTH-MATIK/Uncertainty/src/libmodulove"]
path = A-RYTH-MATIK/Uncertainty/src/libmodulove
url = https://github.com/awonak/libmodulove.git
[submodule "A-RYTH-MATIK/TimeBandit/src/libmodulove"]
path = A-RYTH-MATIK/TimeBandit/src/libmodulove
url = https://github.com/awonak/libmodulove.git
100 changes: 43 additions & 57 deletions A-RYTH-MATIK/BitGarden/BitGarden.ino
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@
#define PERCENT 0x25

// Include the Modulove hardware library.
#include "src/libmodulove/arythmatik.h"
#include <arythmatik.h>

// Script specific output class.
#include "output.h"
@@ -131,6 +131,10 @@ void setup() {
hw.config.ReverseEncoder = true;
#endif

hw.eb.setClickHandler(HandleShortPress);
hw.eb.setLongPressHandler(HandleLongPress);
hw.eb.setEncoderHandler(HandleRotate);

// Initialize the A-RYTH-MATIK peripherials.
hw.Init();

@@ -175,24 +179,6 @@ void loop() {
}
}

// Read the encoder button press event.
Encoder::PressType press = hw.encoder.Pressed();

// Short button press. Change editable parameter.
if (press == Encoder::PRESS_SHORT) {
HandleShortPress();
}
// Long button press. Change menu page.
else if (press == Encoder::PRESS_LONG) {
HandleLongPress();
}

// Read encoder for a change in direction and update the selected page or
// parameter.
(page_select)
? UpdatePage(hw.encoder.Rotate())
: UpdateParameter(hw.encoder.Rotate());

// Render any new UI changes to the OLED display.
UpdateDisplay();
}
@@ -253,7 +239,7 @@ void Reset() {
}

// Short press handler.
void HandleShortPress() {
void HandleShortPress(EncoderButton &eb) {
// If we are in page select mode, set current page.
if (page_select) {
page_select = false;
@@ -293,7 +279,7 @@ void HandleShortPress() {

// Long press handler will toggle between page select mode and parameter edit
// mode for the current page.
void HandleLongPress() {
void HandleLongPress(EncoderButton &eb) {
// Toggle between menu page select mode.
if (!page_select) {
page_select = true;
@@ -308,19 +294,29 @@ void HandleLongPress() {
update_display = true;
}

void HandleRotate(EncoderButton &eb) {
// Read encoder for a change in direction and update the selected page or
// parameter.
int dir = eb.increment();
(page_select)
? UpdatePage(dir)
: UpdateParameter(dir);
}

// When in select page mode, scroll through menu pages.
void UpdatePage(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_UNCHANGED)
return;
else if (dir == Encoder::DIRECTION_INCREMENT && selected_page < PAGE_LAST - 1)
void UpdatePage(int dir) {
if (dir == 1 && selected_page < PAGE_LAST - 1) {
selected_page = static_cast<MenuPage>((selected_page + 1) % PAGE_LAST);
else if (dir == Encoder::DIRECTION_DECREMENT && selected_page > PAGE_MAIN)
update_display = true;
}
else if (dir == -1 && selected_page > PAGE_MAIN) {
selected_page = static_cast<MenuPage>((selected_page - 1) % PAGE_LAST);
update_display = true;
update_display = true;
}
}

// Update the current selected parameter with the current movement of the encoder.
void UpdateParameter(Encoder::Direction dir) {
void UpdateParameter(int dir) {
if (selected_page == PAGE_MAIN) {
if (selected_param == PARAM_SEED) UpdateSeed(dir);
if (selected_param == PARAM_LENGTH) UpdateLength(dir);
@@ -334,34 +330,27 @@ void UpdateParameter(Encoder::Direction dir) {
}

// Select seed from the previous seeds in the packet or add new random seed to packet.
void UpdateSeed(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_UNCHANGED)
return;
else if (dir == Encoder::DIRECTION_INCREMENT)
packet.NextSeed();
else if (dir == Encoder::DIRECTION_DECREMENT)
packet.PrevSeed();
void UpdateSeed(int dir) {
if (dir == 1) packet.NextSeed();
else if (dir == -1) packet.PrevSeed();
update_display = true;
state_changed = true;
}

// Adjust the step length for the given input direction (1=increment, 2=decrement).
void UpdateLength(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_INCREMENT && state.step_length <= MAX_LENGTH) {
void UpdateLength(int dir) {
if (dir == 1 && state.step_length <= MAX_LENGTH) {
SetLength(++state.step_length);
} else if (dir == Encoder::DIRECTION_DECREMENT && state.step_length >= MIN_LENGTH) {
} else if (dir == -1 && state.step_length >= MIN_LENGTH) {
SetLength(--state.step_length);
}
}

// Change the current output mode selection.
void UpdateMode(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_UNCHANGED) return;

if (dir == Encoder::DIRECTION_INCREMENT && state.mode < MODE_LAST - 1) {
void UpdateMode(int dir) {
if (dir == 1 && state.mode < MODE_LAST - 1) {
state.mode = static_cast<Mode>(state.mode + 1);

} else if (dir == Encoder::DIRECTION_DECREMENT && state.mode > 0) {
} else if (dir == -1 && state.mode > 0) {
state.mode = static_cast<Mode>(state.mode - 1);
}
// Update the mode for all outputs.
@@ -372,31 +361,28 @@ void UpdateMode(Encoder::Direction dir) {
state_changed = true;
}

void UpdateProbability(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_UNCHANGED) return;
void UpdateProbability(int dir) {
if (prob_param == PROB_OUTPUT) UpdateOutput(dir);
if (prob_param == PROB_PERCENTAGE) UpdatePercentage(dir);
}

void UpdateOutput(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_INCREMENT && selected_out < OUTPUT_COUNT - 1)
void UpdateOutput(int dir) {
if (dir == 1 && selected_out < OUTPUT_COUNT - 1)
selected_out++;
if (dir == Encoder::DIRECTION_DECREMENT && selected_out > 0)
if (dir == -1 && selected_out > 0)
selected_out--;
update_display = true;
}

void UpdatePercentage(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_INCREMENT) outputs[selected_out].IncProb();
if (dir == Encoder::DIRECTION_DECREMENT) outputs[selected_out].DecProb();
void UpdatePercentage(int dir) {
if (dir == 1) outputs[selected_out].IncProb();
if (dir == -1) outputs[selected_out].DecProb();
update_display = true;
state_changed = true;
}

// Edit the current seed.
void EditSeed(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_UNCHANGED) return;

void EditSeed(int dir) {
int change;
if (seed_index == 0)
change = 0x1000;
@@ -407,9 +393,9 @@ void EditSeed(Encoder::Direction dir) {
else if (seed_index == 3)
change = 0x0001;

if (dir == Encoder::DIRECTION_INCREMENT)
if (dir == 1)
temp_seed += change;
else if (dir == Encoder::DIRECTION_DECREMENT)
else if (dir == -1)
temp_seed -= change;

packet.UpdateSeed(temp_seed);
2 changes: 1 addition & 1 deletion A-RYTH-MATIK/BitGarden/output.h
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
#define OUTPUT_H

// Include the Modulove hardware library.
#include "src/libmodulove/arythmatik.h"
#include <arythmatik.h>

using namespace modulove;

1 change: 0 additions & 1 deletion A-RYTH-MATIK/BitGarden/src/libmodulove
Submodule libmodulove deleted from 690b04
321 changes: 321 additions & 0 deletions A-RYTH-MATIK/Euclidean/Euclidean.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
/**
* @file Euclidean.ino
* @author Adam Wonak (https://github.com/awonak/)
* @brief Euclidean rhythm generator inspired by Pam's Pro Workout.
* @version 0.1
* @date 2025-01-05
*
* @copyright Copyright (c) 2025
*
* ENCODER:
* Short press to change between selecting a prameter and editing the parameter.
* Hold & rotate to change current output channel pattern.
*
* CLK: Clock input used to advance the patterns.
*
* RST: Trigger this input to reset all patterns.
*
*/

// Include the Modulove hardware library.
#include <arythmatik.h>

// Script specific helper libraries.
#include "pattern.h"
#include "save_state.h"

// Graphics identifiers
#define RIGHT_TRIANGLE 0x10
#define LEFT_TRIANGLE 0x11

// 'pencil', 12x12px
const unsigned char pencil_gfx [] PROGMEM = {
0x00, 0x00, 0x00, 0xc0, 0x01, 0xa0, 0x02, 0x60, 0x04, 0x40, 0x08, 0x80, 0x11, 0x00, 0x22, 0x00,
0x64, 0x00, 0x78, 0x00, 0x70, 0x00, 0x00, 0x00
};

// Flag for enabling debug print to serial monitoring output.
// Note: this affects performance and locks LED 4 & 5 on HIGH.
// #define DEBUG

// Flag for reversing the encoder direction.
// #define ENCODER_REVERSED

using namespace modulove;
using namespace arythmatik;

const byte MAX_STEPS = 32;
const byte TRIGGER_DURATION_MS = 50;
const byte UI_TRIGGER_DURATION_MS = 200;

// Declare A-RYTH-MATIK hardware variable.
Arythmatik hw;
Pattern patterns[OUTPUT_COUNT];
byte selected_out = 0;
long last_clock_input = 0;
bool update_display = true;
bool trigger_active = false;
bool ui_trigger_active = false;

// Enum constants for selecting an editable attribute.
enum Parameter {
PARAM_STEPS,
PARAM_HITS,
PARAM_OFFSET,
PARAM_PADDING,
PARAM_LAST,
};
Parameter selected_param = PARAM_STEPS;

enum Mode {
MODE_SELECT,
MODE_EDIT,
MODE_LAST,
};
Mode selected_mode = MODE_SELECT;

void setup() {
// Only enable Serial monitoring if DEBUG is defined.
// Note: this affects performance and locks LED 4 & 5 on HIGH.
// #ifdef DEBUG
// Serial.begin(9600);
// Serial.println("DEBUG READY");
// #endif

// Set up encoder parameters
hw.eb.setEncoderHandler(UpdateRotate);
hw.eb.setClickHandler(UpdatePress);
hw.eb.setEncoderPressedHandler(UpdatePressedRotation);

// Initialize the A-RYTH-MATIK peripherials.
hw.Init();

// Initial patterns.
InitState(patterns);

// Display each clock division on the OLED.
UpdateDisplay();
}

void loop() {
// Read cv inputs and process encoder state to determine state for this
// loop.
hw.ProcessInputs();

// Advance the patterns on CLK input
if (hw.clk.State() == DigitalInput::STATE_RISING) {
for (int i = 0; i < OUTPUT_COUNT; i++) {
if (patterns[i].NextStep() == 1) {
hw.outputs[i].High();
}
}
last_clock_input = millis();
trigger_active = true;
ui_trigger_active = true;
update_display = true;
}

// Turn off all outputs after trigger duration.
if (trigger_active && millis() > last_clock_input + TRIGGER_DURATION_MS) {
for (int i = 0; i < OUTPUT_COUNT; i++) {
hw.outputs[i].Low();
}
trigger_active = false;
}

// Turn off current step animation after ui trigger duration.
if (ui_trigger_active && millis() > last_clock_input + UI_TRIGGER_DURATION_MS) {
ui_trigger_active = false;
update_display = true;
}

// Reset all patterns to the first pattern step on RST input.
if (hw.rst.State() == DigitalInput::STATE_RISING) {
for (int i = 0; i < OUTPUT_COUNT; i++) {
patterns[i].Reset();
}
update_display = true;
}

// Call update display to refresh the screen if required.
if (update_display) UpdateDisplay();
}

void UpdatePress(EncoderButton &eb) {
// If leaving EDIT mode, save state.
if (selected_mode == MODE_EDIT) {
SaveChanges(patterns);
}

// Change current mode.
selected_mode = static_cast<Mode>((selected_mode + 1) % MODE_LAST);
update_display = true;
}

void UpdateRotate(EncoderButton &eb) {
// Convert the direction to an integer equivalent value.
int dir = eb.increment() > 0 ? 1 : -1;

if (selected_mode == MODE_SELECT) {
if (static_cast<Parameter>(selected_param) == 0 && dir < 0) {
selected_param = static_cast<Parameter>(PARAM_LAST - 1);
} else {
selected_param = static_cast<Parameter>((selected_param + dir) % PARAM_LAST);
}
} else if (selected_mode == MODE_EDIT) {
// Handle rotation for current parameter.
switch (selected_param) {
case PARAM_STEPS:
patterns[selected_out].ChangeSteps(dir);
break;
case PARAM_HITS:
patterns[selected_out].ChangeHits(dir);
break;
case PARAM_OFFSET:
patterns[selected_out].ChangeOffset(dir);
break;
case PARAM_PADDING:
patterns[selected_out].ChangePadding(dir);
break;
}
}
update_display = true;
}

void UpdatePressedRotation(EncoderButton &eb) {
ChangeSelectedOutput(eb.increment());
}

void ChangeSelectedOutput(int dir) {
switch (dir) {
case -1:
if (selected_out > 0) --selected_out;
update_display = true;
break;
case 1:
if (selected_out < OUTPUT_COUNT - 1) ++selected_out;
update_display = true;
break;
}
}

void UpdateDisplay() {
if (!update_display) return;
update_display = false;
hw.display.clearDisplay();

// Draw page title.
hw.display.setTextSize(0);
hw.display.setCursor(37, 0);
hw.display.println(F("EUCLIDEAN"));
hw.display.drawFastHLine(0, 10, SCREEN_WIDTH, WHITE);

DisplayMode();
DisplaySelectedMode();
DisplayChannels();
DisplayPattern();

hw.display.display();
}

void DisplayMode() {
hw.display.setCursor(26, 18);
hw.display.setTextSize(0);
if (selected_param == PARAM_STEPS) {
hw.display.print(F("Steps: "));
hw.display.print(String(patterns[selected_out].steps));
} else if (selected_param == PARAM_HITS) {
hw.display.print(F("Hits: "));
hw.display.print(String(patterns[selected_out].hits));
} else if (selected_param == PARAM_OFFSET) {
hw.display.print(F("Offset: "));
hw.display.print(String(patterns[selected_out].offset));
} else if (selected_param == PARAM_PADDING) {
hw.display.print(F("Padding: "));
hw.display.print(String(patterns[selected_out].padding));
}
}

void DisplaySelectedMode() {
switch (selected_mode) {
case MODE_SELECT:
hw.display.drawChar(116, 18, LEFT_TRIANGLE, 1, 0, 1);
break;
case MODE_EDIT:
hw.display.drawBitmap(112, 16, pencil_gfx, 12, 12, 1);
break;
}
}

void DisplayChannels() {
hw.display.setCursor(4, 20);
hw.display.setTextSize(2);
hw.display.println(String(selected_out + 1));

int start = 42;
int top = start;
int left = 4;
int boxSize = 4;
int margin = 2;
int wrap = 3;

for (int i = 1; i <= OUTPUT_COUNT; i++) {
// Draw box, fill current step.
(i == selected_out + 1)
? hw.display.fillRect(left, top, boxSize, boxSize, 1)
: hw.display.drawRect(left, top, boxSize, boxSize, 1);

// Advance the draw cursors.
top += boxSize + margin + 1;

// Wrap the box draw cursor if we hit wrap count.
if (i % wrap == 0) {
top = start;
left += boxSize + margin;
}
}
}

void DisplayPattern() {
// Draw boxes for pattern length.
int start = 26;
int top = 30;
int left = start;
int boxSize = 7;
int margin = 2;
int wrap = 8;

int steps = patterns[selected_out].steps;
int offset = patterns[selected_out].offset;
int padding = patterns[selected_out].padding;

for (int i = 0; i < steps + padding; i++) {
// Draw box, fill current step.
switch(patterns[selected_out].GetStep(i)) {
case 1:
hw.display.fillRect(left, top, boxSize, boxSize, 1);
break;
case 0:
hw.display.drawRect(left, top, boxSize, boxSize, 1);
break;
case 2:
hw.display.drawRect(left, top, boxSize, boxSize, 1);
hw.display.drawLine(left+boxSize-1, top, left, top+boxSize-1, 1);
break;
}

// Draw right arrow on current played step.
if (ui_trigger_active && i == patterns[selected_out].current_step) {
hw.display.drawChar(left + 1, top, RIGHT_TRIANGLE, 1, 0, 1);
}

// Advance the draw cursors.
left += boxSize + margin + 1;

// Wrap the box draw cursor if we hit wrap count.
if ((i + 1) % wrap == 0) {
top += boxSize + margin;
left = start;
}
}
}
129 changes: 129 additions & 0 deletions A-RYTH-MATIK/Euclidean/pattern.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#ifndef PATTERN_H
#define PATTERN_H

// Include the Modulove library.
#include <arythmatik.h>

using namespace modulove;

#define MAX_PATTERN_LEN 32

struct PatternState {
uint8_t steps;
uint8_t hits;
uint8_t offset;
uint8_t padding;
};

class Pattern {
public:
Pattern() {}
~Pattern() {}

uint8_t steps = 0;
uint8_t hits = 0;
uint8_t offset = 0;
uint8_t padding = 0;
uint8_t current_step = 0;

/**
Initializes the euclidean rhythm pattern with the given attributes.
\param steps total length of the pattern.
\param hits the number of "beats" to distribute evenly across the pattern steps.
\param offset rotation of the pattern's hits.
\param padding additional empty steps added to the pattern.
*/
void Init(PatternState state) {
steps = state.steps;
hits = state.hits;
offset = state.offset;
padding = state.padding;
updatePattern();
}

PatternState GetState() { return {steps, hits, offset, padding}; }

// Advance the euclidean rhythm to the next step in the pattern.
// Returns 1 for hit, 0 for rest, and 2 for padding.
int NextStep() {
current_step =
(current_step < steps + padding - 1) ? current_step + 1 : 0;
return GetStep(current_step);
}

// Returns 1 for hit, 0 for rest, and 2 for padding.
int GetStep(int i) { return pattern_[i]; }

void ChangeSteps(int val) {
if (val == 1 && steps < MAX_PATTERN_LEN) {
steps++;
padding = min(padding, MAX_PATTERN_LEN - steps);
updatePattern();
} else if (val == -1 && steps > 0) {
steps--;
hits = min(hits, steps);
updatePattern();
}
}

void ChangeHits(int val) {
if (val == 1 && hits < steps) {
hits++;
updatePattern();
} else if (val == -1 && hits > 1) {
hits--;
updatePattern();
}
}

void ChangeOffset(int val) {
if (val == 1 && offset < (steps + padding) - 1) {
offset++;
updatePattern();
} else if (val == -1 && offset > 0) {
offset--;
updatePattern();
}
}

void ChangePadding(int val) {
if (val == 1 && padding + steps < MAX_PATTERN_LEN) {
padding++;
updatePattern();
} else if (val == -1 && padding > 0) {
padding--;
offset = min(offset, (padding + steps) - 1);
updatePattern();
}
}

void Reset() { current_step = steps; }

private:
uint8_t pattern_[MAX_PATTERN_LEN];

// Update the euclidean rhythm pattern when attributes change.
void updatePattern() {
// Fill current pattern with "padding" steps, then overwrite with hits
// and rests.
for (int i = 0; i < MAX_PATTERN_LEN; i++) {
pattern_[i] = 2;
}

// Populate the euclidean rhythm pattern according to the current
// instance variables.
int bucket = 0;
pattern_[offset] = 1;
for (int i = 1; i < steps; i++) {
bucket += hits;
if (bucket >= steps) {
bucket -= steps;
pattern_[(i + offset) % (steps + padding)] = 1;
} else {
pattern_[(i + offset) % (steps + padding)] = 0;
}
}
}
};

#endif
64 changes: 64 additions & 0 deletions A-RYTH-MATIK/Euclidean/save_state.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#ifndef SAVE_STATE_H
#define SAVE_STATE_H

#include <EEPROM.h>
#include <arythmatik.h>

#include "pattern.h"

using namespace modulove;
using namespace arythmatik;

// Script state & storage variables.
// Expected version string for this firmware.
const char SCRIPT_NAME[] = "EUCLIDEAN";
const uint8_t SCRIPT_VER = 1;

const PatternState default_pattern = { 16, 4, 0, 0};


struct State {
// Version check.
char script[sizeof(SCRIPT_NAME)];
uint8_t version;
// State variables.
PatternState pattern[OUTPUT_COUNT];
};
State state;

// Save state to EEPROM if state has changed.
void SaveChanges(Pattern *patterns) {
for (int i = 0; i < OUTPUT_COUNT; i++) {
state.pattern[i] = (PatternState){patterns[i].GetState()};
}
EEPROM.put(0, state);
}


// Initialize script state from EEPROM or default values.
void InitState(Pattern *patterns) {
// Read previously put state from EEPROM memory. If it doesn't exist or
// match version, then populate the State struct with default values.
EEPROM.get(0, state);

// Check if the data in memory matches expected values.
if ((strcmp(state.script, SCRIPT_NAME) != 0) || (state.version != SCRIPT_VER)) {
// Set script version identifier values.
strcpy(state.script, SCRIPT_NAME);
state.version = SCRIPT_VER;

// Provide a even distribution of default probabilities.
for (int i = 0; i < OUTPUT_COUNT; i++) {
patterns[i].Init(default_pattern);
}
SaveChanges(patterns);

} else {
// Load state patterns into the current patterns in memory.
for (int i = 0; i < OUTPUT_COUNT; i++) {
patterns[i].Init(state.pattern[i]);
}
}
}

#endif
34 changes: 17 additions & 17 deletions A-RYTH-MATIK/TimeBandit/TimeBandit.ino
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@
* RST: Trigger this input to reset the division counter.
*
*/
#include "src/libmodulove/arythmatik.h"
#include <arythmatik.h>

// Flag for enabling debug print to serial monitoring output.
// Note: this affects performance and locks LED 4 & 5 on HIGH.
@@ -66,6 +66,10 @@ void setup() {
// Initialize the A-RYTH-MATIK peripherials.
hw.Init();

// Set up encoder parameters
hw.eb.setEncoderHandler(UpdateRotate);
hw.eb.setClickHandler(UpdatePress);

// Define each of the fixed clock divisions.
// NOTE: This is binary value, clock divisions are bit shifted left by one.
clockDiv[0] = {hw.outputs[0], 1};
@@ -99,28 +103,24 @@ void loop() {
counter = 0;
}

// Read the encoder button press event.
Encoder::PressType press = hw.encoder.Pressed();

// Short button press. Change mode.
if (press == Encoder::PRESS_SHORT) {
// Next param on Main page.
selected_mode = static_cast<Mode>((selected_mode + 1) % MODE_LAST);
update_display = true;
}

UpdateParameter(hw.encoder.Rotate());
UpdateDisplay();
}

void UpdateParameter(Encoder::Direction dir) {
void UpdatePress(EncoderButton &eb) {
// Next param on Main page.
selected_mode = static_cast<Mode>((selected_mode + 1) % MODE_LAST);
update_display = true;
}

void UpdateRotate(EncoderButton &eb) {
int dir = eb.increment() > 0 ? 1 : -1;
if (selected_mode == MODE_SELECT) {
switch (dir) {
case Encoder::DIRECTION_DECREMENT:
case -1:
if (selected_out > 0) --selected_out;
update_display = true;
break;
case Encoder::DIRECTION_INCREMENT:
case 1:
if (selected_out < OUTPUT_COUNT - 1) ++selected_out;
update_display = true;
break;
@@ -129,14 +129,14 @@ void UpdateParameter(Encoder::Direction dir) {
if (selected_mode == MODE_EDIT) {
int division = clockDiv[selected_out].division;
switch (dir) {
case Encoder::DIRECTION_DECREMENT:
case -1:
if (division > 1) {
clockDiv[selected_out].division = division >> 1;
counter = 0;
update_display = true;
}
break;
case Encoder::DIRECTION_INCREMENT:
case 1:
if (division < 4096) {
clockDiv[selected_out].division = division << 1;
counter = 0;
1 change: 0 additions & 1 deletion A-RYTH-MATIK/TimeBandit/src/libmodulove
Submodule libmodulove deleted from 690b04
74 changes: 37 additions & 37 deletions A-RYTH-MATIK/Uncertainty/Uncertainty.ino
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
*/

// Include the Modulove library.
#include "src/libmodulove/arythmatik.h"
#include <arythmatik.h>

// Script specific output class.
#include "output.h"
@@ -55,6 +55,10 @@ void setup() {
Serial.begin(9600);
#endif

hw.eb.setClickHandler(ShortPress);
hw.eb.setLongPressHandler(LongPress);
hw.eb.setEncoderHandler(UpdateParameter);

// Initialize the A-RYTH-MATIK peripherials.
hw.Init();

@@ -96,66 +100,62 @@ void loop() {
}
}

// Read the encoder button press event.
Encoder::PressType press = hw.encoder.Pressed();
// Render any new UI changes to the OLED display.
UpdateDisplay();
}

// Short button press. Change editable parameter.
if (press == Encoder::PRESS_SHORT) {
// Next param on Main page.
if (selected_page == PAGE_MAIN)
selected_param = ++selected_param % PARAM_COUNT;
// Short button press. Change editable parameter.
void ShortPress(EncoderButton &eb) {
// Next param on Main page.
if (selected_page == PAGE_MAIN)
selected_param = ++selected_param % PARAM_COUNT;

update_display = true;
}
update_display = true;
}

// Long button press. Change menu page.
if (press == Encoder::PRESS_LONG) {
if (selected_page == PAGE_MAIN) {
selected_page = PAGE_MODE;
selected_param = 2;
} else {
selected_page = PAGE_MAIN;
selected_param = 0;
}
update_display = true;
// Long button press. Change menu page.
void LongPress(EncoderButton &eb) {
if (selected_page == PAGE_MAIN) {
selected_page = PAGE_MODE;
selected_param = 2;
} else {
selected_page = PAGE_MAIN;
selected_param = 0;
}

// Read encoder for a change in direction and update the selected parameter.
UpdateParameter(hw.encoder.Rotate());

// Render any new UI changes to the OLED display.
UpdateDisplay();
update_display = true;
}

void UpdateParameter(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_UNCHANGED) return;
// Read encoder for a change in direction and update the selected parameter.
void UpdateParameter(EncoderButton &eb) {
int dir = eb.increment();
if (selected_param == 0) UpdateOutput(dir);
if (selected_param == 1) UpdateProb(dir);
if (selected_param == 2) UpdateMode(dir);
update_display = true;
}

void UpdateOutput(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_INCREMENT && selected_out < OUTPUT_COUNT - 1)
void UpdateOutput(int dir) {
if (dir == 1 && selected_out < OUTPUT_COUNT - 1)
selected_out++;
if (dir == Encoder::DIRECTION_DECREMENT && selected_out > 0)
if (dir == -1 && selected_out > 0)
selected_out--;
update_display = true;
}

void UpdateMode(Encoder::Direction dir) {
void UpdateMode(int dir) {
Mode newMode;
if (dir == Encoder::DIRECTION_INCREMENT) newMode = Mode::FLIP;
if (dir == Encoder::DIRECTION_DECREMENT) newMode = Mode::TRIGGER;
if (dir == 1) newMode = Mode::FLIP;
if (dir == -1) newMode = Mode::TRIGGER;
// Update the mode for all outputs.
for (int i = 0; i < OUTPUT_COUNT; i++) {
outputs[i].SetMode(newMode);
}
update_display = true;
}

void UpdateProb(Encoder::Direction dir) {
if (dir == Encoder::DIRECTION_INCREMENT) outputs[selected_out].IncProb();
if (dir == Encoder::DIRECTION_DECREMENT) outputs[selected_out].DecProb();
void UpdateProb(int dir) {
if (dir == 1) outputs[selected_out].IncProb();
if (dir == -1) outputs[selected_out].DecProb();
update_display = true;
}

8 changes: 7 additions & 1 deletion A-RYTH-MATIK/Uncertainty/output.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#pragma once
#ifndef OUTPUT_H
#define OUTPUT_H

// Include the Modulove library.
#include <arythmatik.h>

using namespace modulove;

@@ -85,3 +89,5 @@ class ProbablisticOutput {

inline void low() { output_.Low(); }
};

#endif
1 change: 0 additions & 1 deletion A-RYTH-MATIK/Uncertainty/src/libmodulove
Submodule libmodulove deleted from 690b04
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -18,16 +18,18 @@ Modulove

## Firmware Development

### Clone the repo

This repository uses git submodules so there are extra steps to pull down all of the files you'll need.
### Clone the repo and dependencies

```shell
# Clone the HagiwoModulove repository
$ git clone https://github.com/awonak/HagiwoModulove.git
# Install libmodulove from GitHub using the arduino cli.
$ arduino-cli config init
$ arduino-cli config set library.enable_unsafe_install true
$ arduino-cli lib install --git-url https://github.com/awonak/libModulove.git
$ arduino-cli lib install --git-url https://github.com/awonak/libModulove.git
# OR optionally clone libmodulove for local development
$ git clone https://github.com/awonak/libModulove.git
$ ln -s `pwd`/libmodulove ~/Arduino/libraries
```

### Configure Visual Studio Code for your IDE
57 changes: 42 additions & 15 deletions pages/content/arythmatik.md
Original file line number Diff line number Diff line change
@@ -39,32 +39,32 @@ CV1-6: Gate output with decreasing probability.
{{< encoder_firmware_button hex="A-RYTH-MATIK_BitGarden" buttonText="Flash Bit Garden Firmware" >}}
## Uncertainty
## Euclidean
Configurable stochastic gate processor. [[source](https://github.com/awonak/HagiwoModulove/tree/main/A-RYTH-MATIK/Uncertainty)]
Euclidean Rhythms. [[source](https://github.com/awonak/HagiwoModulove/tree/main/A-RYTH-MATIK/Euclidean)]
This firmware is based on the [Olivia Artz Modular's Uncertainty](https://oamodular.org/products/uncertainty).
Connect a trigger or gate source to the CLK input and the each output will
mirror that signal according to a decreasing probability. Long press the
encoder to change trigger modes. `Trig` will simply echo the trigger or
gate received from the CLK input. `Flip` mode will toggle the output on
or off upon each new CLK input.
Configure each output with a Euclidean Rhythm defined by the number of steps,
the number of hits or beats evenly distributed across the steps, a rotation
offset of the pattern, and finally rest padding appended to the end of the
pattern.
{{< youtube SHZaS9hu4qI >}}
Use the encoder to select which parameter to edit, and click the encoder
button to chanve between select and edit mode. Hold and rotate the encoder
to change the selected output channel.
```yaml
Encoder:
short press: Toggle between selecting an output and editing the outputs probability.
long press: Enter Mode edit menu.
short press: Toggle between selecting and editing a parameter.
hold and rotate: Change selected output channel.

CLK: Provide a gate or trigger for each output to repeat with decreasing
probability in each output.
CLK: Advance the Euclidean Rhythm pattern step for all channels.

CV1-6: Gate output with decreasing probability.
RST: Trigger this input to restart all patterns.

CV1-6: Trigger output for each Euclidean Rhythm.
```
{{< encoder_firmware_button hex="A-RYTH-MATIK_Uncertainty" buttonText="Flash Uncertainty Firmware">}}
{{< encoder_firmware_button hex="A-RYTH-MATIK_Euclidean" buttonText="Flash Euclidean Firmware" >}}
## Time Bandit
@@ -91,3 +91,30 @@ CV1-6: Configurable binary clock divisions of 2, 4, 8, 16, 32..8192
```
{{< encoder_firmware_button hex="A-RYTH-MATIK_TimeBandit" buttonText="Flash Time Bandit Firmware">}}
## Uncertainty
Configurable stochastic gate processor. [[source](https://github.com/awonak/HagiwoModulove/tree/main/A-RYTH-MATIK/Uncertainty)]
This firmware is based on the [Olivia Artz Modular's Uncertainty](https://oamodular.org/products/uncertainty).
Connect a trigger or gate source to the CLK input and the each output will
mirror that signal according to a decreasing probability. Long press the
encoder to change trigger modes. `Trig` will simply echo the trigger or
gate received from the CLK input. `Flip` mode will toggle the output on
or off upon each new CLK input.

{{< youtube SHZaS9hu4qI >}}

```yaml
Encoder:
short press: Toggle between selecting an output and editing the outputs probability.
long press: Enter Mode edit menu.
CLK: Provide a gate or trigger for each output to repeat with decreasing
probability in each output.
CV1-6: Gate output with decreasing probability.
```

{{< encoder_firmware_button hex="A-RYTH-MATIK_Uncertainty" buttonText="Flash Uncertainty Firmware">}}