The Sequencer is part of the LEET Synthesizer project. If you haven’t – It is a good idea to read the main post first before continuing with the details below:
LEET Sequencer is a 16 step, 8 channel sequencer using 16×8 RGB LEDs for visualization. Songs are stored on a microSD card using a custom format (FAT filesystem, MIDI-ish).
Please note two things:
- The sequencer is working, but all functions are not in place. Live recording, MIDI clock and MIDI tick editing are examples of functions that remains to be implemented in firmware.
- The sequencer is more complicated to build than the other LEET devices, so it is recommended to build one of those before the sequencer (but the difference is not that big).
This post describes how the sequencer works, what you need to build your own and how it was developed.
Description of the Sequencer
- 1x 3D printed core 3DPCB
- 1x 3D printed knob for the trimpot
- 1x 3D printed display 3DPCB
- 1x 3D printed display diffusor (two models: round or square pixels are available)
- 2x 3D printed stand clips
- 1x Arduino pro micro, or compatible clone (5V version, ATmega32U4)
- 11x 6x6mm 4pin through hole tact switches – preferably with low activation force (50g / 0.5N)
- 1x micro SD card reader (with 3.3V LDO and SPI level conversion)
- 2x 8×8 WS2812 LED matrixes
- WS2812 LED-strip with 12 LEDs (60 LEDs/m. I used IP60, white FPC)
- 1x trim potentiometer 100k (10k-500k will work)
- 2x 3pin 0.1” pin headers (from SD reader or Arduino pro micro package)
- 3x jumper cables (15-20cm female-female)
- 1x 25cm RK wire (containing 24 strands of 0.3mm copper wires)
- 3D printer (FFF/FDM with PLA & PET filament)
- Solder station with a narrow tip
- Basic tools: needle- and cutting pliers, knife, hot-glue
- Computer to run Arduino IDE and upload firmware (using a Micro USB cable)
I have not made a detailed step by step building instruction, but look at the illustration below and check out the similar step by step instruction for the keyboard. Before mounting any components, it is a good idea to fill the symbols on the base 3DPCB with paint to improve readability. You can use a marker and wipe off excess paint with an alcohol wet tissue. For more saturated colors use model paint and sand the top surface slightly with a fine grit sandpaper once the paint has dried.
This device is more complex than the other LEET devices, with more wires to be routed in an intricate way. Solder one at a time and you should be fine. Note that 5 wires are changing side under the SD card module, route those wires before mounting the SD module.
The micro SD card module I bought had an angled pin header in place that I needed to remove. Desoldering can be done by first solder a piece of wire between all pins on the backside. The wire distributes the heat allowing all pins to be removed at the same time. You can also use a desoldering pump or braid if you prefer that. The pin header can be cut in two and used as contacts for connecting the base to the display.
Note the potentiometer that controls the BPM. It is an ordinary low cost trimpot with a 3D printed knob glued to the top.
When every wire is soldered and the sequencer is tested (especially the display module) – I recommend that the pin headers on the control and display units are secured with epoxy (over the soldered side), to ensure that no wires break when jumper cables are attached or pulled.
After developing the LEET Keyboard, I wanted to be able to record and playback MIDI from the devices. I first designed a single channel mini sequencer that could be used for each instrument – making it super modular. But when I played around with it, I realized that having multiple sequencers increased the number of devices to build, cost, and made working with different songs complicated, as timing, playback and navigation needed to be synchronized. Kill your darlings – a more traditional sequencer that handles several channels seemed like a better approach. But more channels require more resources from the CPU. I considered more powerful architectures (SAMD21, ATMega4809, ESP8266, ESP32 and Raspberry Pi), but pin count, MIDI capabilities, operating voltage, availability, size and cost, complicated the decision. In the end, I decided to try to use the same Arduino micro as in the input devices (ATmega32u4), even if the specification where nowhere near what was needed:
- Typical file size of a MIDI song: 50kB
- Total CPU RAM memory: 2kB
- Time between two MIDI events: 2.5ms (1000bpm & 24 ticks/BPM)
- Display update (4.3ms)
- Time for file access on SD card: 10-20ms
With clever programming, buffers and precise control over timing, this can in theory be solved, but it became way more challenging than I expected (once again ;)
I considered using tiny OLED displays and both monochrome and full color LED matrixes for the visual feedback. Since I had blinkenlights as a goal for the project, the full color matrix was the obvious choice, and I think the end result looks great, even if user interface, memory requirement and timing, made the programming rather challenging.
When I built the first version, I realized that only the libraries for MIDI, SD card and RGB LEDs used more that 84% of available RAM and 84% Flash. By carefully testing several different libraries, I eventually managed to reach 50% Flash and 40% RAM. Usually you can pick two of RAM, Flash or performance, at the cost of the third. Optimizing all three at the same time is much more difficult. To make it work I had to develop a custom file system where each beat contains all MIDI events for the different channels in its own file. 16 beats are grouped in a pattern stored in its own directory, and the pattern directories are grouped in a song/ project directory. It’s a lot of files, but each is small enough to fit inside the available RAM. Next problem is that the file access is longer than the MIDI tick, this is solved by using dual buffers where one is used for playback, while the other is fetched from the file. I don’t like using interrupts, but since I didn’t want to rebuild the file access library, I used TMR1 interrupt for playback of the buffer, while the rest is handled in the main loop. The remaining problem is that the timing of updating the 140 RGB LEDs is so critical that the library has to disable global interrupts (playback) for a while… Careful timing is mitigating the issue, but depending on amount of parallel MIDI events, minor delays might occur. When a new pattern is displayed, there is simply not time to parse all 16 positions in order to update the display. Instead, a display cache file (led.txt) is used that contains led index and note (color) for the LEDs. (This file has to be updated when something is edited).
All in all, the code is well over 1000 lines, and even if I have been forced to clean it up on several occasions, it certainly has room for improvements – splitting it up in several files is probably a good idea to make it easier to grasp for others.
It is pretty cool that the songs are stored on a SD-card that can be read by any computer. This allows backups, sharing of songs, and also conversion between different file formats. By compressing the song directory, it is easy to save all files and directories in a single file.
To demonstrate the capabilities, I wrote a Python script that reads a MIDI file and converts it to the sequencer format, with directories, cache files and all. Unfortunately, I didn’t find any MIDI library that works with Python3, so it has to be run in 2.X.
Python script and demo files for the SD card are found in the GitHub repository.
This text is a copy of the description in leet_sequencer.ino:
- Each Song contains one or several Patterns.
- Each Pattern contains 16 positions (beats).
- Each Position contains 24 curTicks and 8 tracks.
- Every Position is stored as a separate file containing up to 32 MIDI events with corresponding tick – defining when they are due to be played.
The files are stored in the following directory structure:
Each MIDI event in PosXX.txt uses 4 bytes, where the first is curTick (time within the position), followed by three standard MIDI bytes.
- 0x00 – send event at first curTick
- 0x93 – send “note on” (0x9X) on MIDI channel 4 (0xX3)
- 0x30 – tone C4
- 0x7f – Maximum intensity (127)
(0xff in first byte means empty position, despite the following 3 bytes.)
Due to very tight memory restrictions, only one position is stored in RAM (called midiBuffer). Since loading the position file takes longer than a curTick, the midiBuffer is separated in two halves to play and preload curTicks independently.
The lower part of the midiBuffer contains 16 events and stores events on curTick 0-11, while the upper buffer stores events on curTick 12-23. At curTick 12, the lower buffer of next position is loaded (while playback is done from the upper buffer).
Playback is handled by a timer interrupt (currently using tmr1 @ 1ms) and plays the corresponding event from the midiBuffer (when seqMode == seqPlay). I prefer to avoid interrupts, but did not want to rewrite SdFat library to achieve predictable timing…
When switching to a new pattern, there is not enough time to parse all position files to update the LED display (it takes more than 100ms). Instead, SongXX/PatternXX/LED.txt contains pre-calculated values for each pixel (16×8). This file needs to be updated when a position has been altered.
The sequencer operates in one of the following modes (selected and indicated by the lower part of the control panel):
- seqStop – Default mode, displays current pattern (16 positions and 8 tracks).
- seqPlay – Plays the content of each position and moves to the next.
- seqPgm – Stores incoming midiEvents (note on /off) in current position.
- seqTrack – Used to select active track (when editing).
- seqPos – Displays notes within the current position.
- seqPattern – Displays the patterns of the song. Used to change pattern.
- seqSong – Displays the different songs on the SD card. Used to change song.
This program is using the following excellent libraries (I’m standing on the shoulders of giants):
The sequencer has reached a working stage, but there are still things that are not implemented (some are crucial for ‘real life’ usage). I have set up a forum post discussing how they are best implemented. Provide feedback, wishes and contribute if you can, all help is appreciated!
Examples of features to be added in the future:
- Improved UI (LED key feedback)
- MIDI tick editing
- Real time recording with quantization
- MIDI clock support
- Error handling using the LED matrix?
Download project files
All files required for this project are available at this GitHub repository:
This project is open source under MIT License
(Attribution is optional, but appreciated /Johan von Konow ;)