diff --git a/.gitignore b/.gitignore
index 6dc580e..16efdde 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,13 @@
-
-*.o
-*.d
-*.DS_Store
+# Dependency Files
+*.d
+
+# Compiler outputs
+*.o
+build/
+
+# OS generated files #
+*.DS_Store
+
+# Visual Studio Code
+.vscode/
+.devcontainer/
\ No newline at end of file
diff --git a/Community/thorinf/Makefile b/Community/thorinf/Makefile
new file mode 100644
index 0000000..5c05036
--- /dev/null
+++ b/Community/thorinf/Makefile
@@ -0,0 +1,30 @@
+CC = avr-gcc
+CFLAGS = -g -O2 -mmcu=atmega8 -flto
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+LDFLAGS = -flto
+
+SRC_DIR = csrc
+BUILD_DIR = build
+SRC = $(wildcard $(SRC_DIR)/*.c)
+OBJS = $(SRC:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o)
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: $(OBJS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -c $< -o $@
+
+$(BUILD_DIR):
+ mkdir -p $(BUILD_DIR)
diff --git a/Community/thorinf/README.md b/Community/thorinf/README.md
new file mode 100644
index 0000000..73a9edd
--- /dev/null
+++ b/Community/thorinf/README.md
@@ -0,0 +1,84 @@
+# Custom Firmware
+
+
+
+
+
+## MIDI Modes
+
+This firmware allows each of the 8 Gate-CV pairs to be programmed individually with any of 6 MIDI modes. A MIDI Map stores the conditions for the Gate-CV pairs so that MIDI messages can be passed correctly during play. Any MIDI channel can be used, however it's in most cases best for triggers to not match and Pitch values to come from unqiue channels (more details in MIDI Learn).
+
+| **MIDI Mode** | **Gate Style** | **Gate Condition** | **CV** |
+|---------------------------------------------|----------------|--------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
+| **1. Velocity** | Drum Pad | Note message matching trigger condition (Channel & Pitch). | Velocity of the Note message. |
+| **2. Control Change** | Drum Pad | Note message matching trigger condition (Channel & Pitch). | Value from a Control Change (CC) message with matching CC condition (Channel & Controller Number). |
+| **3. Pitch** | Keyboard | Note message matching trigger condition (Channel). | Pitch (5 octave range) of the Note message. |
+| **4. Pitch, Sample & Hold** | Drum Pad | Note message matching trigger condition (Channel G & Pitch). | Pitch value (Note message on Channel P) is held in buffer. Gate trigger (Channel G) updates CV from the stored buffer. |
+| **5. Random Step Sequencer\*** | Drum Pad | Note message matching trigger condition (Channel & Pitch S). | NoteOn message with Step condition (Channel & Pitch S) updates the CV output with a new sequence value. NoteOn message with Reset condition (Channel & Pitch_R) resets the Random Sequence. |
+| **6. Random Step Sequencer\*, Sample & Hold** | Drum Pad | Note message matching trigger condition (Channel & Pitch G). | Similar to Random Step Sequencer, however the new random value from the Step Sequence is sampled when a Gate Condition is triggered. |
+
+### *Random Step Sequencer
+
+New sequences can be generated with a short-press of the button on the Tram8. Without a Reset trigger the sequence will go on seemingly indefinitely, this is because the random step sequencer uses a Pseudo Randum Number Generator (PRNG) to generate new values. This is a deteministic process, although the values will be percieved as a random sequence after an update. The initial values, or seeds, are kept in memory. Reset triggers will simply copy these seeds to reset the PRNG process, making it repeat. A different PRNG algorithm is used to update the seed when the button is pressed, updating with the same alogirthm would just 'shift' the sequence.
+
+## Menu
+
+To access the Menu hold down the button on the Tram8 for about a second until the first Gate illuminates. Once in Menu MIDI messages will cease to be outputted from all Gates and CV outputs. The illuminated Gate indicates where you are in the Menu below, you can cycle through the options with a short press and enter/execute the selected option with a hold press. Since MIDI Learn is the first option, a long hold of the button will put you into MIDI Learn mode - you will see the first Gate turn on then off.
+
+### 1. MIDI Learn
+
+Once entered, you will be able to program the Tram8 with your own MIDI mappings.
+
+- You can cycle through learning each MIDI type with a short press of the button. The LED will blink a count then pause on repeat, indicating the MIDI Mode you are learning (as shown in the table below).
+- Once a Gate-CV pair has been successfully completed, it will illuminate.
+- Depending on the MIDI Mode, you will need to transmit 1-3 appropriate MIDI messages to the Tram8 to complete the mapping for that Gate-CV pair (details in the table below).
+- If you have not transmitted all the required MIDI messages, a short press of the button will reset the learning for the Gate-CV pair and return to learning the Velocity mode.
+- You can exit MIDI Learn at any time by holding the button down; all Gate lights will briefly turn on to indicate exiting.
+- Completing or exiting MIDI Learn will not automatically save the mapping. Saving must be done manually from the Menu. This allows you to test your new mapping and mitigates the risk of mistakes. If you're unhappy with the mapping, you can load from the Menu or restart the module with a power cycle.
+
+
+| **Learning MIDI Mode** | **Number of Messages Required** | **Message Details** |
+|-----------------------------------------|---------------------------------|-------------------------------------------------------------------------------------------------------|
+| **1. Velocity** | 1 | 1 NoteOn message for the Gate. |
+| **2. Control Change** | 2 | 1 NoteOn message for the Gate followed by a CC message. |
+| **3. Pitch** | 1 | Only 1 NoteOn message. This is best on a unique channel, but it can be any note. |
+| **4. Pitch, Sample & Hold** | 2 | 1 NoteOn message for the Gate followed by 1 NoteOn message for Pitch, preferably on a unique channel. |
+| **5. Random Step Sequencer** | 3 | 1 NoteOn message followed by 2 NoteOn messages for both Step and Reset. |
+| **6. Random Step Sequencer, Sample & Hold**| 3 | 1 NoteOn message followed by 2 NoteOn messages for both Step and Reset. |
+
+
+### 2. Save MIDI Map
+
+Saves the MIDI Mapping in memory to the storage chip.
+
+### 3. Load MIDI Map
+
+Loads the MIDI Mapping into memory from the storage chip.
+
+### 4. Copy Preset (Beat Step Pro)
+
+Copies the MIDI Mapping from the Preset into memory. This will not be saved.
+
+|| **MIDI Mode** | **Conditions** |
+|-|---------------------------------------|------------------------------------------------------------------------------------------------------|
+|1| Random Step Sequencer, Sample & Hold | Channel 8 (Drum Sequencer default), Gate C0 (Label C), Step G#0 (Label C#1), Reset A0 (Label D#). |
+|2| Random Step Sequencer, Sample & Hold | Channel 8 (Drum Sequencer default), Gate C#0 (Label D), Step A#0 (Label None), Reset B0 (Label F#). |
+|3| Random Step Sequencer | Channel 8 (Drum Sequencer default), Gate D0 (Label E), Step C1 (Label G#), Reset C#1 (Label A#). |
+|4| Random Step Sequencer | Channel 8 (Drum Sequencer default), Gate D#0 (Label F), Step D1 (Label OCT-), Reset D#1 (Label OCT+).|
+|5| Velocity | Channel 8 (Drum Sequencer default), Gate E0 (Label G). |
+|6| Velocity | Channel 8 (Drum Sequencer default), Gate F0 (Label A). |
+|7| Pitch | Channel 1 (Sequencer 1 default). |
+|8| Pitch | Channel 2 (Sequencer 2 default). |
+
+
+### 5. SysEx MIDI Map
+
+The Tram8 will wait for SysEx messages that indicate the mapping. The tool in the `js` subdirectory can be used to create mappings and send them to the device. To use this, once the repository is cloned simply open `index.html` in your browser. You will need to use a browser that supports sending MIDI or SysEx, but there are a few that do e.g., Chrome.
+
+
+
+
+
+### 6. Exit
+
+Exits the Menu back to ordinary play function.
diff --git a/Community/thorinf/csrc/hardware_config.h b/Community/thorinf/csrc/hardware_config.h
new file mode 100644
index 0000000..4cf2ac8
--- /dev/null
+++ b/Community/thorinf/csrc/hardware_config.h
@@ -0,0 +1,47 @@
+#ifndef HARDWARE_CONFIG_H
+#define HARDWARE_CONFIG_H
+
+#include
+
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+#define MY_UBRR ((F_CPU / (16UL * BAUD)) - 1)
+
+// Gate control
+#define NUM_GATES 8
+
+#define GATE_PORT_B PORTB
+#define GATE_DDR_B DDRB
+#define GATE_PIN_0 PB0
+
+#define GATE_PORT_D PORTD
+#define GATE_DDR_D DDRD
+#define GATE_PIN_1 PD1
+
+// LED control
+#define LED_PORT PORTC
+#define LED_DDR DDRC
+#define LED_PIN PC0
+
+// Button control
+#define BUTTON_PORT PORTC
+#define BUTTON_DDR DDRC
+#define BUTTON_PIN_REG PINC
+#define BUTTON_PIN PC1
+
+// DAC control pins
+#define LDAC_PIN PC2
+#define CLR_PIN PC3
+#define LDAC_PORT PORTC
+#define CLR_PORT PORTC
+#define CONTROL_DDR DDRC
+
+// I2C pins
+#define SDA_PIN PC4
+#define SCL_PIN PC5
+
+// EEPROM configuration
+#define EEPROM_BUTTON_FIX_ADDR 0x07
+#define EEPROM_MIDIMAP_ADDR 0x101
+
+#endif
\ No newline at end of file
diff --git a/Community/thorinf/csrc/io.c b/Community/thorinf/csrc/io.c
new file mode 100644
index 0000000..b5896db
--- /dev/null
+++ b/Community/thorinf/csrc/io.c
@@ -0,0 +1,84 @@
+#include "io.h"
+
+void updateButton(Button *button) {
+ uint8_t currentReading = button->getReading();
+
+ switch (button->buttonState) {
+ case BUTTON_IDLE:
+ if (currentReading) {
+ button->buttonState = BUTTON_DEBOUNCING;
+ button->buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_DEBOUNCING:
+ if (++(button->buttonTimer) >= DEBOUNCE_THRESHOLD) {
+ button->buttonState = BUTTON_PRESSED;
+ button->buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_PRESSED:
+ case BUTTON_DOWN:
+ if (!currentReading) {
+ button->buttonState = (button->buttonState == BUTTON_PRESSED) ? BUTTON_RELEASED : BUTTON_IDLE;
+ } else if (button->buttonTimer >= LONG_PRESS_THRESHOLD) {
+ button->buttonState = BUTTON_HELD;
+ } else {
+ button->buttonTimer++;
+ }
+ break;
+
+ case BUTTON_HELD:
+ if (!currentReading) {
+ button->buttonState = BUTTON_IDLE;
+ } else {
+ button->buttonState = BUTTON_DOWN;
+ button->buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_RELEASED:
+ button->buttonState = BUTTON_IDLE;
+ break;
+ }
+}
+
+void updateLED(LED *led) {
+ uint16_t blinkRate = (200 >> (led->ledState - LED_BLINK1)) / TIMER_TICK;
+
+ switch (led->ledState) {
+ case LED_ON:
+ led->ledOn();
+ break;
+ case LED_OFF:
+ led->ledOff();
+ break;
+ default:
+ led->ledTimer++;
+
+ if (led->pauseState) {
+ led->ledOff();
+ if (led->ledTimer >= blinkRate * led->ledBlinkCount) {
+ led->ledTimer = 0;
+ led->pauseState = 0;
+ }
+ } else {
+ if (led->ledTimer >= blinkRate) {
+ led->ledTimer = 0;
+ led->ledToggle = !led->ledToggle;
+
+ if (led->ledToggle) {
+ led->ledOn();
+ } else {
+ led->ledOff();
+ if (++led->current_blink >= led->ledBlinkCount) {
+ led->pauseState = 1;
+ led->current_blink = 0;
+ }
+ }
+ }
+ }
+ break;
+ }
+}
diff --git a/Community/thorinf/csrc/io.h b/Community/thorinf/csrc/io.h
new file mode 100644
index 0000000..fc2cc5c
--- /dev/null
+++ b/Community/thorinf/csrc/io.h
@@ -0,0 +1,48 @@
+#ifndef CONTROL_H
+#define CONTROL_H
+
+#include
+
+#define DEBOUNCE_TIME 50
+#define LONG_PRESS_TIME 2000
+#define TIMER_TICK 10
+#define DEBOUNCE_THRESHOLD (DEBOUNCE_TIME / TIMER_TICK)
+#define LONG_PRESS_THRESHOLD (LONG_PRESS_TIME / TIMER_TICK)
+
+// Button states
+#define BUTTON_IDLE 0
+#define BUTTON_DEBOUNCING 1
+#define BUTTON_PRESSED 2
+#define BUTTON_HELD 3
+#define BUTTON_DOWN 4
+#define BUTTON_RELEASED 5
+
+// LED states
+#define LED_OFF 1
+#define LED_ON 2
+#define LED_BLINK1 3
+#define LED_BLINK2 4
+#define LED_BLINK3 5
+#define LED_BLINK4 6
+
+typedef struct {
+ uint8_t buttonState;
+ uint16_t buttonTimer;
+ uint8_t (*getReading)(void);
+} Button;
+
+typedef struct {
+ uint8_t ledState;
+ uint8_t ledBlinkCount;
+ uint8_t ledToggle;
+ uint16_t ledTimer;
+ uint8_t current_blink;
+ uint8_t pauseState;
+ void (*ledOn)(void);
+ void (*ledOff)(void);
+} LED;
+
+void updateButton(Button *button);
+void updateLED(LED *led);
+
+#endif
\ No newline at end of file
diff --git a/Community/thorinf/csrc/main.c b/Community/thorinf/csrc/main.c
new file mode 100644
index 0000000..dd779e4
--- /dev/null
+++ b/Community/thorinf/csrc/main.c
@@ -0,0 +1,435 @@
+#include "hardware_config.h"
+#include "max5825_control.h"
+#include "midimap.h"
+#include "pin_control.h"
+#include "pitch.h"
+#include "random.h"
+#include "twi_control.h"
+#include "io.h"
+
+#include
+#include
+#include
+#include
+
+#define IS_NOTE_ON(command) (((command) & 0xF0) == 0x90)
+
+#define AWAITING_CC NUM_MIDIMAP_TYPES
+#define AWAITING_PITCH NUM_MIDIMAP_TYPES + 1
+#define AWAITING_STEP NUM_MIDIMAP_TYPES + 2
+#define AWAITING_RESET NUM_MIDIMAP_TYPES + 3
+
+typedef struct {
+ uint8_t status;
+ uint8_t data1;
+ uint8_t data2;
+ volatile uint8_t ready;
+} MIDI_Message;
+
+Button learnButton = {BUTTON_IDLE, 0, read_button};
+LED learnLED = {LED_OFF, 1, 0, 0, 0, 0, led_on, led_off};
+volatile MIDI_Message midiMsg = {0, 0, 0, 0};
+MIDIMapEntry midi_map[NUM_GATES];
+uint16_t dac_buffer[NUM_GATES];
+uint16_t lfsr_seeds[NUM_GATES];
+volatile uint8_t subRoutine = 0;
+
+void setup(void);
+void USART_Init(unsigned int ubrr);
+void saveMidiMap(MIDIMapEntry *src, uint8_t *location);
+void loadMidiMap(MIDIMapEntry *dst, uint8_t *location);
+void copyMidiMap(MIDIMapEntry *src, MIDIMapEntry *dst);
+void sysExMidiMap(MIDIMapEntry *dst);
+void newSeeds(void);
+void resetDacBuffer(void);
+void handleMIDIMessage(void);
+void midiLearn(void);
+
+void setup() {
+ pin_initialize();
+ twi_init();
+ max5825_init();
+ USART_Init(MY_UBRR);
+ loadMidiMap(midi_map, (uint8_t *)EEPROM_MIDIMAP_ADDR);
+
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ lfsr_seeds[i] = (i + 1) << 4;
+ gate_set(i, 1);
+ _delay_ms(50);
+ }
+ gate_set_multiple(0xFF, 0);
+
+ sei();
+}
+
+int main(void) {
+ setup();
+ uint8_t menuState;
+
+ while (1) {
+ updateButton(&learnButton);
+ updateLED(&learnLED);
+ _delay_ms(TIMER_TICK);
+
+ switch (subRoutine) {
+ case 0: // Normal play
+ if (learnButton.buttonState == BUTTON_RELEASED)
+ newSeeds();
+ else if (learnButton.buttonState == BUTTON_HELD) {
+ menuState = 0;
+ gate_set(menuState, 1);
+ subRoutine = 1;
+ }
+ break;
+
+ case 1: // In Menu
+ if (learnButton.buttonState == BUTTON_RELEASED) {
+ gate_set(menuState, 0);
+ menuState = (menuState + 1) % 6;
+ gate_set(menuState, 1);
+ } else if (learnButton.buttonState == BUTTON_HELD) {
+ gate_set(menuState, 0);
+
+ switch (menuState) {
+ case 0:
+ learnLED.ledState = LED_BLINK1;
+ learnLED.ledBlinkCount = MIDIMAP_VELOCITY + 1;
+ subRoutine = 2;
+ break;
+ case 1:
+ saveMidiMap(midi_map, (uint8_t *)EEPROM_MIDIMAP_ADDR);
+ subRoutine = 0;
+ break;
+ case 2:
+ loadMidiMap(midi_map, (uint8_t *)EEPROM_MIDIMAP_ADDR);
+ subRoutine = 0;
+ break;
+ case 3:
+ copyMidiMap(midi_map_bsp, midi_map);
+ subRoutine = 0;
+ break;
+ case 4:
+ sysExMidiMap(midi_map);
+ subRoutine = 0;
+ break;
+ case 5:
+ subRoutine = 0;
+ break;
+ }
+ }
+ break;
+
+ case 2: // Learning
+ midiLearn();
+ break;
+ }
+ }
+}
+
+void USART_Init(unsigned int ubrr) {
+ UBRRH = (unsigned char)(ubrr >> 8);
+ UBRRL = (unsigned char)ubrr;
+ UCSRB = (1 << RXEN) | (1 << RXCIE);
+ UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0);
+}
+
+ISR(USART_RXC_vect) {
+ static uint8_t midiState = 0;
+ uint8_t byte = UDR;
+
+ switch (midiState) {
+ case 0:
+ if (byte >= 0x80 && byte < 0xF0) {
+ midiMsg.status = byte;
+ midiState = (byte & 0xF0) == 0xD0 ? 1 : 2;
+ }
+ break;
+ case 1:
+ midiMsg.data1 = byte;
+ midiState = 0;
+ break;
+ case 2:
+ midiMsg.data1 = byte;
+ midiState = 3;
+ break;
+ case 3:
+ midiMsg.data2 = byte;
+ midiMsg.ready = 1;
+ midiState = 0;
+ if (!subRoutine) {
+ handleMIDIMessage();
+ }
+ break;
+ }
+}
+
+void saveMidiMap(MIDIMapEntry *src, uint8_t *location) {
+ while (!eeprom_is_ready());
+ eeprom_write_block((const void *)src, (void *)location, sizeof(midi_map));
+}
+
+void loadMidiMap(MIDIMapEntry *dst, uint8_t *location) {
+ while (!eeprom_is_ready());
+ eeprom_read_block((void *)dst, (const void *)location, sizeof(midi_map));
+}
+
+void copyMidiMap(MIDIMapEntry *src, MIDIMapEntry *dst) {
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ dst[i] = src[i];
+ }
+}
+
+void sysExMidiMap(MIDIMapEntry *dst) {
+ uint8_t sysExBuffer[114];
+ uint8_t *bufferPtr = sysExBuffer;
+
+ cli();
+ gate_set_multiple(0x81, 1);
+
+ for (uint8_t i = 0; i < sizeof(sysExBuffer); i++) {
+ while (!(UCSRA & (1 << RXC)));
+ sysExBuffer[i] = UDR;
+ }
+
+ gate_set_multiple(0x81, 0);
+
+ if ((sysExBuffer[0] != 0xF0) || (sysExBuffer[113] != 0xF7)) {
+ gate_set_multiple(0xFF, 1);
+ _delay_ms(50);
+ gate_set_multiple(0xFF, 0);
+ sei();
+ return;
+ }
+
+ uint8_t index = 1;
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ dst[i].mapType = sysExBuffer[index] | (sysExBuffer[index + 1] << 7);
+ dst[i].gateCommand = sysExBuffer[index + 2] | (sysExBuffer[index + 3] << 7);
+ dst[i].gateValue = sysExBuffer[index + 4] | (sysExBuffer[index + 5] << 7);
+ dst[i].cvCommand1 = sysExBuffer[index + 6] | (sysExBuffer[index + 7] << 7);
+ dst[i].cvValue1 = sysExBuffer[index + 8] | (sysExBuffer[index + 9] << 7);
+ dst[i].cvCommand2 = sysExBuffer[index + 10] | (sysExBuffer[index + 11] << 7);
+ dst[i].cvValue2 = sysExBuffer[index + 12] | (sysExBuffer[index + 13] << 7);
+
+ index += 14;
+
+ gate_set_multiple(i, 1);
+ _delay_ms(50);
+ }
+ gate_set_multiple(0xFF, 0);
+ sei();
+}
+
+void resetDacBuffer() {
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ uint8_t mapType = midi_map[i].mapType;
+ if (mapType == MIDIMAP_RANDSEQ || mapType == MIDIMAP_RANDSEQ_SAH) {
+ dac_buffer[i] = lfsr_seeds[i];
+ }
+ }
+}
+
+void newSeeds() {
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ uint8_t mapType = midi_map[i].mapType;
+ if (mapType == MIDIMAP_RANDSEQ || mapType == MIDIMAP_RANDSEQ_SAH) {
+ updateLfsrAlt(&lfsr_seeds[i]);
+ }
+ }
+}
+
+inline void handleMIDIMessage() {
+ uint8_t gateIndex = 0;
+ uint8_t commandFiltered = midiMsg.status & 0xEF;
+ uint8_t noteOnFlag = IS_NOTE_ON(midiMsg.status);
+ uint8_t data1 = midiMsg.data1;
+
+ while (gateIndex < NUM_GATES) {
+ MIDIMapEntry *mapEntry = &midi_map[gateIndex];
+ uint8_t gateCommand = mapEntry->gateCommand & 0xEF;
+
+ switch (mapEntry->mapType) {
+ case MIDIMAP_VELOCITY:
+ if (commandFiltered == gateCommand && data1 == mapEntry->gateValue) {
+ gate_set(gateIndex, noteOnFlag);
+ max5825_write(gateIndex, noteOnFlag ? midiMsg.data2 << 9 : 0); // 7-bit to 16-bit
+ }
+ break;
+
+ case MIDIMAP_CC:
+ if (commandFiltered == gateCommand && data1 == mapEntry->gateValue) {
+ gate_set(gateIndex, noteOnFlag);
+ } else if (midiMsg.status == mapEntry->cvCommand1 && data1 == mapEntry->cvValue1) {
+ max5825_write(gateIndex, midiMsg.data2 << 9);
+ }
+ break;
+
+ case MIDIMAP_PITCH:
+ if (commandFiltered == gateCommand && data1 < PITCH_SIZE) {
+ gate_set(gateIndex, noteOnFlag);
+ max5825_write(gateIndex, pitch_lookup[data1]);
+ }
+ break;
+
+ case MIDIMAP_PITCH_SAH:
+ if (commandFiltered == gateCommand && data1 == mapEntry->gateValue) {
+ gate_set(gateIndex, noteOnFlag);
+ max5825_write(gateIndex, dac_buffer[gateIndex]);
+ } else if (commandFiltered == mapEntry->cvCommand1 && data1 < PITCH_SIZE) {
+ dac_buffer[gateIndex] = pitch_lookup[data1];
+ }
+ break;
+
+ case MIDIMAP_RANDSEQ:
+ if (commandFiltered == gateCommand && data1 == mapEntry->gateValue) {
+ gate_set(gateIndex, noteOnFlag);
+ }
+ if (midiMsg.status == mapEntry->cvCommand1 && data1 == mapEntry->cvValue1) {
+ max5825_write(gateIndex, updateLfsr(&dac_buffer[gateIndex]));
+ } else if (midiMsg.status == mapEntry->cvCommand2 && data1 == mapEntry->cvValue2) {
+ dac_buffer[gateIndex] = lfsr_seeds[gateIndex];
+ }
+ break;
+
+ case MIDIMAP_RANDSEQ_SAH:
+ if (commandFiltered == gateCommand && data1 == mapEntry->gateValue) {
+ gate_set(gateIndex, noteOnFlag);
+ max5825_write(gateIndex, dac_buffer[gateIndex]);
+ }
+ if (midiMsg.status == mapEntry->cvCommand1 && data1 == mapEntry->cvValue1) {
+ updateLfsr(&dac_buffer[gateIndex]);
+ } else if (midiMsg.status == mapEntry->cvCommand2 && data1 == mapEntry->cvValue2) {
+ dac_buffer[gateIndex] = lfsr_seeds[gateIndex];
+ }
+ break;
+ }
+
+ gateIndex++;
+ }
+
+ midiMsg.ready = 0;
+}
+
+inline void midiLearn() {
+ static uint8_t learningIndex = 0;
+ static uint8_t learningMapType = MIDIMAP_VELOCITY;
+ uint8_t nextGateFlag = 0;
+
+ if (midiMsg.ready) {
+ MIDIMapEntry *mapEntry = &midi_map[learningIndex];
+ switch (learningMapType) {
+ case MIDIMAP_VELOCITY:
+ if (IS_NOTE_ON(midiMsg.status)) {
+ mapEntry->mapType = learningMapType;
+ mapEntry->gateCommand = midiMsg.status;
+ mapEntry->gateValue = midiMsg.data1;
+ nextGateFlag = 1;
+ }
+ break;
+ case MIDIMAP_CC:
+ if (IS_NOTE_ON(midiMsg.status)) {
+ mapEntry->mapType = learningMapType;
+ mapEntry->gateCommand = midiMsg.status;
+ mapEntry->gateValue = midiMsg.data1;
+ learningMapType = AWAITING_CC;
+ learnLED.ledState = LED_BLINK2;
+ }
+ break;
+ case MIDIMAP_PITCH:
+ if (IS_NOTE_ON(midiMsg.status)) {
+ mapEntry->mapType = learningMapType;
+ mapEntry->gateCommand = midiMsg.status;
+ nextGateFlag = 1;
+ }
+ break;
+ case MIDIMAP_PITCH_SAH:
+ if (IS_NOTE_ON(midiMsg.status)) {
+ mapEntry->mapType = learningMapType;
+ mapEntry->gateCommand = midiMsg.status;
+ mapEntry->gateValue = midiMsg.data1;
+ learningMapType = AWAITING_PITCH;
+ learnLED.ledState = LED_BLINK2;
+ }
+ break;
+ case MIDIMAP_RANDSEQ:
+ case MIDIMAP_RANDSEQ_SAH:
+ if (IS_NOTE_ON(midiMsg.status)) {
+ mapEntry->mapType = learningMapType;
+ mapEntry->gateCommand = midiMsg.status;
+ learningMapType = AWAITING_STEP;
+ learnLED.ledState = LED_BLINK2;
+ }
+ break;
+ case AWAITING_CC:
+ if ((midiMsg.status & 0xF0) == 0xB0) {
+ mapEntry->cvCommand1 = midiMsg.status;
+ mapEntry->cvValue1 = midiMsg.data1;
+ nextGateFlag = 1;
+ }
+ break;
+ case AWAITING_PITCH:
+ if (IS_NOTE_ON(midiMsg.status)) {
+ mapEntry->cvCommand1 = midiMsg.status;
+ nextGateFlag = 1;
+ }
+ break;
+ case AWAITING_STEP:
+ if (IS_NOTE_ON(midiMsg.status)) {
+ mapEntry->cvCommand1 = midiMsg.status;
+ mapEntry->cvValue1 = midiMsg.data1;
+ learningMapType = AWAITING_RESET;
+ learnLED.ledState = LED_BLINK3;
+ }
+ break;
+ case AWAITING_RESET:
+ if (IS_NOTE_ON(midiMsg.status)) {
+ mapEntry->cvCommand2 = midiMsg.status;
+ mapEntry->cvValue2 = midiMsg.data1;
+ nextGateFlag = 1;
+ }
+ break;
+ default:
+ break;
+ }
+ midiMsg.ready = 0;
+ }
+
+ if (learnButton.buttonState == BUTTON_RELEASED) {
+ switch (learningMapType) {
+ case MIDIMAP_VELOCITY:
+ case MIDIMAP_CC:
+ case MIDIMAP_PITCH:
+ case MIDIMAP_PITCH_SAH:
+ case MIDIMAP_RANDSEQ:
+ learningMapType++;
+ learnLED.ledState = LED_BLINK1;
+ learnLED.ledBlinkCount = learningMapType + 1;
+ break;
+ default:
+ learningMapType = MIDIMAP_VELOCITY;
+ learnLED.ledState = LED_BLINK1;
+ learnLED.ledBlinkCount = learningMapType + 1;
+ break;
+ }
+ }
+
+ if (nextGateFlag) {
+ gate_set(learningIndex, 1);
+ learningIndex++;
+ learningMapType = MIDIMAP_VELOCITY;
+ learnLED.ledState = LED_BLINK1;
+ learnLED.ledBlinkCount = 1;
+ }
+
+ if (learningIndex == NUM_GATES || learnButton.buttonState == BUTTON_HELD) {
+ gate_set_multiple(0xFF, 1);
+ _delay_ms(50);
+ gate_set_multiple(0xFF, 0);
+
+ learningMapType = MIDIMAP_VELOCITY;
+ learnLED.ledState = LED_OFF;
+ learnLED.ledBlinkCount = 1;
+ learningIndex = 0;
+ subRoutine = 0;
+ }
+}
\ No newline at end of file
diff --git a/Community/thorinf/csrc/max5825_control.h b/Community/thorinf/csrc/max5825_control.h
new file mode 100644
index 0000000..310e4a8
--- /dev/null
+++ b/Community/thorinf/csrc/max5825_control.h
@@ -0,0 +1,37 @@
+#ifndef MAX5825_CONTROL_H
+#define MAX5825_CONTROL_H
+
+#include "twi_control.h"
+
+#define MAX5825_ADDR 0x20
+#define MAX5825_REG_REF 0x20
+#define MAX5825_REG_CODEn_LOADall 0xA0
+#define MAX5825_REG_CODEn_LOADn 0xB0
+
+static inline void max5825_init(void) {
+ twi_start();
+ twi_write(MAX5825_ADDR);
+ twi_write(MAX5825_REG_REF | 0b101); // Setup command for reference voltage
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+
+ twi_start();
+ twi_write(MAX5825_ADDR); // Device address with write bit
+ twi_write(MAX5825_REG_CODEn_LOADall);
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+}
+
+static inline void max5825_write(uint8_t channel, uint16_t value) {
+ twi_start();
+ twi_write(MAX5825_ADDR);
+ twi_write(MAX5825_REG_CODEn_LOADn | (channel & 0x0F));
+
+ twi_write((uint8_t)(value >> 4));
+ twi_write((uint8_t)((value & 0x0F) << 4));
+ twi_stop();
+}
+
+#endif
diff --git a/Community/thorinf/csrc/midi_parser.c b/Community/thorinf/csrc/midi_parser.c
new file mode 100644
index 0000000..3db6441
--- /dev/null
+++ b/Community/thorinf/csrc/midi_parser.c
@@ -0,0 +1,72 @@
+#include "midi_parser.h"
+#include
+
+static inline uint8_t data_need(uint8_t s) {
+ uint8_t hi = s & 0xF0;
+ return (hi == 0xC0 || hi == 0xD0) ? 1 : 2; // Program Change and Pressure Value only 1 data byte, else 2
+}
+
+void midi_parser_init(MidiParser *p) { p->running = p->curr = p->need = p->have = p->d1_tmp = p->desync = 0; }
+
+static inline void midi_parser_force_desync(MidiParser *p) {
+ p->running = 0;
+ p->curr = 0;
+ p->need = 0;
+ p->have = 0;
+ p->d1_tmp = 0;
+ p->desync = 1;
+}
+
+uint8_t midi_parse(MidiParser *p, uint8_t b, MidiMsg *out) {
+ if (b >= 0xF8) {
+ // system real-time messages always passes
+ // desync doesn't matter as they're length 1
+ out->status = b;
+ out->d1 = out->d2 = 0;
+ out->len = 1;
+ return 1;
+ }
+
+ if (p->desync) {
+ if (b < 0x80) return 0; // ignore data until status
+ p->desync = 0; // got states, continue below
+ }
+
+ if (b & 0x80) { // status
+ if (b >= 0xF0) { // ignore SysEx/System Common
+ p->running = p->curr = p->need = p->have = 0;
+ return 0;
+ }
+ p->running = p->curr = b;
+ p->need = data_need(p->curr);
+ p->have = 0;
+ return 0;
+ }
+ // data
+ if (p->curr == 0) {
+ if (!p->running) return 0; // stray data
+ p->curr = p->running;
+ p->need = data_need(p->curr);
+ p->have = 0;
+ }
+ if (p->need == 1) {
+ out->status = p->curr;
+ out->d1 = b;
+ out->d2 = 0;
+ out->len = 2;
+ p->curr = 0;
+ return 1;
+ }
+ if (p->have == 0) {
+ p->d1_tmp = b;
+ p->have = 1;
+ return 0;
+ }
+ out->status = p->curr;
+ out->d1 = p->d1_tmp;
+ out->d2 = b;
+ out->len = 3;
+ p->curr = 0;
+ p->have = 0;
+ return 1;
+}
diff --git a/Community/thorinf/csrc/midi_parser.h b/Community/thorinf/csrc/midi_parser.h
new file mode 100644
index 0000000..2460c07
--- /dev/null
+++ b/Community/thorinf/csrc/midi_parser.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include
+
+typedef struct {
+ uint8_t status, d1, d2;
+ uint8_t len;
+} MidiMsg;
+
+typedef struct {
+ uint8_t running;
+ uint8_t curr;
+ uint8_t need, have;
+ uint8_t d1_tmp;
+ uint8_t desync;
+} MidiParser;
+
+void midi_parser_init(MidiParser *p);
+uint8_t midi_parse(MidiParser *p, uint8_t byte, MidiMsg *out);
+static inline void midi_parser_force_desync(MidiParser *p);
diff --git a/Community/thorinf/csrc/midimap.h b/Community/thorinf/csrc/midimap.h
new file mode 100644
index 0000000..e311d1c
--- /dev/null
+++ b/Community/thorinf/csrc/midimap.h
@@ -0,0 +1,66 @@
+#ifndef MIDIMAP_H
+#define MIDIMAP_H
+
+#include
+
+#include "hardware_config.h"
+
+// MIDI Map Types
+#define NUM_MIDIMAP_TYPES 6
+#define MIDIMAP_VELOCITY 0
+#define MIDIMAP_CC 1
+#define MIDIMAP_PITCH 2
+#define MIDIMAP_PITCH_SAH 3
+#define MIDIMAP_RANDSEQ 4
+#define MIDIMAP_RANDSEQ_SAH 5
+
+// MIDI Map Struct Definition
+typedef struct {
+ uint8_t mapType;
+ uint8_t gateCommand;
+ uint8_t gateValue;
+ uint8_t cvCommand1;
+ uint8_t cvValue1;
+ uint8_t cvCommand2;
+ uint8_t cvValue2;
+} MIDIMapEntry;
+
+#define MIDI_MAP_SIZE (sizeof(MIDIMapEntry) * NUM_GATES)
+
+// MIDI mapping for velocity (Original Tram8)
+MIDIMapEntry midi_map_velo[NUM_GATES] = {
+ {MIDIMAP_VELOCITY, 0x90, 24, 0, 0, 0, 0}, // Gate C0
+ {MIDIMAP_VELOCITY, 0x90, 25, 0, 0, 0, 0}, // Gate C#0
+ {MIDIMAP_VELOCITY, 0x90, 26, 0, 0, 0, 0}, // Gate D0
+ {MIDIMAP_VELOCITY, 0x90, 27, 0, 0, 0, 0}, // Gate D#0
+ {MIDIMAP_VELOCITY, 0x90, 28, 0, 0, 0, 0}, // Gate E0
+ {MIDIMAP_VELOCITY, 0x90, 29, 0, 0, 0, 0}, // Gate F0
+ {MIDIMAP_VELOCITY, 0x90, 30, 0, 0, 0, 0}, // Gate F#0
+ {MIDIMAP_VELOCITY, 0x90, 31, 0, 0, 0, 0} // Gate G0
+};
+
+// MIDI mapping for CC (Original Tram8)
+MIDIMapEntry midi_map_cc[NUM_GATES] = {
+ {MIDIMAP_VELOCITY, 0x90, 24, 0xB0, 69, 0, 0}, // Gate C0
+ {MIDIMAP_VELOCITY, 0x90, 25, 0xB0, 70, 0, 0}, // Gate C#0
+ {MIDIMAP_VELOCITY, 0x90, 26, 0xB0, 71, 0, 0}, // Gate D0
+ {MIDIMAP_VELOCITY, 0x90, 27, 0xB0, 72, 0, 0}, // Gate D#0
+ {MIDIMAP_VELOCITY, 0x90, 28, 0xB0, 73, 0, 0}, // Gate E0
+ {MIDIMAP_VELOCITY, 0x90, 29, 0xB0, 74, 0, 0}, // Gate F0
+ {MIDIMAP_VELOCITY, 0x90, 30, 0xB0, 75, 0, 0}, // Gate F#0
+ {MIDIMAP_VELOCITY, 0x90, 31, 0xB0, 76, 0, 0} // Gate G0
+};
+
+// MIDI mapping for the BeatStep Pro
+MIDIMapEntry midi_map_bsp[NUM_GATES] = {
+ {MIDIMAP_RANDSEQ_SAH, 0x97, 36, 0x97, 44, 0x97, 45}, // Gate C0, Step G#0, Reset A0
+ {MIDIMAP_RANDSEQ_SAH, 0x97, 37, 0x97, 46, 0x97, 47}, // Gate C#0, Step A#0, Reset B0
+ {MIDIMAP_RANDSEQ, 0x97, 38, 0x97, 48, 0x97, 49}, // Gate D0, Step C1, Reset C#1
+ {MIDIMAP_RANDSEQ, 0x97, 39, 0x97, 50, 0x97, 51}, // Gate D#0, Step D1, Reset D#1
+ {MIDIMAP_VELOCITY, 0x97, 40, 0, 0, 0, 0}, // Gate E0
+ {MIDIMAP_VELOCITY, 0x97, 41, 0, 0, 0, 0}, // Gate F0
+ {MIDIMAP_PITCH, 0x90, 0, 0, 0, 0, 0}, // Sequencer 1
+ {MIDIMAP_PITCH, 0x91, 0, 0, 0, 0, 0}, // Sequencer 2
+};
+
+#endif
\ No newline at end of file
diff --git a/Community/thorinf/csrc/pin_control.c b/Community/thorinf/csrc/pin_control.c
new file mode 100644
index 0000000..49ffe18
--- /dev/null
+++ b/Community/thorinf/csrc/pin_control.c
@@ -0,0 +1,36 @@
+#include "pin_control.h"
+
+#include
+
+void pin_initialize(void) {
+ GATE_DDR_B |= (1 << GATE_PIN_0); // Set PB0 as output
+ GATE_DDR_D |= 0xFE; // Set PD1 to PD7 as outputs
+
+ DDRC |= (1 << LED_PIN);
+ led_off();
+
+ // This is to match original codebase, true case ought to be default
+ while (!eeprom_is_ready());
+ if (eeprom_read_byte((uint8_t *)EEPROM_BUTTON_FIX_ADDR) == 0xAA) {
+ BUTTON_DDR &= ~(1 << BUTTON_PIN);
+ } else {
+ BUTTON_DDR |= ~(1 << BUTTON_PIN);
+ }
+
+ CONTROL_DDR |= (1 << LDAC_PIN) | (1 << CLR_PIN);
+ LDAC_PORT |= (1 << LDAC_PIN);
+ CLR_PORT |= (1 << CLR_PIN);
+}
+
+void gate_set_multiple(uint8_t gateMask, uint8_t state) {
+ uint8_t portBMask = (gateMask & 0x01) << GATE_PIN_0;
+ uint8_t portDMask = ((gateMask >> 1) & 0x7F) << GATE_PIN_1;
+
+ if (state) {
+ GATE_PORT_B |= portBMask;
+ GATE_PORT_D |= portDMask;
+ } else {
+ GATE_PORT_B &= ~portBMask;
+ GATE_PORT_D &= ~portDMask;
+ }
+}
\ No newline at end of file
diff --git a/Community/thorinf/csrc/pin_control.h b/Community/thorinf/csrc/pin_control.h
new file mode 100644
index 0000000..511636d
--- /dev/null
+++ b/Community/thorinf/csrc/pin_control.h
@@ -0,0 +1,34 @@
+#ifndef PIN_CONTROL_H
+#define PIN_CONTROL_H
+
+#include "hardware_config.h"
+
+void pin_initialize(void);
+
+static inline uint8_t read_button(void) { return BUTTON_PIN_REG & (1 << BUTTON_PIN); }
+
+static inline void led_on(void) { LED_PORT &= ~(1 << LED_PIN); }
+
+static inline void led_off(void) { LED_PORT |= (1 << LED_PIN); }
+
+static inline void gate_set(uint8_t gateIndex, uint8_t state) {
+ static const uint8_t gatePortMasks[NUM_GATES] = {
+ (1 << GATE_PIN_0), (1 << (GATE_PIN_1 + 0)), (1 << (GATE_PIN_1 + 1)), (1 << (GATE_PIN_1 + 2)),
+ (1 << (GATE_PIN_1 + 3)), (1 << (GATE_PIN_1 + 4)), (1 << (GATE_PIN_1 + 5)), (1 << (GATE_PIN_1 + 6))};
+
+ static volatile uint8_t *const gatePorts[NUM_GATES] = {&GATE_PORT_B, &GATE_PORT_D, &GATE_PORT_D, &GATE_PORT_D,
+ &GATE_PORT_D, &GATE_PORT_D, &GATE_PORT_D, &GATE_PORT_D};
+
+ volatile uint8_t *port = gatePorts[gateIndex];
+ uint8_t mask = gatePortMasks[gateIndex];
+
+ if (state) {
+ *port |= mask;
+ } else {
+ *port &= ~mask;
+ }
+}
+
+void gate_set_multiple(uint8_t gateMask, uint8_t state);
+
+#endif
\ No newline at end of file
diff --git a/Community/thorinf/csrc/pitch.h b/Community/thorinf/csrc/pitch.h
new file mode 100644
index 0000000..671c545
--- /dev/null
+++ b/Community/thorinf/csrc/pitch.h
@@ -0,0 +1,32 @@
+#ifndef PITCH_LOOKUP_H
+#define PITCH_LOOKUP_H
+
+#include
+
+#define PITCH_SIZE 61
+
+// enum Pitch {
+// C0 = 0, Cs0, D0, Ds0, E0, F0, Fs0, G0, Gs0, A0, As0, B0,
+// C1, Cs1, D1, Ds1, E1, F1, Fs1, G1, Gs1, A1, As1, B1,
+// C2, Cs2, D2, Ds2, E2, F2, Fs2, G2, Gs2, A2, As2, B2,
+// C3, Cs3, D3, Ds3, E3, F3, Fs3, G3, Gs3, A3, As3, B3,
+// C4, Cs4, D4, Ds4, E4, F4, Fs4, G4, Gs4, A4, As4, B4,
+// C5, Cs5, D5, Ds5, E5, F5, Fs5, G5, Gs5, A5, As5, B5,
+// C6, Cs6, D6, Ds6, E6, F6, Fs6, G6, Gs6, A6, As6, B6,
+// C7, Cs7, D7, Ds7, E7, F7, Fs7, G7, Gs7, A7, As7, B7,
+// C8, Cs8, D8, Ds8, E8, F8, Fs8, G8, Gs8, A8, As8, B8,
+// C9, Cs9, D9, Ds9, E9, F9, Fs9, G9, Gs9, A9, As9, B9,
+// C10, Cs10, D10, Ds10, E10, F10, Fs10, G10
+// };
+
+// Pitch lookup array definition
+static const uint16_t pitch_lookup[61] = {
+ 0x0000, 0x0440, 0x0880, 0x0CD0, 0x1110, 0x1550, 0x19A0, 0x1DE0, 0x2220, 0x2660, 0x2AA0, 0x2EF0, // C-2
+ 0x3330, 0x3770, 0x3BC0, 0x4000, 0x4440, 0x4880, 0x4CC0, 0x5110, 0x5550, 0x5990, 0x5DE0, 0x6220, // C-1
+ 0x6660, 0x6AA0, 0x6EE0, 0x7330, 0x7770, 0x7BB0, 0x8000, 0x8440, 0x8880, 0x8CC0, 0x9100, 0x9550, // C0
+ 0x9990, 0x9DD0, 0xA220, 0xA660, 0xAAA0, 0xAEE0, 0xB320, 0xB770, 0xBBB0, 0xBFF0, 0xC440, 0xC880, // C1
+ 0xCCC0, 0xD100, 0xD550, 0xD990, 0xDDD0, 0xE210, 0xE660, 0xEAA0, 0xEEE0, 0xF320, 0xF760, 0xFBB0, // C2
+ 0xFFF0 // C3
+};
+
+#endif
diff --git a/Community/thorinf/csrc/random.c b/Community/thorinf/csrc/random.c
new file mode 100644
index 0000000..2155afe
--- /dev/null
+++ b/Community/thorinf/csrc/random.c
@@ -0,0 +1,13 @@
+#include "random.h"
+
+inline uint16_t updateLfsr(uint16_t *lfsr) {
+ uint16_t bit = ((*lfsr >> 0) ^ (*lfsr >> 2) ^ (*lfsr >> 3) ^ (*lfsr >> 5)) & 1;
+ *lfsr = (*lfsr >> 1) | (bit << 15);
+ return *lfsr;
+}
+
+inline uint16_t updateLfsrAlt(uint16_t *lfsr) {
+ uint16_t bit = ((*lfsr >> 1) ^ (*lfsr >> 3) ^ (*lfsr >> 4) ^ (*lfsr >> 8)) & 1;
+ *lfsr = (*lfsr >> 1) | (bit << 15);
+ return *lfsr;
+}
\ No newline at end of file
diff --git a/Community/thorinf/csrc/random.h b/Community/thorinf/csrc/random.h
new file mode 100644
index 0000000..d36bb78
--- /dev/null
+++ b/Community/thorinf/csrc/random.h
@@ -0,0 +1,9 @@
+#ifndef RANDOM_H
+#define RANDOM_H
+
+#include
+
+uint16_t updateLfsr(uint16_t *lfsr);
+uint16_t updateLfsrAlt(uint16_t *lfsr);
+
+#endif
\ No newline at end of file
diff --git a/Community/thorinf/csrc/twi_control.h b/Community/thorinf/csrc/twi_control.h
new file mode 100644
index 0000000..0bbf89f
--- /dev/null
+++ b/Community/thorinf/csrc/twi_control.h
@@ -0,0 +1,37 @@
+#ifndef TWI_CONTROL_H
+#define TWI_CONTROL_H
+
+#include
+
+#ifndef F_CPU
+#define F_CPU 16000000UL
+#endif
+
+#ifndef TWI_FREQ
+#define TWI_FREQ 400000UL
+#endif
+
+
+void twi_init(void) {
+ TWSR = 0x00;
+ TWBR = (uint8_t)(((F_CPU / TWI_FREQ) - 16) / 2);
+ TWCR = (1 << TWEN);
+}
+
+static inline void twi_start(void) {
+ TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
+ while (!(TWCR & (1 << TWINT)));
+}
+
+static inline void twi_stop(void) {
+ TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
+ // No need to wait for stop condition to complete
+}
+
+static inline void twi_write(uint8_t data) {
+ TWDR = data;
+ TWCR = (1 << TWINT) | (1 << TWEN);
+ while (!(TWCR & (1 << TWINT)));
+}
+
+#endif
diff --git a/Community/thorinf/js/index.html b/Community/thorinf/js/index.html
new file mode 100644
index 0000000..b9bd520
--- /dev/null
+++ b/Community/thorinf/js/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+ Tram8 MIDI Mapper
+
+
+
+
+ Tram8 MIDI Mapper
+
+
+ Send SysEx Message
+
+
diff --git a/Community/thorinf/js/script.js b/Community/thorinf/js/script.js
new file mode 100644
index 0000000..c56e312
--- /dev/null
+++ b/Community/thorinf/js/script.js
@@ -0,0 +1,246 @@
+const MIDIMAP_VELOCITY = 0;
+const MIDIMAP_CC = 1;
+const MIDIMAP_PITCH = 2;
+const MIDIMAP_PITCH_SAH = 3;
+const MIDIMAP_RANDSEQ = 4;
+const MIDIMAP_RANDSEQ_SAH = 5;
+
+const channelOptions = Array.from({ length: 16 }, (_, i) => ({ value: 0x90 + i, text: `Channel ${i + 1}` }));
+const noteOptions = Array.from({ length: 128 }, (_, i) => ({ value: i, text: `Note ${i}` }));
+const controllerOptions = Array.from({ length: 128 }, (_, i) => ({ value: i, text: `Controller ${i}` }));
+
+const midiModeOptions = [
+ {
+ value: 0,
+ text: "Velocity",
+ requiredOptions: [channelOptions, noteOptions],
+ requiredLabels: ["Gate Channel", "Gate Note"]
+ },
+ {
+ value: 1,
+ text: "Control Change",
+ requiredOptions: [channelOptions, noteOptions, channelOptions, controllerOptions],
+ requiredLabels: ["Gate Channel", "Gate Note", "Controller Channel", "Controller"]
+ },
+ {
+ value: 2,
+ text: "Pitch",
+ requiredOptions: [channelOptions],
+ requiredLabels: ["Pitch Channel"]
+ },
+ {
+ value: 3,
+ text: "Pitch, Sample & Hold",
+ requiredOptions: [channelOptions, noteOptions, channelOptions],
+ requiredLabels: ["Gate Channel", "Gate Note", "Pitch Channel"]
+ },
+ {
+ value: 4,
+ text: "Random Step Sequencer",
+ requiredOptions: [channelOptions, noteOptions, channelOptions, noteOptions, channelOptions, noteOptions],
+ requiredLabels: ["Gate Channel", "Gate Note", "Step Channel", "Step Note", "Reset Channel", "Reset Note"]
+ },
+ {
+ value: 5,
+ text: "Random Step Sequencer, Sample & Hold",
+ requiredOptions: [channelOptions, noteOptions, channelOptions, noteOptions, channelOptions, noteOptions],
+ requiredLabels: ["Gate Channel", "Gate Note", "Step Channel", "Step Note", "Reset Channel", "Reset Note"]
+ }
+];
+
+function loadInitialData() {
+ const savedArray = localStorage.getItem('globalArray');
+ if (savedArray) {
+ return JSON.parse(savedArray);
+ } else {
+ return [
+ [MIDIMAP_VELOCITY, 0x90, 24, 0, 0, 0, 0],
+ [MIDIMAP_VELOCITY, 0x90, 25, 0, 0, 0, 0],
+ [MIDIMAP_VELOCITY, 0x90, 26, 0, 0, 0, 0],
+ [MIDIMAP_VELOCITY, 0x90, 27, 0, 0, 0, 0],
+ [MIDIMAP_VELOCITY, 0x90, 28, 0, 0, 0, 0],
+ [MIDIMAP_VELOCITY, 0x90, 29, 0, 0, 0, 0],
+ [MIDIMAP_VELOCITY, 0x90, 30, 0, 0, 0, 0],
+ [MIDIMAP_VELOCITY, 0x90, 31, 0, 0, 0, 0]
+ ];
+ }
+}
+
+function initializeDropdowns() {
+ const dropdownArea = document.getElementById('dropdownArea');
+ dropdownArea.innerHTML = '';
+ globalArray = loadInitialData();
+ console.log("Loaded array: ", globalArray);
+
+ globalArray.slice(0, 8).forEach((row, index) => {
+ const selectedMidiMode = row[0];
+ const dropdown = createDropdown(midiModeOptions, selectedMidiMode, (newValue) => {
+ updateGlobalArray(index, 0, newValue);
+ rebuildChildDropdowns(index, newValue);
+ updateTextAreaFromArray();
+ });
+
+ const label = document.createElement('label');
+ label.textContent = `${index + 1}.`;
+
+ const labelDropdownWrapper = document.createElement('div');
+ labelDropdownWrapper.classList.add('label-dropdown-wrapper');
+ labelDropdownWrapper.appendChild(label);
+ labelDropdownWrapper.appendChild(dropdown);
+
+ const container = document.createElement('div');
+ container.classList.add('dropdown-container');
+ container.appendChild(labelDropdownWrapper);
+
+ const childContainer = document.createElement('div');
+ childContainer.classList.add('child-container');
+ container.appendChild(childContainer);
+
+ dropdownArea.appendChild(container);
+
+ rebuildChildDropdowns(index, selectedMidiMode);
+ });
+
+ updateTextAreaFromArray();
+}
+
+function rebuildChildDropdowns(rowIndex, midiMode) {
+ const container = document.querySelectorAll('.child-container')[rowIndex];
+ container.innerHTML = ''; // Clear existing dropdowns
+
+ const row = globalArray[rowIndex];
+ const { requiredLabels, requiredOptions } = midiModeOptions[midiMode];
+
+ requiredLabels.forEach((labelText, i) => {
+ const dropdownOptions = requiredOptions[i];
+ const selectedValue = row[i + 1];
+
+ const dropdown = createDropdown(dropdownOptions, selectedValue, (newValue) => {
+ updateGlobalArray(rowIndex, i + 1, newValue);
+ updateTextAreaFromArray();
+ });
+
+ const label = document.createElement('label');
+ label.textContent = labelText;
+
+ const wrapper = document.createElement('div');
+ wrapper.classList.add('label-dropdown-wrapper');
+ wrapper.appendChild(label);
+ wrapper.appendChild(dropdown);
+
+ container.appendChild(wrapper);
+ });
+}
+
+
+function createDropdown(options, selectedValue, onChangeCallback) {
+ const dropdown = document.createElement('select');
+ options.forEach(option => {
+ const opt = document.createElement('option');
+ opt.value = option.value;
+ opt.textContent = option.text;
+ if (option.value === selectedValue) {
+ opt.selected = true;
+ }
+ dropdown.appendChild(opt);
+ });
+
+ dropdown.addEventListener('change', function (event) {
+ const newValue = parseInt(event.target.value);
+ onChangeCallback(newValue);
+ });
+
+ return dropdown;
+}
+
+function updateGlobalArray(rowIndex, columnIndex, newValue) {
+ globalArray[rowIndex][columnIndex] = newValue;
+ console.log("Updated globalArray:", globalArray);
+ localStorage.setItem('globalArray', JSON.stringify(globalArray));
+}
+
+function updateTextAreaFromArray() {
+ const arrayTextArea = document.getElementById('arrayTextArea');
+
+ const maskedArray = globalArray.slice(0, 8).map((row) => {
+ const numKeep = midiModeOptions[row[0]].requiredLabels.length + 1;
+ const numPad = row.length - numKeep;
+ return row.slice(0, numKeep).concat(Array(numPad).fill(0));
+ });
+
+ arrayTextArea.value = JSON.stringify(maskedArray, null, 0);
+}
+
+function updateArrayFromTextArea() {
+ console.log("Tryinng to update from textbox...");
+ const arrayTextArea = document.getElementById('arrayTextArea');
+
+ try {
+ const parsedArray = JSON.parse(arrayTextArea.value);
+
+ if (Array.isArray(parsedArray)) {
+ parsedArray.slice(0, 8).forEach((row, index) => {
+ const numKeep = midiModeOptions[row[0]].requiredLabels.length + 1
+ globalArray[index].splice(0, numKeep, ...parsedArray[index].slice(0, numKeep));
+ });
+
+ localStorage.setItem('globalArray', JSON.stringify(globalArray));
+
+ console.log("globalArray updated successfully:", globalArray);
+ initializeDropdowns();
+ } else {
+ console.error("The content in the textarea is not a valid array.");
+ }
+ } catch (error) {
+ console.error("Invalid JSON format:", error);
+ }
+}
+
+async function sendSysExMessageWithPauses() {
+ if (navigator.requestMIDIAccess) {
+ try {
+ const midiAccess = await navigator.requestMIDIAccess({ sysex: true });
+ onMIDISuccess(midiAccess);
+ } catch (error) {
+ console.error("Failed to access MIDI devices:", error);
+ }
+ } else {
+ alert("Web MIDI API is not supported in this browser.");
+ }
+}
+
+async function onMIDISuccess(midiAccess) {
+ const outputs = Array.from(midiAccess.outputs.values());
+
+ const maskedArray = globalArray.slice(0, 8).map((row) => {
+ const numKeep = midiModeOptions[row[0]].requiredLabels.length + 1;
+ const numPad = row.length - numKeep;
+ return row.slice(0, numKeep).concat(Array(numPad).fill(0));
+ });
+
+ if (outputs.length > 0) {
+ const output = outputs[0];
+ const sysExMessage = asSysEx(maskedArray)
+ output.send(sysExMessage);
+ console.log("Sent SysEx message:", sysExMessage);
+ } else {
+ console.log("No MIDI output devices available.");
+ }
+}
+
+function asSysEx(array) {
+ const sysExArray = [0xF0];
+ array.flat().forEach(value => {
+ sysExArray.push(value & 0x7F); // Push the least significant 7 bits
+ sysExArray.push((value >>= 7) & 0x7F); // Push the remaining 7 bits if non-zero
+ });
+ sysExArray.push(0xF7);
+ return sysExArray;
+}
+
+function delay(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+document.getElementById("sendSysExButton").addEventListener("click", sendSysExMessageWithPauses);
+window.onload = initializeDropdowns;
diff --git a/Community/thorinf/js/styles.css b/Community/thorinf/js/styles.css
new file mode 100644
index 0000000..5616fd8
--- /dev/null
+++ b/Community/thorinf/js/styles.css
@@ -0,0 +1,102 @@
+body {
+ font-family: 'Arial', sans-serif;
+ background-color: #f9f9fb;
+ color: #333;
+ padding: 20px;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+h1 {
+ color: #333;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ font-size: 24px;
+}
+
+#dropdownArea {
+ background-color: #fff;
+ padding: 0px;
+ width: auto;
+ margin-bottom: 20px;
+ border-radius: 10px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.dropdown-container {
+ margin-bottom: 0px;
+ margin-left: 20px;
+ padding: 10px 0;
+ border-bottom: 1px solid #eaeaea;
+}
+
+.child-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ margin-top: 10px;
+}
+
+select {
+ min-width: 150px;
+ padding: 10px 12px;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ background-color: #fff;
+ cursor: pointer;
+ transition: border-color 0.3s;
+ margin-left: 15px;
+}
+
+select:hover, select:focus {
+ border-color: #888;
+ outline: none;
+}
+
+#arrayTextArea {
+ width: calc(100% - 20px);
+ padding: 10px;
+ border-radius: 5px;
+ border: 1px solid #ccc;
+ resize: vertical;
+ margin-bottom: 0px;
+ background-color: #ffffff;
+ font-family: monospace;
+ font-size: 14px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+}
+
+button {
+ padding: 10px 18px;
+ background-color: #007bff;
+ color: white;
+ font-size: 15px;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ transition: background-color 0.3s, transform 0.3s;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+}
+
+button:hover {
+ background-color: #0056b3;
+ transform: translateY(-2px);
+}
+
+button:focus {
+ outline: none;
+}
+
+@media (max-width: 768px) {
+ #dropdownArea {
+ padding: 10px;
+ }
+
+ .child-container {
+ gap: 10px;
+ }
+
+ select {
+ min-width: 100px;
+ }
+}
diff --git a/Community/thorinf/resources/midi_mapper_tool.PNG b/Community/thorinf/resources/midi_mapper_tool.PNG
new file mode 100644
index 0000000..b5fd745
Binary files /dev/null and b/Community/thorinf/resources/midi_mapper_tool.PNG differ
diff --git a/Community/thorinf/resources/tram8.PNG b/Community/thorinf/resources/tram8.PNG
new file mode 100644
index 0000000..b98c6b9
Binary files /dev/null and b/Community/thorinf/resources/tram8.PNG differ
diff --git a/Community/thorinf/thorinf_firmware.syx b/Community/thorinf/thorinf_firmware.syx
new file mode 100644
index 0000000..bc18fd0
Binary files /dev/null and b/Community/thorinf/thorinf_firmware.syx differ
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..84fe5da
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,20 @@
+# Use an official Ubuntu runtime as a parent image
+FROM ubuntu:latest
+
+# Update the system and install AVR GCC toolchain, Make, Python3, and pip
+RUN apt-get update && apt-get install -y \
+ git \
+ gcc-avr \
+ binutils-avr \
+ avr-libc \
+ make \
+ python3 \
+ python3-pip
+
+# Install Python packages using pip
+RUN pip3 install intelhex
+RUN pip3 install numpy
+
+# Clean up APT when done.
+RUN apt-get clean && \
+ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
diff --git a/Experiments/01GateCycle/Makefile b/Experiments/01GateCycle/Makefile
new file mode 100644
index 0000000..c05df6a
--- /dev/null
+++ b/Experiments/01GateCycle/Makefile
@@ -0,0 +1,22 @@
+CC = avr-gcc
+CFLAGS = -g -Os -mmcu=atmega8
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+BUILD_DIR = build
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: main.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf | $(BUILD_DIR)
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR):
+ mkdir -p $@
\ No newline at end of file
diff --git a/Experiments/01GateCycle/main.c b/Experiments/01GateCycle/main.c
new file mode 100644
index 0000000..a6d464f
--- /dev/null
+++ b/Experiments/01GateCycle/main.c
@@ -0,0 +1,51 @@
+// Define the CPU frequency and MIDI baud rate
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+
+#include
+#include
+
+// Define the number of gates and their respective hardware configuration
+#define NUM_GATES 8
+#define GATE_PORT PORTD
+#define GATE_PORT_0 PORTB
+#define GATE_DDR DDRD
+#define GATE_DDR_0 DDRB
+#define GATE_PIN_0 PB0
+#define GATE_PIN_1 PD1
+
+// Macros for setting and clearing bits
+#define SET_BIT(PORT, PIN) ((PORT) |= (1 << (PIN)))
+#define CLEAR_BIT(PORT, PIN) ((PORT) &= ~(1 << (PIN)))
+
+// Function prototypes
+void setup(void);
+void setGate(uint8_t gateIndex, uint8_t state);
+
+void setup() {
+ // Set gate pins as outputs
+ GATE_DDR_0 |= (1 << GATE_PIN_0); // Set PB0 as output
+ GATE_DDR |= 0xFE; // Set PD1 to PD7 as outputs (0xFE = 0b11111110)
+}
+
+void setGate(uint8_t gateIndex, uint8_t state) {
+ uint8_t pin = (gateIndex == 0) ? GATE_PIN_0 : GATE_PIN_1 + gateIndex - 1;
+ volatile uint8_t* port = (gateIndex == 0) ? &GATE_PORT_0 : &GATE_PORT;
+ if (state) {
+ SET_BIT(*port, pin);
+ } else {
+ CLEAR_BIT(*port, pin);
+ }
+}
+
+int main(void) {
+ setup();
+
+ while (1) {
+ static uint8_t ctrlIndex = 0;
+ setGate(ctrlIndex, 1);
+ _delay_ms(100);
+ setGate(ctrlIndex, 0);
+ ctrlIndex = (ctrlIndex + 1) % NUM_GATES;
+ }
+}
diff --git a/Experiments/02ToggleDAC/Makefile b/Experiments/02ToggleDAC/Makefile
new file mode 100644
index 0000000..c05df6a
--- /dev/null
+++ b/Experiments/02ToggleDAC/Makefile
@@ -0,0 +1,22 @@
+CC = avr-gcc
+CFLAGS = -g -Os -mmcu=atmega8
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+BUILD_DIR = build
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: main.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf | $(BUILD_DIR)
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR):
+ mkdir -p $@
\ No newline at end of file
diff --git a/Experiments/02ToggleDAC/main.c b/Experiments/02ToggleDAC/main.c
new file mode 100644
index 0000000..98cc7bd
--- /dev/null
+++ b/Experiments/02ToggleDAC/main.c
@@ -0,0 +1,106 @@
+// Define the CPU frequency and MIDI baud rate
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+
+#include
+#include
+
+// TWI (I2C) parameters, these are predefined in the ATmega8 - here for reference
+#define SDA_PIN PC4 // SDA pin
+#define SCL_PIN PC5 // SCL pin
+
+// Pins for additional control
+#define LDAC_PIN PC2
+#define CLR_PIN PC3
+#define LDAC_PORT PORTC
+#define CLR_PORT PORTC
+#define CONTROL_DDR DDRC
+
+// MAX5825 DAC Definitions
+#define MAX5825_ADDR 0x20 // 0010 000W, where W is the write bit
+#define MAX5825_REG_REF 0x20
+#define MAX5825_REG_CODEn_LOADn 0xB0 // 1011 XXXX, where X encodes the DAC channel
+#define MAX5825_REG_CODEn_LOADall 0xA0
+
+// Macros for setting and clearing bits
+#define SET_BIT(PORT, PIN) ((PORT) |= (1 << (PIN)))
+#define CLEAR_BIT(PORT, PIN) ((PORT) &= ~(1 << (PIN)))
+
+// Function prototypes
+void setup(void);
+void twi_init(void);
+void twi_start(void);
+void twi_stop(void);
+void twi_write(uint8_t data);
+void max5825_write(uint8_t channel, uint16_t value);
+
+void setup() {
+ // Initialize control pins for DAC
+ CONTROL_DDR |= (1 << LDAC_PIN) | (1 << CLR_PIN); // Set LDAC and CLR as outputs
+ // Set LDAC high to hold the DAC output (active-low signal, high means no update)
+ LDAC_PORT |= (1 << LDAC_PIN);
+ // Set CLR high to avoid clearing the DAC (active-low signal, high means no clear)
+ CLR_PORT |= (1 << CLR_PIN);
+
+ // Initialize TWI (I2C)
+ twi_init();
+
+ // Initialize MAX5825 DAC
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write((MAX5825_REG_REF | 0b101)); // DAC channel
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write(MAX5825_REG_CODEn_LOADall); // DAC channel
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+}
+
+void twi_init(void) {
+ TWSR = 0x00; // Set prescaler to zero
+ TWBR = 12; // Baud rate set to 400kHz assuming F_CPU is 16MHz and no prescaler
+ TWCR = (1 << TWEN); // Enable TWI
+}
+
+void twi_start(void) {
+ TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
+ while (!(TWCR &
+ (1 << TWINT))); // Wait for TWINT Flag set. This indicates that the START condition has been transmitted
+}
+
+void twi_stop(void) { TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); }
+
+void twi_write(uint8_t data) {
+ TWDR = data;
+ TWCR = (1 << TWINT) | (1 << TWEN);
+ while (!(TWCR & (1 << TWINT))); // Wait for TWINT Flag set. This indicates that the data has been transmitted, and
+ // ACK/NACK has been received
+}
+
+void max5825_write(uint8_t channel, uint16_t value) {
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write(MAX5825_REG_CODEn_LOADn | (channel & 0x0F)); // DAC channel
+ // Send the 12-bit data
+ // The upper 8 bits of the value (MSB)
+ twi_write((uint8_t)((value >> 8) & 0xFF));
+ // The lower 4 bits of the value (LSB) shifted into the upper 4 bits of the byte, as the MAX5825 expects it
+ twi_write((uint8_t)(value & 0xF0));
+ twi_stop();
+}
+
+int main(void) {
+ setup();
+
+ while (1) {
+ max5825_write(0, 0xFFFF);
+ _delay_ms(500);
+ max5825_write(0, 0x0000);
+ _delay_ms(500);
+ }
+}
diff --git a/Experiments/03NoteOn/Makefile b/Experiments/03NoteOn/Makefile
new file mode 100644
index 0000000..c05df6a
--- /dev/null
+++ b/Experiments/03NoteOn/Makefile
@@ -0,0 +1,22 @@
+CC = avr-gcc
+CFLAGS = -g -Os -mmcu=atmega8
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+BUILD_DIR = build
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: main.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf | $(BUILD_DIR)
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR):
+ mkdir -p $@
\ No newline at end of file
diff --git a/Experiments/03NoteOn/main.c b/Experiments/03NoteOn/main.c
new file mode 100644
index 0000000..0282fe1
--- /dev/null
+++ b/Experiments/03NoteOn/main.c
@@ -0,0 +1,99 @@
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+#define MY_UBRR F_CPU / 16 / BAUD - 1
+
+#include
+#include
+
+// Define the number of gates and their respective hardware configuration
+#define NUM_GATES 8
+#define GATE_PORT PORTD
+#define GATE_PORT_0 PORTB
+#define GATE_DDR DDRD
+#define GATE_DDR_0 DDRB
+#define GATE_PIN_0 PB0
+#define GATE_PIN_1 PD1
+
+// MIDI message structure
+typedef struct {
+ uint8_t status;
+ uint8_t data1;
+ uint8_t data2;
+ volatile uint8_t ready;
+} MIDI_Message;
+
+volatile MIDI_Message midiMsg = {0, 0, 0, 0};
+
+// Macros for setting and clearing bits
+#define SET_BIT(PORT, PIN) ((PORT) |= (1 << (PIN)))
+#define CLEAR_BIT(PORT, PIN) ((PORT) &= ~(1 << (PIN)))
+
+// Function prototypes
+void setup(void);
+void USART_Init(unsigned int ubrr);
+void setGate(uint8_t gateIndex, uint8_t state);
+void processMIDI(void);
+
+int main(void) {
+ setup();
+
+ while (1) {
+ }
+}
+
+void setup() {
+ GATE_DDR_0 |= (1 << GATE_PIN_0); // Set PB0 as output
+ GATE_DDR |= 0xFE; // Set PD1 to PD7 as outputs (0xFE = 0b11111110)
+
+ USART_Init(MY_UBRR);
+ sei();
+}
+
+void USART_Init(unsigned int ubrr) {
+ UBRRH = (unsigned char)(ubrr >> 8);
+ UBRRL = (unsigned char)ubrr;
+ UCSRB = (1 << RXEN) | (1 << RXCIE); // Enable receiver and receive complete interrupt
+ UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0); // 8-bit data
+}
+
+ISR(USART_RXC_vect) {
+ static uint8_t midiState = 0;
+ uint8_t byte = UDR; // Read the received MIDI byte from the UDR register
+
+ switch (midiState) {
+ case 0:
+ if (byte >= 0x80 && byte < 0xF0) { // MIDI command byte
+ midiMsg.status = byte;
+ midiState = 1;
+ }
+ break;
+ case 1:
+ midiMsg.data1 = byte;
+ midiState = 2;
+ break;
+ case 2:
+ midiMsg.data2 = byte;
+ midiMsg.ready = 1; // Mark message as ready
+ midiState = 0;
+ processMIDI();
+ break;
+ }
+}
+
+inline void setGate(uint8_t gateIndex, uint8_t state) {
+ uint8_t pin = (gateIndex == 0) ? GATE_PIN_0 : GATE_PIN_1 + gateIndex - 1;
+ volatile uint8_t* port = (gateIndex == 0) ? &GATE_PORT_0 : &GATE_PORT;
+ if (state)
+ SET_BIT(*port, pin);
+ else
+ CLEAR_BIT(*port, pin);
+}
+
+void processMIDI() {
+ if ((midiMsg.status & 0xF0) == 0x90) { // Note on
+ setGate(midiMsg.data1 % NUM_GATES, 1);
+ } else if ((midiMsg.status & 0xF0) == 0x80) { // Note off
+ setGate(midiMsg.data1 % NUM_GATES, 0);
+ }
+ midiMsg.ready = 0; // Reset the ready flag
+}
diff --git a/Experiments/04PitchDAC/Makefile b/Experiments/04PitchDAC/Makefile
new file mode 100644
index 0000000..c05df6a
--- /dev/null
+++ b/Experiments/04PitchDAC/Makefile
@@ -0,0 +1,22 @@
+CC = avr-gcc
+CFLAGS = -g -Os -mmcu=atmega8
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+BUILD_DIR = build
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: main.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf | $(BUILD_DIR)
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR):
+ mkdir -p $@
\ No newline at end of file
diff --git a/Experiments/04PitchDAC/main.c b/Experiments/04PitchDAC/main.c
new file mode 100644
index 0000000..63881a8
--- /dev/null
+++ b/Experiments/04PitchDAC/main.c
@@ -0,0 +1,168 @@
+#include
+#include
+
+// Define the CPU frequency and MIDI baud rate
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+#define MY_UBRR F_CPU / 16 / BAUD - 1
+
+// TWI (I2C) parameters, these are predefined in the ATmega8 - here for reference
+#define SDA_PIN PC4 // SDA pin
+#define SCL_PIN PC5 // SCL pin
+
+// Pins for additional control
+#define LDAC_PIN PC2
+#define CLR_PIN PC3
+#define LDAC_PORT PORTC
+#define CLR_PORT PORTC
+#define CONTROL_DDR DDRC
+
+// MAX5825 DAC Definitions
+#define MAX5825_ADDR 0x20 // 0010 000W, where W is the write bit
+#define MAX5825_REG_REF 0x20
+#define MAX5825_REG_CODEn_LOADall 0xA0
+#define MAX5825_REG_CODEn_LOADn 0xB0 // 1011 XXXX, where X encodes the DAC channel
+
+typedef struct {
+ uint8_t status;
+ uint8_t data1;
+ uint8_t data2;
+ volatile uint8_t ready;
+} MIDI_Message;
+
+volatile MIDI_Message midiMsg = {0, 0, 0, 0}; // Ensure the structure is volatile
+
+// Lookup table for pitch values
+uint16_t pitch_lookup[61] = {
+ 0x0000, 0x0440, 0x0880, 0x0CD0, 0x1110, 0x1550, 0x19A0, 0x1DE0, 0x2220, 0x2660, 0x2AA0, 0x2EF0, // C-2
+ 0x3330, 0x3770, 0x3BC0, 0x4000, 0x4440, 0x4880, 0x4CC0, 0x5110, 0x5550, 0x5990, 0x5DE0, 0x6220, // C-1
+ 0x6660, 0x6AA0, 0x6EE0, 0x7330, 0x7770, 0x7BB0, 0x8000, 0x8440, 0x8880, 0x8CC0, 0x9100, 0x9550, // C0
+ 0x9990, 0x9DD0, 0xA220, 0xA660, 0xAAA0, 0xAEE0, 0xB320, 0xB770, 0xBBB0, 0xBFF0, 0xC440, 0xC880, // C1
+ 0xCCC0, 0xD100, 0xD550, 0xD990, 0xDDD0, 0xE210, 0xE660, 0xEAA0, 0xEEE0, 0xF320, 0xF760, 0xFBB0, // C2
+ 0xFFF0, // C3
+};
+
+// Macros for setting and clearing bits
+#define SET_BIT(PORT, PIN) ((PORT) |= (1 << (PIN)))
+#define CLEAR_BIT(PORT, PIN) ((PORT) &= ~(1 << (PIN)))
+
+// Function prototypes
+void setup(void);
+void USART_Init(unsigned int ubrr);
+ISR(USART_RXC_vect);
+void twi_init(void);
+void twi_start(void);
+void twi_stop(void);
+void twi_write(uint8_t data);
+void max5825_write(uint8_t channel, uint16_t value);
+
+void setup() {
+ // Initialize control pins for DAC
+ CONTROL_DDR |= (1 << LDAC_PIN) | (1 << CLR_PIN); // Set LDAC and CLR as outputs
+ // Set LDAC high to hold the DAC output (active-low signal, high means no update)
+ LDAC_PORT |= (1 << LDAC_PIN);
+ // Set CLR high to avoid clearing the DAC (active-low signal, high means no clear)
+ CLR_PORT |= (1 << CLR_PIN);
+
+ // Initialize TWI (I2C)
+ twi_init();
+
+ // Initialize MAX5825 DAC
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write((MAX5825_REG_REF | 0b101)); // DAC channel
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write(MAX5825_REG_CODEn_LOADall); // DAC channel
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+}
+
+void USART_Init(unsigned int ubrr) {
+ UBRRH = (unsigned char)(ubrr >> 8);
+ UBRRL = (unsigned char)ubrr;
+ UCSRB = (1 << RXEN) | (1 << RXCIE);
+ UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0);
+}
+
+ISR(USART_RXC_vect) {
+ static uint8_t midiState = 0;
+ uint8_t byte = UDR; // Read the received MIDI byte from the UDR register
+
+ // Parse the MIDI messages that are 3 bytes long
+ switch (midiState) {
+ case 0:
+ if (byte >= 0x80 && byte < 0xF0) {
+ midiMsg.status = byte;
+ midiState = 1;
+ }
+ break;
+ case 1:
+ midiMsg.data1 = byte;
+ midiState = 2;
+ break;
+ case 2:
+ midiMsg.data2 = byte;
+ midiMsg.ready = 1; // Message is ready
+ midiState = 0;
+ break;
+ }
+}
+
+void twi_init(void) {
+ TWSR = 0x00; // Set prescaler to zero
+ TWBR = 12; // Baud rate set to 400kHz assuming F_CPU is 16MHz and no prescaler
+ TWCR = (1 << TWEN); // Enable TWI
+}
+
+void twi_start(void) {
+ TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
+ while (!(TWCR &
+ (1 << TWINT))); // Wait for TWINT Flag set. This indicates that the START condition has been transmitted
+}
+
+void twi_stop(void) { TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); }
+
+void twi_write(uint8_t data) {
+ TWDR = data;
+ TWCR = (1 << TWINT) | (1 << TWEN);
+ while (!(TWCR & (1 << TWINT))); // Wait for TWINT Flag set. This indicates that the data has been transmitted, and
+ // ACK/NACK has been received
+}
+
+void max5825_write(uint8_t channel, uint16_t value) {
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write(MAX5825_REG_CODEn_LOADn | (channel & 0x0F)); // DAC channel
+ // Send the 12-bit data
+ // The upper 8 bits of the value (MSB)
+ twi_write((uint8_t)((value >> 8) & 0xFF));
+ // The lower 4 bits of the value (LSB) shifted into the upper 4 bits of the byte, as the MAX5825 expects it
+ twi_write((uint8_t)(value & 0xF0));
+ twi_stop();
+}
+
+int main(void) {
+ setup();
+ USART_Init(MY_UBRR);
+ sei();
+
+ while (1) {
+ if (midiMsg.ready) {
+ // Process MIDI message here
+ uint8_t channel = midiMsg.status & 0x0F;
+ uint8_t command = midiMsg.status & 0xF0;
+ if (command == 0x90 && midiMsg.data2 > 0) { // Note on message with non-zero velocity
+ if (midiMsg.data1 < 61) { // Ensure the note is within the range of the lookup table
+ max5825_write(channel, pitch_lookup[midiMsg.data1]);
+ }
+ }
+ midiMsg.ready = 0;
+ }
+ }
+}
diff --git a/Experiments/05EEPROM/Makefile b/Experiments/05EEPROM/Makefile
new file mode 100644
index 0000000..c05df6a
--- /dev/null
+++ b/Experiments/05EEPROM/Makefile
@@ -0,0 +1,22 @@
+CC = avr-gcc
+CFLAGS = -g -Os -mmcu=atmega8
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+BUILD_DIR = build
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: main.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf | $(BUILD_DIR)
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR):
+ mkdir -p $@
\ No newline at end of file
diff --git a/Experiments/05EEPROM/main.c b/Experiments/05EEPROM/main.c
new file mode 100644
index 0000000..9a98193
--- /dev/null
+++ b/Experiments/05EEPROM/main.c
@@ -0,0 +1,93 @@
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+
+#include
+#include
+#include
+
+#define NUM_GATES 8
+#define GATE_PORT PORTD
+#define GATE_PORT_0 PORTB
+#define GATE_DDR DDRD
+#define GATE_DDR_0 DDRB
+#define GATE_PIN_0 PB0
+#define GATE_PIN_1 PD1
+
+#define LED_PORT PORTC
+#define LED_DDR DDRC
+#define LED_PIN PC0
+
+#define BUTTON_PORT PORTC
+#define BUTTON_DDR DDRC
+#define BUTTON_PIN PC1
+#define BUTTON_PIN_REG PINC
+
+#define SET_BIT(PORT, PIN) ((PORT) |= (1 << (PIN)))
+#define CLEAR_BIT(PORT, PIN) ((PORT) &= ~(1 << (PIN)))
+
+#define LED_ON() CLEAR_BIT(LED_PORT, LED_PIN)
+#define LED_OFF() SET_BIT(LED_PORT, LED_PIN)
+
+#define EEPROM_BUTTON_FIX (uint8_t *)0x07
+#define EEPROM_GATE_ADDR (uint8_t *)0x100
+
+// Function prototypes
+void setup(void);
+void setGate(uint8_t gateIndex, uint8_t state);
+
+void setup() {
+ // Set gate pins as outputs
+ GATE_DDR_0 |= (1 << GATE_PIN_0); // Set PB0 as output
+ GATE_DDR |= 0xFE; // Set PD1 to PD7 as outputs (0xFE = 0b11111110)
+
+ DDRC = (1 << LED_PIN) | (1 << BUTTON_PIN);
+
+ // Fix for Hardware Version 1.5, from original code
+ while (!eeprom_is_ready());
+ uint8_t buttonfix_flag = eeprom_read_byte(EEPROM_BUTTON_FIX);
+
+ if (buttonfix_flag == 0xAA) {
+ // Set the button pin as input
+ DDRC &= ~((1 << BUTTON_PIN));
+ }
+
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ setGate(i, 1);
+ _delay_ms(50);
+ setGate(i, 0);
+ }
+}
+
+void setGate(uint8_t gateIndex, uint8_t state) {
+ uint8_t pin = (gateIndex == 0) ? GATE_PIN_0 : GATE_PIN_1 + gateIndex - 1;
+ volatile uint8_t *port = (gateIndex == 0) ? &GATE_PORT_0 : &GATE_PORT;
+ if (state) {
+ SET_BIT(*port, pin);
+ } else {
+ CLEAR_BIT(*port, pin);
+ }
+}
+
+int main(void) {
+ setup();
+
+ uint8_t ctrlIndex = eeprom_read_byte(EEPROM_GATE_ADDR) % 8;
+ setGate(ctrlIndex, 1);
+
+ while (1) {
+ uint8_t button_current_state = BUTTON_PIN_REG & (1 << BUTTON_PIN);
+
+ if (button_current_state) {
+ setGate(ctrlIndex, 0);
+ ctrlIndex = (ctrlIndex + 1) % 8;
+
+ while (!eeprom_is_ready());
+
+ eeprom_write_byte(EEPROM_GATE_ADDR, ctrlIndex);
+
+ setGate(ctrlIndex, 1);
+ }
+
+ _delay_ms(50);
+ }
+}
diff --git a/Experiments/06DebounceBlink/Makefile b/Experiments/06DebounceBlink/Makefile
new file mode 100644
index 0000000..c05df6a
--- /dev/null
+++ b/Experiments/06DebounceBlink/Makefile
@@ -0,0 +1,22 @@
+CC = avr-gcc
+CFLAGS = -g -Os -mmcu=atmega8
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+BUILD_DIR = build
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: main.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf | $(BUILD_DIR)
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR):
+ mkdir -p $@
\ No newline at end of file
diff --git a/Experiments/06DebounceBlink/main.c b/Experiments/06DebounceBlink/main.c
new file mode 100644
index 0000000..9727477
--- /dev/null
+++ b/Experiments/06DebounceBlink/main.c
@@ -0,0 +1,181 @@
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+
+#include
+#include
+#include
+#include
+
+#define NUM_GATES 8
+#define DEBOUNCE_TIME 50
+#define LONG_PRESS_TIME 2000
+#define TIMER_TICK 10
+
+#define SET_BIT(PORT, PIN) ((PORT) |= (1 << (PIN)))
+#define CLEAR_BIT(PORT, PIN) ((PORT) &= ~(1 << (PIN)))
+
+// Gate control
+#define GATE_PORT PORTD
+#define GATE_PORT_0 PORTB
+#define GATE_DDR DDRD
+#define GATE_DDR_0 DDRB
+#define GATE_PIN_0 PB0
+#define GATE_PIN_1 PD1
+
+// LED control
+#define LED_PORT PORTC
+#define LED_DDR DDRC
+#define LED_PIN PC0
+#define LED_SET_ON() CLEAR_BIT(LED_PORT, LED_PIN)
+#define LED_SET_OFF() SET_BIT(LED_PORT, LED_PIN)
+
+// Button control
+#define BUTTON_PORT PORTC
+#define BUTTON_DDR DDRC
+#define BUTTON_PIN PC1
+#define BUTTON_PIN_REG PINC
+
+// EEPROM configuration
+#define EEPROM_BUTTON_FIX (uint8_t*)0x07
+
+// Button states
+#define BUTTON_IDLE 0
+#define BUTTON_DEBOUNCING 1
+#define BUTTON_PRESSED 2
+#define BUTTON_HELD 3
+#define BUTTON_DOWN 4
+#define BUTTON_RELEASED 5
+
+// LED states
+#define LED_OFF 1
+#define LED_ON 2
+#define LED_BLINK1 3
+#define LED_BLINK2 4
+#define LED_BLINK3 5
+#define LED_BLINK4 6
+
+volatile uint8_t buttonState = BUTTON_IDLE;
+volatile uint8_t ledState = LED_OFF;
+
+void setup(void);
+void processButton(void);
+void updateLED(void);
+void setGate(uint8_t gateIndex, uint8_t state);
+
+int main(void) {
+ setup();
+
+ while (1) {
+ processButton();
+
+ if (buttonState == BUTTON_HELD) {
+ ledState = ledState == LED_OFF ? LED_BLINK1 : LED_OFF;
+ } else if (buttonState == BUTTON_RELEASED && ledState > LED_ON) {
+ ledState = ((ledState - LED_BLINK1 + 1) % 4) + LED_BLINK1;
+ }
+
+ updateLED();
+
+ setGate(buttonState, 1);
+ _delay_ms(TIMER_TICK);
+ setGate(buttonState, 0);
+ }
+}
+
+void setup() {
+ GATE_DDR_0 |= (1 << GATE_PIN_0); // Set PB0 as output
+ GATE_DDR |= 0xFE; // Set PD1 to PD7 as outputs (0xFE = 0b11111110)
+
+ DDRC = (1 << LED_PIN) | (1 << BUTTON_PIN);
+ LED_SET_OFF();
+
+ // Fix for Hardware Version 1.5, from original code
+ while (!eeprom_is_ready());
+ if (eeprom_read_byte(EEPROM_BUTTON_FIX) == 0xAA) {
+ BUTTON_DDR &= ~(1 << BUTTON_PIN);
+ }
+
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ setGate(i, 1);
+ _delay_ms(50);
+ setGate(i, 0);
+ }
+}
+
+void processButton() {
+ static uint16_t buttonTimer = 0;
+ uint8_t currentReading = BUTTON_PIN_REG & (1 << BUTTON_PIN);
+
+ switch (buttonState) {
+ case BUTTON_IDLE:
+ if (currentReading) {
+ buttonState = BUTTON_DEBOUNCING;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_DEBOUNCING:
+ if (++buttonTimer >= (DEBOUNCE_TIME / TIMER_TICK)) {
+ buttonState = BUTTON_PRESSED;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_PRESSED:
+ case BUTTON_DOWN:
+ if (!currentReading) {
+ buttonState = buttonState == BUTTON_PRESSED ? BUTTON_RELEASED : BUTTON_IDLE;
+ } else if (buttonTimer >= (LONG_PRESS_TIME / TIMER_TICK)) {
+ buttonState = BUTTON_HELD;
+ } else {
+ buttonTimer++;
+ }
+ break;
+
+ case BUTTON_HELD:
+ if (!currentReading) {
+ buttonState = BUTTON_IDLE;
+ } else {
+ buttonState = BUTTON_DOWN;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_RELEASED:
+ buttonState = BUTTON_IDLE;
+ break;
+ }
+}
+
+void updateLED(void) {
+ static uint16_t ledTimer = 0;
+ static uint8_t ledToggle = 0;
+
+ switch (ledState) {
+ case LED_ON:
+ LED_SET_ON();
+ break;
+ case LED_OFF:
+ LED_SET_OFF();
+ break;
+ default:
+ if (++ledTimer >= (1000 >> (ledState - LED_BLINK1)) / TIMER_TICK) {
+ ledToggle = !ledToggle;
+ if (ledToggle)
+ LED_SET_ON();
+ else
+ LED_SET_OFF();
+ ledTimer = 0;
+ }
+ break;
+ }
+}
+
+inline void setGate(uint8_t gateIndex, uint8_t state) {
+ uint8_t pin = (gateIndex == 0) ? GATE_PIN_0 : GATE_PIN_1 + gateIndex - 1;
+ volatile uint8_t* port = (gateIndex == 0) ? &GATE_PORT_0 : &GATE_PORT;
+ if (state)
+ SET_BIT(*port, pin);
+ else
+ CLEAR_BIT(*port, pin);
+}
diff --git a/Experiments/07MIDMap/Makefile b/Experiments/07MIDMap/Makefile
new file mode 100644
index 0000000..c05df6a
--- /dev/null
+++ b/Experiments/07MIDMap/Makefile
@@ -0,0 +1,22 @@
+CC = avr-gcc
+CFLAGS = -g -Os -mmcu=atmega8
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+BUILD_DIR = build
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: main.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf | $(BUILD_DIR)
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR):
+ mkdir -p $@
\ No newline at end of file
diff --git a/Experiments/07MIDMap/main.c b/Experiments/07MIDMap/main.c
new file mode 100644
index 0000000..29bfc80
--- /dev/null
+++ b/Experiments/07MIDMap/main.c
@@ -0,0 +1,349 @@
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+#define MY_UBRR F_CPU / 16 / BAUD - 1
+
+#include
+#include
+#include
+#include
+
+#define NUM_GATES 8
+#define DEBOUNCE_TIME 50
+#define LONG_PRESS_TIME 2000
+#define TIMER_TICK 10
+#define DEBOUNCE_THRESHOLD (DEBOUNCE_TIME / TIMER_TICK)
+#define LONG_PRESS_THRESHOLD (LONG_PRESS_TIME / TIMER_TICK)
+
+#define SET_BIT(PORT, PIN) ((PORT) |= (1 << (PIN)))
+#define CLEAR_BIT(PORT, PIN) ((PORT) &= ~(1 << (PIN)))
+
+// Gate control
+#define GATE_PORT PORTD
+#define GATE_PORT_0 PORTB
+#define GATE_DDR DDRD
+#define GATE_DDR_0 DDRB
+#define GATE_PIN_0 PB0
+#define GATE_PIN_1 PD1
+
+// LED control
+#define LED_PORT PORTC
+#define LED_DDR DDRC
+#define LED_PIN PC0
+#define LED_SET_ON() CLEAR_BIT(LED_PORT, LED_PIN)
+#define LED_SET_OFF() SET_BIT(LED_PORT, LED_PIN)
+
+// Button control
+#define BUTTON_PORT PORTC
+#define BUTTON_DDR DDRC
+#define BUTTON_PIN PC1
+#define BUTTON_PIN_REG PINC
+
+// TWI (I2C) parameters, these are predefined in the ATmega8 - here for reference
+#define SDA_PIN PC4 // SDA pin
+#define SCL_PIN PC5 // SCL pin
+
+// LDAC control
+#define LDAC_PIN PC2
+#define CLR_PIN PC3
+#define LDAC_PORT PORTC
+#define CLR_PORT PORTC
+#define CONTROL_DDR DDRC
+
+// MAX5825 DAC definitions
+#define MAX5825_ADDR 0x20 // 0010 000W, where W is the write bit
+#define MAX5825_REG_REF 0x20
+#define MAX5825_REG_CODEn_LOADall 0xA0
+#define MAX5825_REG_CODEn_LOADn 0xB0 // 1011 XXXX, where X encodes the DAC channel
+
+// EEPROM configuration
+#define EEPROM_BUTTON_FIX (uint8_t *)0x07
+
+// Button states
+#define BUTTON_IDLE 0
+#define BUTTON_DEBOUNCING 1
+#define BUTTON_PRESSED 2
+#define BUTTON_HELD 3
+#define BUTTON_DOWN 4
+#define BUTTON_RELEASED 5
+
+// LED states
+#define LED_OFF 1
+#define LED_ON 2
+#define LED_BLINK1 3
+#define LED_BLINK2 4
+#define LED_BLINK3 5
+#define LED_BLINK4 6
+
+volatile uint8_t buttonState = BUTTON_IDLE;
+volatile uint8_t ledState = LED_OFF;
+
+uint16_t pitch_lookup[61] = {
+ 0x0000, 0x0440, 0x0880, 0x0CD0, 0x1110, 0x1550, 0x19A0, 0x1DE0, 0x2220, 0x2660, 0x2AA0, 0x2EF0, // C-2
+ 0x3330, 0x3770, 0x3BC0, 0x4000, 0x4440, 0x4880, 0x4CC0, 0x5110, 0x5550, 0x5990, 0x5DE0, 0x6220, // C-1
+ 0x6660, 0x6AA0, 0x6EE0, 0x7330, 0x7770, 0x7BB0, 0x8000, 0x8440, 0x8880, 0x8CC0, 0x9100, 0x9550, // C0
+ 0x9990, 0x9DD0, 0xA220, 0xA660, 0xAAA0, 0xAEE0, 0xB320, 0xB770, 0xBBB0, 0xBFF0, 0xC440, 0xC880, // C1
+ 0xCCC0, 0xD100, 0xD550, 0xD990, 0xDDD0, 0xE210, 0xE660, 0xEAA0, 0xEEE0, 0xF320, 0xF760, 0xFBB0, // C2
+ 0xFFF0}; // C3
+
+typedef struct {
+ uint8_t status;
+ uint8_t data1;
+ uint8_t data2;
+ volatile uint8_t ready;
+} MIDI_Message;
+
+volatile MIDI_Message midiMsg = {0, 0, 0, 0};
+
+#define MAP_DRUM_VELO 0
+#define MAP_DRUM_CC 1
+#define MAP_KEYS_PITCH 2
+
+uint8_t midi_map[40] = {
+ MAP_DRUM_VELO, 0x80, 0, 0, 0, MAP_DRUM_VELO, 0x80, 1, 0, 0, MAP_DRUM_VELO, 0x80, 2, 0, 0,
+ MAP_DRUM_VELO, 0x80, 3, 0, 0, MAP_DRUM_VELO, 0x80, 4, 0, 0, MAP_DRUM_VELO, 0x80, 5, 0, 0,
+ MAP_DRUM_CC, 0x80, 6, 0xB0, 69, MAP_KEYS_PITCH, 0x81, 0, 0, 0,
+};
+
+void setup(void);
+void USART_Init(unsigned int ubrr);
+void processButton(void);
+void updateLED(void);
+void setGate(uint8_t gateIndex, uint8_t state);
+void twi_init(void);
+void twi_start(void);
+void twi_stop(void);
+void twi_write(uint8_t data);
+void max5825_write(uint8_t channel, uint16_t value);
+void processMIDI(void);
+
+int main(void) {
+ setup();
+
+ while (1) {
+ processButton();
+ updateLED();
+ _delay_ms(TIMER_TICK);
+ }
+}
+
+void setup() {
+ GATE_DDR_0 |= (1 << GATE_PIN_0); // Set PB0 as output
+ GATE_DDR |= 0xFE; // Set PD1 to PD7 as outputs (0xFE = 0b11111110)
+
+ DDRC = (1 << LED_PIN) | (1 << BUTTON_PIN);
+ LED_SET_OFF();
+
+ // Fix for Hardware Version 1.5, from original code
+ while (!eeprom_is_ready());
+ if (eeprom_read_byte(EEPROM_BUTTON_FIX) == 0xAA) {
+ BUTTON_DDR &= ~(1 << BUTTON_PIN);
+ }
+
+ CONTROL_DDR |= (1 << LDAC_PIN) | (1 << CLR_PIN); // Set LDAC and CLR as outputs
+ // Set LDAC high to hold the DAC output (active-low signal, high means no update)
+ LDAC_PORT |= (1 << LDAC_PIN);
+ // Set CLR high to avoid clearing the DAC (active-low signal, high means no clear)
+ CLR_PORT |= (1 << CLR_PIN);
+
+ // Initialize TWI (I2C)
+ twi_init();
+
+ // Initialize MAX5825 DAC
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write((MAX5825_REG_REF | 0b101)); // DAC channel
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write(MAX5825_REG_CODEn_LOADall); // DAC channel
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+
+ USART_Init(MY_UBRR);
+ sei();
+
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ setGate(i, 1);
+ _delay_ms(50);
+ setGate(i, 0);
+ }
+}
+
+void USART_Init(unsigned int ubrr) {
+ UBRRH = (unsigned char)(ubrr >> 8);
+ UBRRL = (unsigned char)ubrr;
+ UCSRB = (1 << RXEN) | (1 << RXCIE);
+ UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0);
+}
+
+ISR(USART_RXC_vect) {
+ static uint8_t midiState = 0;
+ uint8_t byte = UDR;
+
+ switch (midiState) {
+ case 0:
+ if (byte >= 0x80 && byte < 0xF0) {
+ midiMsg.status = byte;
+ midiState = 1;
+ }
+ break;
+ case 1:
+ midiMsg.data1 = byte;
+ midiState = 2;
+ break;
+ case 2:
+ midiMsg.data2 = byte;
+ midiMsg.ready = 1; // Message is ready
+ midiState = 0;
+
+ processMIDI();
+ break;
+ }
+}
+
+void twi_init(void) {
+ TWSR = 0x00; // Set prescaler to zero
+ TWBR = 12; // Baud rate set to 400kHz assuming F_CPU is 16MHz and no prescaler
+ TWCR = (1 << TWEN); // Enable TWI
+}
+
+void twi_start(void) {
+ TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
+ // Wait for TWINT Flag set. This indicates that the START condition has been transmitted
+ while (!(TWCR & (1 << TWINT)));
+}
+
+void twi_stop(void) { TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); }
+
+void twi_write(uint8_t data) {
+ TWDR = data;
+ TWCR = (1 << TWINT) | (1 << TWEN);
+ // Wait for TWINT Flag set. This indicates that the data has been transmitted, and ACK/NACK has been received
+ while (!(TWCR & (1 << TWINT)));
+}
+
+void max5825_write(uint8_t channel, uint16_t value) {
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write(MAX5825_REG_CODEn_LOADn | (channel & 0x0F)); // DAC channel
+ // Send the 12-bit data
+ // The upper 8 bits of the value (MSB)
+ twi_write((uint8_t)((value >> 8) & 0xFF));
+ // The lower 4 bits of the value (LSB) shifted into the upper 4 bits of the byte, as the MAX5825 expects it
+ twi_write((uint8_t)(value & 0xF0));
+ twi_stop();
+}
+
+void processButton() {
+ static uint16_t buttonTimer = 0;
+ uint8_t currentReading = BUTTON_PIN_REG & (1 << BUTTON_PIN);
+
+ switch (buttonState) {
+ case BUTTON_IDLE:
+ if (currentReading) {
+ buttonState = BUTTON_DEBOUNCING;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_DEBOUNCING:
+ if (++buttonTimer >= DEBOUNCE_THRESHOLD) {
+ buttonState = BUTTON_PRESSED;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_PRESSED:
+ case BUTTON_DOWN:
+ if (!currentReading) {
+ buttonState = (buttonState == BUTTON_PRESSED) ? BUTTON_RELEASED : BUTTON_IDLE;
+ } else if (buttonTimer >= LONG_PRESS_THRESHOLD) {
+ buttonState = BUTTON_HELD;
+ } else {
+ buttonTimer++;
+ }
+ break;
+
+ case BUTTON_HELD:
+ if (!currentReading) {
+ buttonState = BUTTON_IDLE;
+ } else {
+ buttonState = BUTTON_DOWN;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_RELEASED:
+ buttonState = BUTTON_IDLE;
+ break;
+ }
+}
+
+void updateLED(void) {
+ static uint16_t ledTimer = 0;
+ static uint8_t ledToggle = 0;
+
+ switch (ledState) {
+ case LED_ON:
+ LED_SET_ON();
+ break;
+ case LED_OFF:
+ LED_SET_OFF();
+ break;
+ default:
+ if (++ledTimer >= (1000 >> (ledState - LED_BLINK1)) / TIMER_TICK) {
+ ledToggle = !ledToggle;
+ if (ledToggle)
+ LED_SET_ON();
+ else
+ LED_SET_OFF();
+ ledTimer = 0;
+ }
+ break;
+ }
+}
+
+inline void setGate(uint8_t gateIndex, uint8_t state) {
+ uint8_t pin = (gateIndex == 0) ? GATE_PIN_0 : GATE_PIN_1 + gateIndex - 1;
+ volatile uint8_t *port = (gateIndex == 0) ? &GATE_PORT_0 : &GATE_PORT;
+ if (state)
+ SET_BIT(*port, pin);
+ else
+ CLEAR_BIT(*port, pin);
+}
+
+void processMIDI() {
+ uint8_t gateIndex = 0;
+ uint8_t *mapPtr = midi_map;
+
+ while (gateIndex < NUM_GATES) {
+ uint8_t mapType = *mapPtr++;
+ uint8_t gateCommand = *mapPtr++;
+ uint8_t gateValue = *mapPtr++;
+ uint8_t cvCommand = *mapPtr++;
+ uint8_t cvValue = *mapPtr++;
+
+ // Make NoteOffs into NoteOns and compare
+ if ((midiMsg.status & 0xEF) == gateCommand) {
+ if (midiMsg.data1 == gateValue || mapType == MAP_KEYS_PITCH) {
+ setGate(gateIndex, (midiMsg.status & 0xF0) == 0x90);
+
+ if (mapType == MAP_DRUM_VELO) {
+ max5825_write(gateIndex, (midiMsg.status & 0xF0) == 0x90 ? midiMsg.data2 << 9 : 0);
+ } else if (mapType == MAP_KEYS_PITCH) {
+ max5825_write(gateIndex, pitch_lookup[midiMsg.data1 < 61 ? midiMsg.data1 : 60]);
+ }
+ }
+ } else if (midiMsg.status == cvCommand && midiMsg.data1 == cvValue) {
+ max5825_write(gateIndex, midiMsg.data2 << 9);
+ }
+
+ gateIndex++;
+ }
+
+ midiMsg.ready = 0;
+}
diff --git a/Experiments/08RandStepSeq/Makefile b/Experiments/08RandStepSeq/Makefile
new file mode 100644
index 0000000..c05df6a
--- /dev/null
+++ b/Experiments/08RandStepSeq/Makefile
@@ -0,0 +1,22 @@
+CC = avr-gcc
+CFLAGS = -g -Os -mmcu=atmega8
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+BUILD_DIR = build
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: main.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf | $(BUILD_DIR)
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR):
+ mkdir -p $@
\ No newline at end of file
diff --git a/Experiments/08RandStepSeq/main.c b/Experiments/08RandStepSeq/main.c
new file mode 100644
index 0000000..f185973
--- /dev/null
+++ b/Experiments/08RandStepSeq/main.c
@@ -0,0 +1,374 @@
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+#define MY_UBRR F_CPU / 16 / BAUD - 1
+
+#include
+#include
+#include
+#include
+
+#define NUM_GATES 8
+#define DEBOUNCE_TIME 50
+#define LONG_PRESS_TIME 2000
+#define TIMER_TICK 10
+#define DEBOUNCE_THRESHOLD (DEBOUNCE_TIME / TIMER_TICK)
+#define LONG_PRESS_THRESHOLD (LONG_PRESS_TIME / TIMER_TICK)
+
+#define SET_BIT(PORT, PIN) ((PORT) |= (1 << (PIN)))
+#define CLEAR_BIT(PORT, PIN) ((PORT) &= ~(1 << (PIN)))
+
+// Gate control
+#define GATE_PORT PORTD
+#define GATE_PORT_0 PORTB
+#define GATE_DDR DDRD
+#define GATE_DDR_0 DDRB
+#define GATE_PIN_0 PB0
+#define GATE_PIN_1 PD1
+
+// LED control
+#define LED_PORT PORTC
+#define LED_DDR DDRC
+#define LED_PIN PC0
+#define LED_SET_ON() CLEAR_BIT(LED_PORT, LED_PIN)
+#define LED_SET_OFF() SET_BIT(LED_PORT, LED_PIN)
+
+// Button control
+#define BUTTON_PORT PORTC
+#define BUTTON_DDR DDRC
+#define BUTTON_PIN PC1
+#define BUTTON_PIN_REG PINC
+
+// TWI (I2C) parameters, these are predefined in the ATmega8 - here for reference
+#define SDA_PIN PC4 // SDA pin
+#define SCL_PIN PC5 // SCL pin
+
+// LDAC control
+#define LDAC_PIN PC2
+#define CLR_PIN PC3
+#define LDAC_PORT PORTC
+#define CLR_PORT PORTC
+#define CONTROL_DDR DDRC
+
+// MAX5825 DAC definitions
+#define MAX5825_ADDR 0x20 // 0010 000W, where W is the write bit
+#define MAX5825_REG_REF 0x20
+#define MAX5825_REG_CODEn_LOADall 0xA0
+#define MAX5825_REG_CODEn_LOADn 0xB0 // 1011 XXXX, where X encodes the DAC channel
+
+// EEPROM configuration
+#define EEPROM_BUTTON_FIX (uint8_t *)0x07
+
+// Button states
+#define BUTTON_IDLE 0
+#define BUTTON_DEBOUNCING 1
+#define BUTTON_PRESSED 2
+#define BUTTON_HELD 3
+#define BUTTON_DOWN 4
+#define BUTTON_RELEASED 5
+
+// LED states
+#define LED_OFF 1
+#define LED_ON 2
+#define LED_BLINK1 3
+#define LED_BLINK2 4
+#define LED_BLINK3 5
+#define LED_BLINK4 6
+
+volatile uint8_t buttonState = BUTTON_IDLE;
+volatile uint8_t ledState = LED_OFF;
+
+typedef struct {
+ uint8_t status;
+ uint8_t data1;
+ uint8_t data2;
+ volatile uint8_t ready;
+} MIDI_Message;
+
+volatile MIDI_Message midiMsg = {0, 0, 0, 0};
+
+uint8_t midi_map[48] = {
+ 0x80, 24, 0x90, 32, 0x90, 33, // Gate C0, Step G#0, Reset A0
+ 0x80, 25, 0x90, 34, 0x90, 35, // Gate C#0, Step A#0, Reset B0
+ 0x80, 26, 0x90, 36, 0x90, 37, // Gate D0, Step C1, Reset C#1
+ 0x80, 27, 0x90, 38, 0x90, 39, // Gate D#0, Step D1, Reset D#1
+ 0x80, 28, 0x90, 40, 0x90, 41, // Gate E0, Step E1, Reset F1
+ 0x80, 29, 0x90, 42, 0x90, 43, // Gate F0, Step F#1, Reset G1
+ 0x80, 30, 0x90, 44, 0x90, 45, // Gate F#0, Step G#1, Reset A1
+ 0x80, 31, 0x90, 46, 0x90, 47, // Gate G0, Step A#1, Reset B1
+};
+
+uint16_t lfsr_seeds[NUM_GATES];
+uint16_t lfsr_array[NUM_GATES];
+
+void setup(void);
+void USART_Init(unsigned int ubrr);
+void processButton(void);
+void updateLED(void);
+void setGate(uint8_t gateIndex, uint8_t state);
+void twi_init(void);
+void twi_start(void);
+void twi_stop(void);
+void twi_write(uint8_t data);
+void max5825_write(uint8_t channel, uint16_t value);
+void processMIDI(void);
+uint16_t updateRand(uint16_t *lfsr);
+uint16_t updateRandAlt(uint16_t *lfsr);
+
+int main(void) {
+ setup();
+
+ while (1) {
+ processButton();
+ updateLED();
+
+ if (buttonState == BUTTON_RELEASED) {
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ lfsr_seeds[i] = updateRandAlt(&lfsr_array[i]);
+ }
+ }
+
+ _delay_ms(TIMER_TICK);
+ }
+}
+
+void setup() {
+ GATE_DDR_0 |= (1 << GATE_PIN_0); // Set PB0 as output
+ GATE_DDR |= 0xFE; // Set PD1 to PD7 as outputs (0xFE = 0b11111110)
+
+ DDRC = (1 << LED_PIN) | (1 << BUTTON_PIN);
+ LED_SET_OFF();
+
+ // Fix for Hardware Version 1.5, from original code
+ while (!eeprom_is_ready());
+ if (eeprom_read_byte(EEPROM_BUTTON_FIX) == 0xAA) {
+ BUTTON_DDR &= ~(1 << BUTTON_PIN);
+ }
+
+ CONTROL_DDR |= (1 << LDAC_PIN) | (1 << CLR_PIN); // Set LDAC and CLR as outputs
+ // Set LDAC high to hold the DAC output (active-low signal, high means no update)
+ LDAC_PORT |= (1 << LDAC_PIN);
+ // Set CLR high to avoid clearing the DAC (active-low signal, high means no clear)
+ CLR_PORT |= (1 << CLR_PIN);
+
+ // Initialize TWI (I2C)
+ twi_init();
+
+ // Initialize MAX5825 DAC
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write((MAX5825_REG_REF | 0b101)); // DAC channel
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write(MAX5825_REG_CODEn_LOADall); // DAC channel
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+
+ USART_Init(MY_UBRR);
+
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ lfsr_seeds[i] = (i + 1) << 4;
+ lfsr_array[i] = (i + 1) << 4;
+
+ setGate(i, 1);
+ _delay_ms(50);
+ setGate(i, 0);
+ }
+
+ sei();
+}
+
+void USART_Init(unsigned int ubrr) {
+ UBRRH = (unsigned char)(ubrr >> 8);
+ UBRRL = (unsigned char)ubrr;
+ UCSRB = (1 << RXEN) | (1 << RXCIE);
+ UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0);
+}
+
+ISR(USART_RXC_vect) {
+ static uint8_t midiState = 0;
+ uint8_t byte = UDR;
+
+ switch (midiState) {
+ case 0:
+ if (byte >= 0x80 && byte < 0xF0) {
+ midiMsg.status = byte;
+ midiState = 1;
+ }
+ break;
+ case 1:
+ midiMsg.data1 = byte;
+ midiState = 2;
+ break;
+ case 2:
+ midiMsg.data2 = byte;
+ midiMsg.ready = 1; // Message is ready
+ midiState = 0;
+
+ break;
+ }
+
+ if (midiMsg.ready) {
+ processMIDI();
+ }
+}
+
+void twi_init(void) {
+ TWSR = 0x00; // Set prescaler to zero
+ TWBR = 12; // Baud rate set to 400kHz assuming F_CPU is 16MHz and no prescaler
+ TWCR = (1 << TWEN); // Enable TWI
+}
+
+void twi_start(void) {
+ TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
+ // Wait for TWINT Flag set. This indicates that the START condition has been transmitted
+ while (!(TWCR & (1 << TWINT)));
+}
+
+void twi_stop(void) { TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); }
+
+void twi_write(uint8_t data) {
+ TWDR = data;
+ TWCR = (1 << TWINT) | (1 << TWEN);
+ // Wait for TWINT Flag set. This indicates that the data has been transmitted, and ACK/NACK has been received
+ while (!(TWCR & (1 << TWINT)));
+}
+
+void max5825_write(uint8_t channel, uint16_t value) {
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write(MAX5825_REG_CODEn_LOADn | (channel & 0x0F)); // DAC channel
+ // Send the 12-bit data
+ // The upper 8 bits of the value (MSB)
+ twi_write((uint8_t)((value >> 8) & 0xFF));
+ // The lower 4 bits of the value (LSB) shifted into the upper 4 bits of the byte, as the MAX5825 expects it
+ twi_write((uint8_t)(value & 0xF0));
+ twi_stop();
+}
+
+void processButton() {
+ static uint16_t buttonTimer = 0;
+ uint8_t currentReading = BUTTON_PIN_REG & (1 << BUTTON_PIN);
+
+ switch (buttonState) {
+ case BUTTON_IDLE:
+ if (currentReading) {
+ buttonState = BUTTON_DEBOUNCING;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_DEBOUNCING:
+ if (++buttonTimer >= DEBOUNCE_THRESHOLD) {
+ buttonState = BUTTON_PRESSED;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_PRESSED:
+ case BUTTON_DOWN:
+ if (!currentReading) {
+ buttonState = (buttonState == BUTTON_PRESSED) ? BUTTON_RELEASED : BUTTON_IDLE;
+ } else if (buttonTimer >= LONG_PRESS_THRESHOLD) {
+ buttonState = BUTTON_HELD;
+ } else {
+ buttonTimer++;
+ }
+ break;
+
+ case BUTTON_HELD:
+ if (!currentReading) {
+ buttonState = BUTTON_IDLE;
+ } else {
+ buttonState = BUTTON_DOWN;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_RELEASED:
+ buttonState = BUTTON_IDLE;
+ break;
+ }
+}
+
+void updateLED(void) {
+ static uint16_t ledTimer = 0;
+ static uint8_t ledToggle = 0;
+
+ switch (ledState) {
+ case LED_ON:
+ LED_SET_ON();
+ break;
+ case LED_OFF:
+ LED_SET_OFF();
+ break;
+ default:
+ if (++ledTimer >= (1000 >> (ledState - LED_BLINK1)) / TIMER_TICK) {
+ ledToggle = !ledToggle;
+ if (ledToggle)
+ LED_SET_ON();
+ else
+ LED_SET_OFF();
+ ledTimer = 0;
+ }
+ break;
+ }
+}
+
+inline void setGate(uint8_t gateIndex, uint8_t state) {
+ uint8_t pin = (gateIndex == 0) ? GATE_PIN_0 : GATE_PIN_1 + gateIndex - 1;
+ volatile uint8_t *port = (gateIndex == 0) ? &GATE_PORT_0 : &GATE_PORT;
+ if (state)
+ SET_BIT(*port, pin);
+ else
+ CLEAR_BIT(*port, pin);
+}
+
+inline uint16_t updateRand(uint16_t *lfsr) {
+ uint16_t bit = ((*lfsr >> 0) ^ (*lfsr >> 2) ^ (*lfsr >> 3) ^ (*lfsr >> 5)) & 1;
+ *lfsr = (*lfsr >> 1) | (bit << 15);
+ return *lfsr;
+}
+
+inline uint16_t updateRandAlt(uint16_t *lfsr) {
+ uint16_t bit = ((*lfsr >> 1) ^ (*lfsr >> 3) ^ (*lfsr >> 4) ^ (*lfsr >> 8)) & 1;
+ *lfsr = (*lfsr >> 1) | (bit << 15);
+ return *lfsr;
+}
+
+inline void processMIDI() {
+ uint8_t gateIndex = 0;
+ uint8_t *mapPtr = midi_map;
+ uint8_t gateCommand, gateValue, cvCommand1, cvValue1, cvCommand2, cvValue2;
+ uint8_t commandFiltered = midiMsg.status & 0xEF;
+ uint8_t noteOnFlag = (midiMsg.status & 0xF0) == 0x90;
+ uint8_t data1 = midiMsg.data1;
+
+ while (gateIndex < NUM_GATES) {
+ gateCommand = *mapPtr++;
+ gateValue = *mapPtr++;
+ cvCommand1 = *mapPtr++;
+ cvValue1 = *mapPtr++;
+ cvCommand2 = *mapPtr++;
+ cvValue2 = *mapPtr++;
+
+ if (commandFiltered == gateCommand && data1 == gateValue) {
+ setGate(gateIndex, noteOnFlag);
+ }
+ if (midiMsg.status == cvCommand1 && data1 == cvValue1) {
+ uint16_t rand = updateRand(&lfsr_array[gateIndex]);
+ max5825_write(gateIndex, rand);
+ }
+ if (midiMsg.status == cvCommand2 && data1 == cvValue2) {
+ lfsr_array[gateIndex] = lfsr_seeds[gateIndex];
+ }
+
+ gateIndex++;
+ }
+
+ midiMsg.ready = 0;
+}
diff --git a/Experiments/09MIDILearn/Makefile b/Experiments/09MIDILearn/Makefile
new file mode 100644
index 0000000..c05df6a
--- /dev/null
+++ b/Experiments/09MIDILearn/Makefile
@@ -0,0 +1,22 @@
+CC = avr-gcc
+CFLAGS = -g -Os -mmcu=atmega8
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+BUILD_DIR = build
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: main.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf | $(BUILD_DIR)
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR):
+ mkdir -p $@
\ No newline at end of file
diff --git a/Experiments/09MIDILearn/main.c b/Experiments/09MIDILearn/main.c
new file mode 100644
index 0000000..00b1342
--- /dev/null
+++ b/Experiments/09MIDILearn/main.c
@@ -0,0 +1,477 @@
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+#define MY_UBRR F_CPU / 16 / BAUD - 1
+
+#include
+#include
+#include
+#include
+
+#define NUM_GATES 8
+#define DEBOUNCE_TIME 50
+#define LONG_PRESS_TIME 2000
+#define TIMER_TICK 10
+#define DEBOUNCE_THRESHOLD (DEBOUNCE_TIME / TIMER_TICK)
+#define LONG_PRESS_THRESHOLD (LONG_PRESS_TIME / TIMER_TICK)
+
+#define SET_BIT(PORT, PIN) ((PORT) |= (1 << (PIN)))
+#define CLEAR_BIT(PORT, PIN) ((PORT) &= ~(1 << (PIN)))
+
+// Gate control
+#define GATE_PORT PORTD
+#define GATE_PORT_0 PORTB
+#define GATE_DDR DDRD
+#define GATE_DDR_0 DDRB
+#define GATE_PIN_0 PB0
+#define GATE_PIN_1 PD1
+
+// LED control
+#define LED_PORT PORTC
+#define LED_DDR DDRC
+#define LED_PIN PC0
+#define LED_SET_ON() CLEAR_BIT(LED_PORT, LED_PIN)
+#define LED_SET_OFF() SET_BIT(LED_PORT, LED_PIN)
+
+// Button control
+#define BUTTON_PORT PORTC
+#define BUTTON_DDR DDRC
+#define BUTTON_PIN PC1
+#define BUTTON_PIN_REG PINC
+
+// TWI (I2C) parameters, these are predefined in the ATmega8 - here for reference
+#define SDA_PIN PC4 // SDA pin
+#define SCL_PIN PC5 // SCL pin
+
+// LDAC control
+#define LDAC_PIN PC2
+#define CLR_PIN PC3
+#define LDAC_PORT PORTC
+#define CLR_PORT PORTC
+#define CONTROL_DDR DDRC
+
+// MAX5825 DAC definitions
+#define MAX5825_ADDR 0x20 // 0010 000W, where W is the write bit
+#define MAX5825_REG_REF 0x20
+#define MAX5825_REG_CODEn_LOADall 0xA0
+#define MAX5825_REG_CODEn_LOADn 0xB0 // 1011 XXXX, where X encodes the DAC channel
+
+// EEPROM configuration
+#define EEPROM_BUTTON_FIX (uint8_t *)0x07
+#define EEPROM_MIDIMAP (uint8_t *)0x101
+
+// Button states
+#define BUTTON_IDLE 0
+#define BUTTON_DEBOUNCING 1
+#define BUTTON_PRESSED 2
+#define BUTTON_HELD 3
+#define BUTTON_DOWN 4
+#define BUTTON_RELEASED 5
+
+// LED states
+#define LED_OFF 1
+#define LED_ON 2
+#define LED_BLINK1 3
+#define LED_BLINK2 4
+#define LED_BLINK3 5
+#define LED_BLINK4 6
+
+// Learning/Awaiting CV States
+#define NUM_LEARNING_STATES 3
+#define LEARNING_VELOCITY 0
+#define LEARNING_PITCH 1
+#define LEARNING_CC 2
+#define AWAITING_CC 3
+
+volatile uint8_t buttonState = BUTTON_IDLE;
+volatile uint8_t ledState = LED_OFF;
+volatile uint8_t learningMode = 0;
+volatile uint8_t learningGateIndex = 0;
+volatile uint8_t learningCVState = LEARNING_VELOCITY;
+
+uint16_t pitch_lookup[61] = {
+ 0x0000, 0x0440, 0x0880, 0x0CD0, 0x1110, 0x1550, 0x19A0, 0x1DE0, 0x2220, 0x2660, 0x2AA0, 0x2EF0, // C-2
+ 0x3330, 0x3770, 0x3BC0, 0x4000, 0x4440, 0x4880, 0x4CC0, 0x5110, 0x5550, 0x5990, 0x5DE0, 0x6220, // C-1
+ 0x6660, 0x6AA0, 0x6EE0, 0x7330, 0x7770, 0x7BB0, 0x8000, 0x8440, 0x8880, 0x8CC0, 0x9100, 0x9550, // C0
+ 0x9990, 0x9DD0, 0xA220, 0xA660, 0xAAA0, 0xAEE0, 0xB320, 0xB770, 0xBBB0, 0xBFF0, 0xC440, 0xC880, // C1
+ 0xCCC0, 0xD100, 0xD550, 0xD990, 0xDDD0, 0xE210, 0xE660, 0xEAA0, 0xEEE0, 0xF320, 0xF760, 0xFBB0, // C2
+ 0xFFF0}; // C3
+
+typedef struct {
+ uint8_t status;
+ uint8_t data1;
+ uint8_t data2;
+ volatile uint8_t ready;
+} MIDI_Message;
+
+volatile MIDI_Message midiMsg = {0, 0, 0, 0};
+
+#define MIDIMAP_VELOCITY 0
+#define MIDIMAP_CC 1
+#define MIDIMAP_PITCH 2
+
+uint8_t midi_map[40] = {
+ MIDIMAP_VELOCITY, 0x80, 24, 0, 0, MIDIMAP_VELOCITY, 0x80, 25, 0, 0, MIDIMAP_VELOCITY, 0x80, 26, 0, 0,
+ MIDIMAP_VELOCITY, 0x80, 27, 0, 0, MIDIMAP_VELOCITY, 0x80, 28, 0, 0, MIDIMAP_VELOCITY, 0x80, 29, 0, 0,
+ MIDIMAP_VELOCITY, 0x80, 30, 0, 0, MIDIMAP_VELOCITY, 0x80, 31, 0, 0,
+};
+
+void setup(void);
+void loadMidiMap(void);
+void saveMidiMap(void);
+void USART_Init(unsigned int ubrr);
+void processButton(void);
+void updateLED(void);
+void setGate(uint8_t gateIndex, uint8_t state);
+void twi_init(void);
+void twi_start(void);
+void twi_stop(void);
+void twi_write(uint8_t data);
+void max5825_write(uint8_t channel, uint16_t value);
+void processMIDI(void);
+void midiLearn(void);
+
+int main(void) {
+ setup();
+
+ while (1) {
+ processButton();
+ updateLED();
+
+ if (buttonState == BUTTON_HELD) {
+ learningMode ^= 1;
+ ledState = learningMode ? LED_BLINK1 : LED_OFF;
+ learningGateIndex = 0;
+ }
+
+ if (learningMode) {
+ midiLearn();
+ }
+
+ _delay_ms(TIMER_TICK);
+ }
+}
+
+void setup() {
+ GATE_DDR_0 |= (1 << GATE_PIN_0); // Set PB0 as output
+ GATE_DDR |= 0xFE; // Set PD1 to PD7 as outputs (0xFE = 0b11111110)
+
+ DDRC = (1 << LED_PIN) | (1 << BUTTON_PIN);
+ LED_SET_OFF();
+
+ // Fix for Hardware Version 1.5, from original code
+ while (!eeprom_is_ready());
+ if (eeprom_read_byte(EEPROM_BUTTON_FIX) == 0xAA) {
+ BUTTON_DDR &= ~(1 << BUTTON_PIN);
+ }
+
+ CONTROL_DDR |= (1 << LDAC_PIN) | (1 << CLR_PIN); // Set LDAC and CLR as outputs
+ // Set LDAC high to hold the DAC output (active-low signal, high means no update)
+ LDAC_PORT |= (1 << LDAC_PIN);
+ // Set CLR high to avoid clearing the DAC (active-low signal, high means no clear)
+ CLR_PORT |= (1 << CLR_PIN);
+
+ // Initialize TWI (I2C)
+ twi_init();
+
+ // Initialize MAX5825 DAC
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write((MAX5825_REG_REF | 0b101)); // DAC channel
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write(MAX5825_REG_CODEn_LOADall); // DAC channel
+ twi_write(0x00);
+ twi_write(0x00);
+ twi_stop();
+
+ USART_Init(MY_UBRR);
+
+ loadMidiMap();
+
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ setGate(i, 1);
+ _delay_ms(50);
+ setGate(i, 0);
+ }
+
+ sei();
+}
+
+void loadMidiMap() {
+ while (!eeprom_is_ready());
+ eeprom_read_block(&midi_map, EEPROM_MIDIMAP, 40);
+}
+
+void saveMidiMap() {
+ while (!eeprom_is_ready());
+ eeprom_write_block(&midi_map, EEPROM_MIDIMAP, 40);
+}
+
+void USART_Init(unsigned int ubrr) {
+ UBRRH = (unsigned char)(ubrr >> 8);
+ UBRRL = (unsigned char)ubrr;
+ UCSRB = (1 << RXEN) | (1 << RXCIE);
+ UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0);
+}
+
+ISR(USART_RXC_vect) {
+ static uint8_t midiState = 0;
+ uint8_t byte = UDR;
+
+ switch (midiState) {
+ case 0:
+ if (byte >= 0x80 && byte < 0xF0) {
+ midiMsg.status = byte;
+ midiState = 1;
+ }
+ break;
+ case 1:
+ midiMsg.data1 = byte;
+ midiState = 2;
+ break;
+ case 2:
+ midiMsg.data2 = byte;
+ midiMsg.ready = 1;
+ midiState = 0;
+ break;
+ }
+
+ if (midiMsg.ready && !learningMode) {
+ processMIDI();
+ }
+}
+
+void twi_init(void) {
+ TWSR = 0x00; // Set prescaler to zero
+ TWBR = 12; // Baud rate set to 400kHz assuming F_CPU is 16MHz and no prescaler
+ TWCR = (1 << TWEN); // Enable TWI
+}
+
+void twi_start(void) {
+ TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
+ // Wait for TWINT Flag set. This indicates that the START condition has been transmitted
+ while (!(TWCR & (1 << TWINT)));
+}
+
+void twi_stop(void) { TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); }
+
+void twi_write(uint8_t data) {
+ TWDR = data;
+ TWCR = (1 << TWINT) | (1 << TWEN);
+ // Wait for TWINT Flag set. This indicates that the data has been transmitted, and ACK/NACK has been received
+ while (!(TWCR & (1 << TWINT)));
+}
+
+void max5825_write(uint8_t channel, uint16_t value) {
+ twi_start();
+ twi_write(MAX5825_ADDR); // Address and write command
+ twi_write(MAX5825_REG_CODEn_LOADn | (channel & 0x0F)); // DAC channel
+ // Send the 12-bit data
+ // The upper 8 bits of the value (MSB)
+ twi_write((uint8_t)((value >> 8) & 0xFF));
+ // The lower 4 bits of the value (LSB) shifted into the upper 4 bits of the byte, as the MAX5825 expects it
+ twi_write((uint8_t)(value & 0xF0));
+ twi_stop();
+}
+
+void processButton() {
+ static uint16_t buttonTimer = 0;
+ uint8_t currentReading = BUTTON_PIN_REG & (1 << BUTTON_PIN);
+
+ switch (buttonState) {
+ case BUTTON_IDLE:
+ if (currentReading) {
+ buttonState = BUTTON_DEBOUNCING;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_DEBOUNCING:
+ if (++buttonTimer >= DEBOUNCE_THRESHOLD) {
+ buttonState = BUTTON_PRESSED;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_PRESSED:
+ case BUTTON_DOWN:
+ if (!currentReading) {
+ buttonState = (buttonState == BUTTON_PRESSED) ? BUTTON_RELEASED : BUTTON_IDLE;
+ } else if (buttonTimer >= LONG_PRESS_THRESHOLD) {
+ buttonState = BUTTON_HELD;
+ } else {
+ buttonTimer++;
+ }
+ break;
+
+ case BUTTON_HELD:
+ if (!currentReading) {
+ buttonState = BUTTON_IDLE;
+ } else {
+ buttonState = BUTTON_DOWN;
+ buttonTimer = 0;
+ }
+ break;
+
+ case BUTTON_RELEASED:
+ buttonState = BUTTON_IDLE;
+ break;
+ }
+}
+
+void updateLED(void) {
+ static uint16_t ledTimer = 0;
+ static uint8_t ledToggle = 0;
+
+ switch (ledState) {
+ case LED_ON:
+ LED_SET_ON();
+ break;
+ case LED_OFF:
+ LED_SET_OFF();
+ break;
+ default:
+ if (++ledTimer >= (1000 >> (ledState - LED_BLINK1)) / TIMER_TICK) {
+ ledToggle = !ledToggle;
+ if (ledToggle)
+ LED_SET_ON();
+ else
+ LED_SET_OFF();
+ ledTimer = 0;
+ }
+ break;
+ }
+}
+
+inline void setGate(uint8_t gateIndex, uint8_t state) {
+ uint8_t pin = (gateIndex == 0) ? GATE_PIN_0 : GATE_PIN_1 + gateIndex - 1;
+ volatile uint8_t *port = (gateIndex == 0) ? &GATE_PORT_0 : &GATE_PORT;
+ if (state)
+ SET_BIT(*port, pin);
+ else
+ CLEAR_BIT(*port, pin);
+}
+
+inline void processMIDI() {
+ uint8_t gateIndex = 0;
+ uint8_t *mapPtr = midi_map;
+ uint8_t mapType, gateCommand, gateValue, cvCommand1, cvValue1, cvCommand2, cvValue2;
+ uint8_t commandFiltered = midiMsg.status & 0xEF;
+ uint8_t noteOnFlag = (midiMsg.status & 0xF0) == 0x90;
+ uint8_t data1 = midiMsg.data1;
+
+ while (gateIndex < NUM_GATES) {
+ mapType = *mapPtr++;
+ gateCommand = *mapPtr++;
+ gateValue = *mapPtr++;
+ cvCommand1 = *mapPtr++;
+ cvValue1 = *mapPtr++;
+ cvCommand2 = *mapPtr++;
+ cvValue2 = *mapPtr++;
+
+ if (commandFiltered == gateCommand) {
+ if (data1 == gateValue || mapType == MIDIMAP_PITCH) {
+ setGate(gateIndex, noteOnFlag);
+
+ if (mapType == MIDIMAP_VELOCITY) {
+ max5825_write(gateIndex, noteOnFlag ? midiMsg.data2 << 9 : 0);
+ } else if (mapType == MIDIMAP_PITCH) {
+ max5825_write(gateIndex, pitch_lookup[data1 < 61 ? data1 : 60]);
+ }
+ }
+ } else if (midiMsg.status == cvCommand1 && data1 == cvValue1) {
+ max5825_write(gateIndex, midiMsg.data2 << 9);
+ }
+
+ gateIndex++;
+ }
+
+ midiMsg.ready = 0;
+}
+
+inline void midiLearn() {
+ if (midiMsg.ready) {
+ switch (learningCVState) {
+ case LEARNING_VELOCITY:
+ if ((midiMsg.status & 0xF0) == 0x90) {
+ midi_map[learningGateIndex * 7] = MIDIMAP_VELOCITY;
+ midi_map[learningGateIndex * 7 + 1] = midiMsg.status & 0xEF;
+ midi_map[learningGateIndex * 7 + 2] = midiMsg.data1;
+
+ setGate(learningGateIndex, 1);
+ learningGateIndex++;
+ learningCVState = LEARNING_VELOCITY;
+ ledState = learningCVState + LED_BLINK1;
+ }
+ break;
+ case LEARNING_PITCH:
+ if ((midiMsg.status & 0xF0) == 0x90) {
+ midi_map[learningGateIndex * 7] = MIDIMAP_PITCH;
+ midi_map[learningGateIndex * 7 + 1] = midiMsg.status & 0xEF;
+
+ setGate(learningGateIndex, 1);
+ learningGateIndex++;
+ learningCVState = LEARNING_VELOCITY;
+ ledState = learningCVState + LED_BLINK1;
+ }
+ break;
+ case LEARNING_CC:
+ if ((midiMsg.status & 0xF0) == 0x90) {
+ midi_map[learningGateIndex * 7] = MIDIMAP_CC;
+ midi_map[learningGateIndex * 7 + 1] = midiMsg.status & 0xEF;
+ learningCVState = AWAITING_CC;
+ }
+ break;
+ case AWAITING_CC:
+ if ((midiMsg.status & 0xF0) == 0xB0) {
+ midi_map[learningGateIndex * 7 + 3] = midiMsg.status;
+ midi_map[learningGateIndex * 7 + 4] = midiMsg.data1;
+
+ setGate(learningGateIndex, 1);
+ learningGateIndex++;
+ learningCVState = LEARNING_VELOCITY;
+ ledState = learningCVState + LED_BLINK1;
+ }
+ break;
+ default:
+ break;
+ }
+ midiMsg.ready = 0;
+ }
+
+ if (buttonState == BUTTON_RELEASED) {
+ switch (learningCVState) {
+ case LEARNING_VELOCITY:
+ case LEARNING_PITCH:
+ case LEARNING_CC:
+ learningCVState = (learningCVState + 1) % NUM_LEARNING_STATES;
+ break;
+ default:
+ learningCVState = LEARNING_VELOCITY;
+ break;
+ }
+ }
+
+ switch (learningCVState) {
+ case AWAITING_CC:
+ ledState = LED_BLINK3;
+ break;
+ default:
+ ledState = learningCVState + LED_BLINK1;
+ break;
+ }
+
+ if (learningGateIndex == NUM_GATES) {
+ for (uint8_t i = 0; i < NUM_GATES; i++) {
+ setGate(i, 0);
+ }
+ learningMode = 0;
+ learningCVState = LEARNING_VELOCITY;
+ ledState = LED_OFF;
+ saveMidiMap();
+ }
+}
\ No newline at end of file
diff --git a/Experiments/10RBISR/Makefile b/Experiments/10RBISR/Makefile
new file mode 100644
index 0000000..c05df6a
--- /dev/null
+++ b/Experiments/10RBISR/Makefile
@@ -0,0 +1,22 @@
+CC = avr-gcc
+CFLAGS = -g -Os -mmcu=atmega8
+OBJCOPY = avr-objcopy
+SIZE = avr-size
+BUILD_DIR = build
+
+all: $(BUILD_DIR)/main.hex size
+
+$(BUILD_DIR)/main.elf: main.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -o $@ $^
+
+$(BUILD_DIR)/main.hex: $(BUILD_DIR)/main.elf | $(BUILD_DIR)
+ $(OBJCOPY) -j .text -j .data -O ihex $^ $@
+
+size: $(BUILD_DIR)/main.elf
+ $(SIZE) -C --mcu=atmega8 $^
+
+clean:
+ -rm -rf $(BUILD_DIR)
+
+$(BUILD_DIR):
+ mkdir -p $@
\ No newline at end of file
diff --git a/Experiments/10RBISR/main.c b/Experiments/10RBISR/main.c
new file mode 100644
index 0000000..9f8d28d
--- /dev/null
+++ b/Experiments/10RBISR/main.c
@@ -0,0 +1,129 @@
+#include
+#define F_CPU 16000000UL
+#define BAUD 31250UL
+#define MY_UBRR F_CPU / 16 / BAUD - 1
+
+#include
+#include
+
+// Define the number of gates and their respective hardware configuration
+#define NUM_GATES 8
+#define GATE_PORT PORTD
+#define GATE_PORT_0 PORTB
+#define GATE_DDR DDRD
+#define GATE_DDR_0 DDRB
+#define GATE_PIN_0 PB0
+#define GATE_PIN_1 PD1
+
+#define RB_SIZE 64
+#define RB_MASK (RB_SIZE - 1)
+
+static volatile uint8_t rb[RB_SIZE];
+static volatile uint8_t rb_head = 0;
+static volatile uint8_t rb_tail = 0;
+static volatile uint8_t rb_overflow = 0;
+
+// MIDI message structure
+typedef struct {
+ uint8_t status;
+ uint8_t data1;
+ uint8_t data2;
+ volatile uint8_t ready;
+} MIDI_Message;
+
+volatile MIDI_Message midiMsg = {0, 0, 0, 0};
+
+// Macros for setting and clearing bits
+#define SET_BIT(PORT, PIN) ((PORT) |= (1 << (PIN)))
+#define CLEAR_BIT(PORT, PIN) ((PORT) &= ~(1 << (PIN)))
+
+// Function prototypes
+void setup(void);
+void USART_Init(unsigned int ubrr);
+void setGate(uint8_t gateIndex, uint8_t state);
+void processMIDI(void);
+
+void setup() {
+ GATE_DDR_0 |= (1 << GATE_PIN_0); // Set PB0 as output
+ GATE_DDR |= 0xFE; // Set PD1 to PD7 as outputs (0xFE = 0b11111110)
+
+ USART_Init(MY_UBRR);
+ sei();
+}
+
+void USART_Init(unsigned int ubrr) {
+ UBRRH = (unsigned char)(ubrr >> 8);
+ UBRRL = (unsigned char)ubrr;
+ UCSRB = (1 << RXEN) | (1 << RXCIE); // Enable receiver and receive complete interrupt
+ UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0); // 8-bit data
+}
+
+ISR(USART_RXC_vect) {
+ uint8_t byte = UDR;
+ uint8_t head = rb_head;
+ uint8_t next = (head + 1) & RB_MASK;
+
+ if (next == rb_tail) {
+ rb_overflow = 1;
+ } else {
+ rb[head] = byte;
+ rb_head = next;
+ }
+}
+
+static inline uint8_t rb_pop(uint8_t *out) {
+ uint8_t tail = rb_tail;
+ if (tail == rb_head) return 0;
+ *out = rb[tail];
+ rb_tail = (tail + 1) & RB_MASK;
+ return 1;
+}
+
+inline void setGate(uint8_t gateIndex, uint8_t state) {
+ uint8_t pin = (gateIndex == 0) ? GATE_PIN_0 : GATE_PIN_1 + gateIndex - 1;
+ volatile uint8_t *port = (gateIndex == 0) ? &GATE_PORT_0 : &GATE_PORT;
+ if (state)
+ SET_BIT(*port, pin);
+ else
+ CLEAR_BIT(*port, pin);
+}
+
+void parseMIDI(uint8_t byte) {
+ static uint8_t midiState = 0;
+ switch (midiState) {
+ case 0:
+ if (byte >= 0x80 && byte < 0xF0) {
+ midiMsg.status = byte;
+ midiState = 1;
+ }
+ break;
+ case 1:
+ midiMsg.data1 = byte;
+ midiState = 2;
+ break;
+ case 2:
+ midiMsg.data2 = byte;
+ midiMsg.ready = 1;
+ midiState = 0;
+ break;
+ }
+}
+
+void processMIDI() {
+ if ((midiMsg.status & 0xF0) == 0x90) { // Note on
+ setGate(midiMsg.data1 % NUM_GATES, 1);
+ } else if ((midiMsg.status & 0xF0) == 0x80) { // Note off
+ setGate(midiMsg.data1 % NUM_GATES, 0);
+ }
+ midiMsg.ready = 0; // Reset the ready flag
+}
+
+int main(void) {
+ setup();
+
+ for (;;) {
+ uint8_t byte;
+ if (rb_pop(&byte)) parseMIDI(byte);
+ if (midiMsg.ready == 1) processMIDI();
+ }
+}
diff --git a/README.md b/README.md
index 75f86a9..14a022d 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,10 @@ Velocity/CV Outputs:
7: random triggered each bar
8: 16th stepped ramp to go from 0-5V in a bar
+# Custom Firmware:
+
+* [thorinf](Community/thorinf)
+
# CC BY-NC-ND 4.0 KAY KNOFE OF LPZW.modules
diff --git a/Scripts/pitch_tracking.py b/Scripts/pitch_tracking.py
new file mode 100644
index 0000000..befdc0d
--- /dev/null
+++ b/Scripts/pitch_tracking.py
@@ -0,0 +1,58 @@
+import math
+
+
+def pitch2voltage(noteon):
+ return noteon / 12.0
+
+
+def voltage2pitch(voltage):
+ return 12.0 * voltage
+
+
+def dac_quantise(voltage, v_ref=5, bits=12):
+ levels = 2**bits
+ lsb = v_ref / (levels - 1)
+ dac = round(voltage / lsb)
+ voltage = dac * lsb
+ return dac, voltage
+
+
+def pitch_tracking(input_pitch, target_pitch):
+ cents = 100 * (input_pitch - target_pitch)
+ return cents
+
+
+def to_header(dac_values):
+ # Printing in C array format with 12 values per line
+ print(f"\nuint16_t pitch_lookup[{len(dac_values)}]" + " = {")
+ for i, value in enumerate(dac_values):
+ uint16_t = hex(value << 4)[2:].zfill(4)
+ if i % 12 == 0 and i != 0:
+ print()
+ print(f"0x{uint16_t.upper()}", end=", " if i < len(dac_values) - 1 else "")
+ print("};\n")
+
+
+def main():
+ tunings = []
+ dac_values = []
+ for pitch in range(128):
+ voltage = pitch2voltage(pitch)
+ dac, nearest_voltage = dac_quantise(voltage)
+ nearest_pitch = voltage2pitch(nearest_voltage)
+ cents = pitch_tracking(nearest_pitch, pitch)
+
+ if dac >= 2**12:
+ print(f"\nNoteon: {pitch:3d}, DAC value exceeds 12-bit range")
+ break
+
+ print(f"Noteon: {pitch:3d}, Nearest: {nearest_pitch:0.3f}, Cents: {cents:0.5f}")
+ dac_values.append(dac)
+ tunings.append(abs(cents))
+
+ print(f"\nAverage Cents: {sum(tunings) / len(tunings):0.5f}")
+ to_header(dac_values)
+
+
+if __name__ == "__main__":
+ main()