Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
4faf406
docker for build environment
thorinf Apr 28, 2024
d158d7e
gate cycle experiment, simple gate triggering
thorinf Apr 28, 2024
a9bec27
experiment in toggling DAC, 1 channel
thorinf Apr 28, 2024
0076c11
experiment using midi note on to trigger gates
thorinf May 12, 2024
c1db123
python script to work out pitch values for dac
thorinf May 17, 2024
f2a342d
fixes to dac toggle to get full 5v
thorinf May 17, 2024
add8873
cleaner gatecycle implementation
thorinf May 17, 2024
881f8a5
toggledac cleanup
thorinf May 17, 2024
7b0cc2a
noteon experiment cleanup
thorinf May 17, 2024
bbf91dc
noteon dir rename
thorinf May 17, 2024
7d952b7
implementation for sending pitch via midi notes
thorinf May 17, 2024
a381f9e
cleaned up the pitch tracking script
thorinf May 18, 2024
36746b5
simple eeprom example
thorinf Aug 19, 2024
6490a1b
debounce button and led blink example
thorinf Aug 20, 2024
68120c8
faster note on experiment
thorinf Aug 20, 2024
7f75490
experiment using a midi map to assign midi to gate and dac
thorinf Aug 26, 2024
93b15c0
experiment with lfsr rand dav values and gate resets
thorinf Aug 26, 2024
4554c3c
amending commit
thorinf Aug 26, 2024
e47415b
randseq has step and reset now
thorinf Aug 27, 2024
71099c5
experiment for learning midi mapping
thorinf Aug 27, 2024
c315a32
updated dockerfile to use numpy
thorinf Aug 27, 2024
26d9e36
tidy up
thorinf Aug 27, 2024
b5089f4
improved makefiles
thorinf Aug 28, 2024
6a642c2
libraries for custom firmware
thorinf Aug 28, 2024
ca51760
lfsr random number functions
thorinf Aug 29, 2024
a44f488
midimap fixed
thorinf Aug 29, 2024
b87d384
working firmware prototype
thorinf Aug 30, 2024
13af272
firmware and docs
thorinf Aug 31, 2024
b339911
typo fix and outputting header
thorinf Sep 3, 2024
4b0fe49
basic web interface for creating the midi mapping
thorinf Sep 5, 2024
7022630
added midimap from sysex to menu
thorinf Sep 5, 2024
de9c5ea
amended midimap from sysex to show error, and js now sends sysex map
thorinf Sep 5, 2024
8eefb51
style css making it look slightly nicer
thorinf Sep 5, 2024
6584945
updated docs and some style changes
thorinf Sep 5, 2024
eb4f3db
added figure for midi mapper tool, added it to readme
thorinf Sep 5, 2024
91e2d75
fixed readme figure
thorinf Sep 5, 2024
c46f28e
updating the readme with module reference pic
thorinf Sep 6, 2024
a402f5e
new approach for script.js and sysex map transfer
thorinf Sep 6, 2024
d7eab0e
firmware v1
thorinf Sep 7, 2024
7be9918
new midimapper fig
thorinf Sep 7, 2024
5b0d36d
Struct midi map
thorinf Sep 8, 2024
7bf1857
led menu indicator, and midiLearn blink count fixes
thorinf Sep 8, 2024
3f0be92
fixed menu exit
thorinf Sep 8, 2024
e9651a6
factorised button and led updates out
thorinf Sep 8, 2024
c746dbf
firmware update
thorinf Sep 8, 2024
ef1fb3a
firmware update
thorinf Sep 8, 2024
f0e5e0a
twi as inline, header only
thorinf Sep 13, 2024
1f3f1a6
max5825 inline
thorinf Sep 13, 2024
7d91a6d
precomputing ports and pins for gate_set so its faster
thorinf Sep 13, 2024
a06e411
hardware config header updated
thorinf Sep 13, 2024
987d7ec
udpated the syx
thorinf Sep 13, 2024
c1147bd
optimisation : inline functions, precomputed ports/pins
thorinf Sep 13, 2024
e9372d7
fixes
thorinf Sep 13, 2024
acc7c21
updates to documentation
thorinf Sep 13, 2024
f0676e6
quick ringbuffer experiment
thorinf Sep 28, 2025
e9f5dd9
midi parser
thorinf Sep 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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/
30 changes: 30 additions & 0 deletions Community/thorinf/Makefile
Original file line number Diff line number Diff line change
@@ -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)
84 changes: 84 additions & 0 deletions Community/thorinf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Custom Firmware

<p align="center">
<img src="./resources/tram8.PNG" alt="Tram8"/>
</p>

## 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.

<p align="center">
<img src="./resources/midi_mapper_tool.PNG" alt="MIDI Mapper Tool"/>
</p>

### 6. Exit

Exits the Menu back to ordinary play function.
47 changes: 47 additions & 0 deletions Community/thorinf/csrc/hardware_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#ifndef HARDWARE_CONFIG_H
#define HARDWARE_CONFIG_H

#include <avr/io.h>

#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
84 changes: 84 additions & 0 deletions Community/thorinf/csrc/io.c
Original file line number Diff line number Diff line change
@@ -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;
}
}
48 changes: 48 additions & 0 deletions Community/thorinf/csrc/io.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef CONTROL_H
#define CONTROL_H

#include <avr/io.h>

#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
Loading