diff --git a/software/GToE/GToE.ino b/software/GToE/GToE.ino new file mode 100644 index 0000000..810883a --- /dev/null +++ b/software/GToE/GToE.ino @@ -0,0 +1,400 @@ +#include +#include +#include +#include +#include +#include + +#define SCREEN_ADDRESS 0x3C + +#define PPQN 24 +#define PULSE_LENGTH 5 //ms +#define MAXBPM 250 //250 at 24ppqn with 5ms pulse will be 50/50 square wave +#define MINBPM 20 + +#define INPUT_CONNECTED_PIN 0 +#define INPUT_PIN 2 //needs to be an interrupt pin +#define ENC_BTN_PIN 17 +#define ENC_D1_PIN 4 +#define ENC_D2_PIN 3 +#define START_STOP_BTN_PIN 14 +#define ANALOGUE_INPUT_1_PIN A1 +#define ANALOGUE_INPUT_2_PIN A1 + +const int outsPins[6] = {5, 6, 7, 8, 9, 10}; + +const int clockModes[18] = {-24, -16, -12, -8, -6, -4, -3, -2, 1, 2, 3, 4, 5, 6, 7, 8, 16, 32}; //positive - divide, negative - multiply, 0 - off + +unsigned int bpm = 130; + +struct channel { + unsigned int mode; + bool random; + bool modulationChannel; //0 - A1, 1 - A2 + int modulationRange; +}; + +channel channels[6] = { //array of channel settings + { 8, 0, 0, 0 }, + { 9, 0, 0, 0 }, + {10, 0, 0, 0 }, + { 7, 1, 0, 0 }, + { 2, 0, 1, 4 }, + { 6, 1, 1,-4 } +}; + +int outsClocksCounts[6]; +int playingModes[6]; //actual channel modes array updated from channels each beat + +bool externalClockConnected; +bool externalClockConnectedOld; +int clockCount = 0; + +int pulseClockCount = 0; +int pulseCount = 0; +int pulsePeriod; +bool isPlaying = 0; +int needToResetChannel; +bool beatCounted = false; +bool pulseCounted = false; + +int displayTab = 0; +int displayTabOld; +int insideTab = 0; +bool playBtnPushed = false; + +int a1Input = 0; +int a2Input = 0; + +int encPositionOld = 0; +unsigned long encPressedTime; +unsigned long encReleasedTime; +bool encPressRegistered; + +Adafruit_SSD1306 display(128, 64, &Wire, -1); +RotaryEncoder encoder(ENC_D1_PIN, ENC_D2_PIN, RotaryEncoder::LatchMode::TWO03); + +void setup() { + Serial.begin(9600); + + EEPROM.get(0, bpm); + EEPROM.get(sizeof(int), channels); + + pinMode(INPUT_CONNECTED_PIN, INPUT); + pinMode(ENC_BTN_PIN, INPUT_PULLUP); + pinMode(START_STOP_BTN_PIN, INPUT_PULLUP); + pinMode(START_STOP_BTN_PIN, ANALOGUE_INPUT_1_PIN); + pinMode(INPUT_PIN, INPUT_PULLUP); //probably will need interrupt + attachInterrupt(digitalPinToInterrupt(INPUT_PIN), externalClock, RISING); + + for (int i=0; i<6; i++) { + pinMode(outsPins[i], OUTPUT); + } + + display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS); + + updateScreen(); + updateTiming(); + + FlexiTimer2::set(1, 1.0/1000, internalClock); // 1.0/1000 = 1ms period + FlexiTimer2::start(); +} + +void loop() { + checkInputs(); +} + +void internalClock() { + if (isPlaying) { + + // Action on each pulse + if (pulseClockCount == 0 && !pulseCounted) { + + //modulation + for (int i = 0; i<6; i++) { + int mod; + if (!channels[i].modulationChannel) { + mod = a1Input; + } else { + mod = a2Input; + } + mod = map (mod, 0, 1023, 0, channels[i].modulationRange); + playingModes[i] = clockModes[channels[i].mode - mod]; //subtrackting because the innitiall array is backwards + } + + //divider + if (pulseCount == 0 && !beatCounted) { + for (int i = 0; i<6; i++) { + playingModes[i] = clockModes[channels[i].mode]; //updated here to prevent sync problems for multipliers + if (playingModes[i] > 0) { + if (outsClocksCounts[i] == 0) { //Pulse on 0 + if (channels[i].random == 0 || (channels[i].random == 1 && random(2))) { //random + digitalWrite(outsPins[i], HIGH); + } + } + if (outsClocksCounts[i] < (playingModes[i] - 1)) { + outsClocksCounts[i]++; + } else { + outsClocksCounts[i] = 0; + } + } + } + beatCounted = true; + } + + //multiplier + for (int i = 0; i<6; i++) { + if (playingModes[i] < 0) { + if (outsClocksCounts[i] == 0) { //Pulse on 0 + if (channels[i].random == 0 || (channels[i].random == 1 && random(2))) { //random + digitalWrite(outsPins[i], HIGH); + } + } + if (outsClocksCounts[i] < (PPQN / abs(playingModes[i])) - 1) { + outsClocksCounts[i]++; + } else { + outsClocksCounts[i] = 0; + } + } + } + pulseCounted = true; + } + + //internal pulse + if (pulseClockCount == 0) { + pulseCount++; + beatCounted = false; + pulseCounted = false; + } + if (pulseClockCount < pulsePeriod) { + pulseClockCount++; + } else { + pulseClockCount = 0; + } + if (pulseCount >= PPQN) { + pulseCount = 0; + } + + // pull low all outputs after set pulse length + if (pulseClockCount >= PULSE_LENGTH) { + for (int i = 0; i<6; i++) { + digitalWrite(outsPins[i], LOW); + } + } + + } +} + +void externalClock() { + digitalWrite(outsPins[0], !digitalRead(outsPins[0])); +} + +void updateTiming() { + pulsePeriod = 60000 / (bpm * PPQN); +} + +void resetClocks() { + for (int i = 0; i<6; i++) { + outsClocksCounts[i] = 0; + digitalWrite(outsPins[i], LOW); //to avoid stuck leds + } +} + +void saveState() { + EEPROM.put(0, bpm); + EEPROM.put(sizeof(int), channels); +} + +void checkInputs() { + + //input jack switcch + externalClockConnected = digitalRead(INPUT_CONNECTED_PIN); + if (externalClockConnected != externalClockConnectedOld) { + //updateScreen(); + externalClockConnectedOld = externalClockConnected; + Serial.println(externalClockConnected); + } + + //encoder button + if (!digitalRead(ENC_BTN_PIN) && !encPressRegistered) { + encPressRegistered = true; + encPressedTime = millis(); + } else if (digitalRead(ENC_BTN_PIN) && encPressRegistered) { + encPressRegistered = false; + encReleasedTime = millis(); + //Serial.println(encReleasedTime - encPressedTime); + + if (encReleasedTime - encPressedTime < 500) { // press shorter than .5s switches tabs + if (insideTab == 0) { + displayTabOld = displayTab; + displayTab++; + if (displayTab>6) { + displayTab = 0; + } + } else if (insideTab < 2) { + insideTab ++; + } else { + insideTab = 1; + } + updateScreen(); + } else if (encReleasedTime - encPressedTime < 2000 && displayTab != 0) { // longer press (<2s) and switches random mode, longer than 2s presses are ignored + if (insideTab == 0) { + insideTab = 1; + } else { + insideTab = 0; + } + updateScreen(); + } + } + + //encoder + encoder.tick(); + int encPosition = encoder.getPosition(); + if (encPositionOld != encPosition) { + int change = encPositionOld - encPosition; + if (displayTab == 0) { + bpm = bpm + change; + if (bpm > MAXBPM) { + bpm = MAXBPM; + } else if (bpm < MINBPM) { + bpm = MINBPM; + } + updateTiming(); + } else if (displayTab != 0 && insideTab == 0) { + channels[displayTab-1].mode = channels[displayTab-1].mode - change; + if (channels[displayTab-1].mode < 0) { + channels[displayTab-1].mode = 0; + } else if (channels[displayTab-1].mode > (sizeof(clockModes)/sizeof(int)) - 1) { + channels[displayTab-1].mode = (sizeof(clockModes)/sizeof(int)) - 1; + } + needToResetChannel = displayTab-1; + } else if (displayTab != 0 && insideTab == 1) { //random + channels[displayTab-1].random = !channels[displayTab-1].random; + } else if (displayTab != 0 && insideTab == 2) { //modulation + channels[displayTab-1].modulationRange = channels[displayTab-1].modulationRange + change; + if (channels[displayTab-1].modulationRange > 6 || channels[displayTab-1].modulationRange < -6) { + channels[displayTab-1].modulationChannel = !channels[displayTab-1].modulationChannel; + channels[displayTab-1].modulationRange = 0; + } + } + updateScreen(); + encPositionOld = encPosition; + } + + //play button + if (!digitalRead(START_STOP_BTN_PIN) && !playBtnPushed) { + isPlaying = !isPlaying; + resetClocks(); + playBtnPushed = true; + saveState(); + } else if (digitalRead(START_STOP_BTN_PIN) && playBtnPushed) { + playBtnPushed = false; + } + + //modulations + a1Input = analogRead(ANALOGUE_INPUT_1_PIN); + a2Input = analogRead(ANALOGUE_INPUT_2_PIN); + +} + +void updateScreen() { + display.clearDisplay(); + + //Tabs + display.drawRect(0, 0, 128, 2, SSD1306_WHITE); + display.setCursor(0,2); + display.setTextSize(1); + if (displayTab == 0) { + display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); + display.print(F(" ")); + display.setTextColor(SSD1306_WHITE); + display.print(F(" bpm ")); + } else { + display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); + display.print(F(" bpm")); + } + + for (int i = 1; i <= 6; i++) { + if (displayTab == i) { + display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); + display.print(" "); + display.setTextColor(SSD1306_WHITE); + display.print(" "); + display.print(i); + display.print(" "); + } else { + display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); + display.print(" "); + display.print(i); + } + } + display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); + //display.println(F(" ")); + display.fillRect(108, 2, 20, 8, SSD1306_WHITE); + display.println(); + display.println(); + display.fillRect(0, 10, 128, 2, SSD1306_WHITE); + + //Content + display.setTextSize(3); + display.setTextColor(SSD1306_WHITE); + if (displayTab == 0) { + display.print(bpm); + display.println(F("bpm")); + } else { + if (clockModes[channels[displayTab-1].mode] == 0) { + display.print(F(" OFF")); + } else if (clockModes[channels[displayTab-1].mode]>0) { + display.print(F(" /")); + display.print(abs(clockModes[channels[displayTab-1].mode])); + } else { + display.print(F(" x")); + display.print(abs(clockModes[channels[displayTab-1].mode])); + } + } + display.println(); + display.setTextSize(1); + display.println(); + + //Extra params + display.setTextSize(1); + if (displayTab != 0) { + if (insideTab == 1) { + display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); + } else { + display.setTextColor(SSD1306_WHITE); + } + display.print(F(" RND:")); + if (channels[displayTab-1].random) { + display.print(F("On ")); + } else { + display.print(F("Off ")); + } + display.setCursor(60,50); + if (insideTab == 2) { + display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); + } else { + display.setTextColor(SSD1306_WHITE); + } + display.print(F(" MOD:")); + if (channels[displayTab-1].modulationChannel && channels[displayTab-1].modulationRange != 0) { + display.print(F("A2 ")); + if (channels[displayTab-1].modulationRange > 0) { + display.print(F("+")); + } + display.print(channels[displayTab-1].modulationRange); + display.print(F(" ")); + } else if (!channels[displayTab-1].modulationChannel && channels[displayTab-1].modulationRange != 0) { + display.print(F("A1 ")); + if (channels[displayTab-1].modulationRange > 0) { + display.print(F("+")); + } + display.print(channels[displayTab-1].modulationRange); + display.print(F(" ")); + } else { + display.print(F("Off ")); + } + } + + display.display(); +} \ No newline at end of file