Shruthi - Firmware hacking

Where?

The firmware code is hosted on github.

Latest version (v1.02).

What?

To build the firmware you need:

To flash it, you need either:

The Shruthi build environment is command-line / makefile based. OS X is the system on which Mutable instruments develops ; but getting it to work on Linux is straightforward. On Windows machines you might have to fiddle a bit with system paths to get things to work.

How?

Building the firmware

  1. Get the source from github (or run git clone ...).
  2. Move to the directory created by git (or the directory created when decompressing the .zip).
  3. Run git submodule init and git submodule update to pull the dependencies.
  4. Edit the avrlib/makefile.mk file (extended by all makefiles) to change the path (AVR_TOOLS_PATH / AVR_ETC_PATH) to the directory containing the AVR toolchain. This path depends on your system.
  5. In the same file, change PROGRAMMER to the reference of your ISP programmer (don’t care about this if you want to upload your modified firmware by MIDI).
  6. Build the resources: make resources. Pre-compiled resource files (resources.cc, resources.h) are already bundled with the source code, so if you don’t modify the Python resources file, you don’t need this step.
  7. Build the firmware: make.
  8. Make sure that the firmware size is below 64512 bytes: make size (If you don’t have figlet and cowsay installed, you’re going to miss some awesome Moose action, but you can still type: cat shruthi1.size and add the first 2 numbers). Note that trying to upload with the standard MIDI update procedure a firmware size too large can “brick” your unit.
  9. If you want to upload the firmware by MIDI (or realease a mod to the community): make midi (you need python installed on your computer for this step). The resulting file is in build/shruthi1/shruthi1.mid.

Bootloader development

The “firmware update by MIDI” feature is made possible by a 1k bootloader programmed into the chips distributed with the kits. The source code of this bootloader can be compiled with make -f bootloader/makefile ; and uploaded with make -f bootloader/makefile upload

Starting from a blank chip

The single command: make bake_all first sets up the fuses at a slow speed ; and then flashes the firmware, bootloader and internal eeprom at high speed.

Installing the factory presets

This is not properly speaking firmware development, but hey, here we go. The factory data for the eeprom is in this file. Play the .syx file into the Shruthi (with its firmware installed of course).

Some notes about the code

AVR development library (avrlib)

Most of the code directly dealing with the hardware is in the avrlib directory, which is an import for the avril project. You’ll find there various template classes allowing hardware resources (serial ports, switches, LCD display…) to be manipulated with a friendly, high-level syntax… without the cost associated with abstraction since everything is flattened/evaluated/inlined at compile time. A good example (I think) of what C** can bring to embedded development.

Resources

All the data residing in program flash ROM (strings, lookup tables, waveforms) is contained in the shruthi/resources.h and shruthi/resources.cc files, which are automatically generated from some python code (this is where the wavetable generation happens). If you want to add a string to your program, add the string to the list shruthi/resources/strings.py, build the resources, and use the ResourcesManager::LoadStringResource(STR_RES_MY_STRING, buffer, size); idiom to copy it to RAM whenever you want to access it.

Code tour

Core data structures

Patch (patch.h) is the data structure holding the synthesis parameters. SequencerSettings (sequencer_settings.h) is the data structure holding the sequencer configuration (tempo, quantize, arpeggiator pattern) and the sequence itself. SystemSettings (system_settings.h) is holding the global configuration data (MIDI options, transpose, tuning).

Storage (storage.h) contains the routines for loading patch or sequence data from the internal or external eeprom ; and for parsing incoming SysEx MIDI messages (since all of them are related to reception/transmission of user data).

Synthesis

Envelope (envelope.h) has two instances for the two envelope generators. Just like Lfo in lfo.h.

Oscillator (oscillator.h) contains the main oscillator code. To add an oscillator algorithm:

  1. Add the oscillator name in the oscillator names list in strings.py
  2. Add an entry in the OscillatorAlgorithm enum in patch.h
  3. In oscillator.cc, locate the table of function pointers at the end of the file. Add an entry: &Osc::RenderMyAlgo. Note that you have to make sure that the string, enum entry, and function pointer entry are in the same order! For example, if you’ve added the string at the end of the list, you’ll have to add the enum entry and function pointer entries at the end of their respective lists too.
  4. Add a void RenderMyAlgo(uint8_t* buffer) forward declaration in oscillator.h ; and implement void Oscillator::RenderMyAlgo(uint8_t* buffer) in oscillator.cc. Your function must fill kAudioBlockSize samples of audio into the buffer passed as an argument. The macros BEGIN_SAMPLE_LOOP, END_SAMPLE_LOOP and UPDATE_PHASE can be used to take care of phase incrementation and sync. RenderDirtyPwm is probably the simplest example to study!

TransientGenerator (transient_generator.h) is the sound generator responsible for adding clicks/attacks at the beginning of the note.

NoteStack (note_stack.h) is a stack (implemented as a linked list with “pointers in a pool array” instead of pointers) used for storing the depressed keys and doing voice allocation.

SynthesisEngine (synthesis_engine.h) contains the bulk of the synthesis code, and integrates all the synthesis-related objects described above. The synthesis engine can handle several voices (at the moment only one) and start/stop a sound on each voice. However, it is not responsible for doing all the voice stealing / arpeggiation stuff. The classes responsible for turning sequencer data and incoming MIDI messages into actual notes routed to the synthesis voice are VoiceController (voice_controller.h) and VoiceAllocator (voice_allocator.h). VoiceAllocator is used in polychaining mode, and implements a LRU voice-stealing algorithm. VoiceController is used otherwise, and handles the note priority (through a NoteStack) and all the additional events generated by the sequencer or arpeggiator.

Interaction with the rest of the world ®

Editor (editor.h) is responsible for all the UI, pages navigation, etc. There are 3 modes for the editor, corresponding to the 5th key: patch ; sequence ; and performance. Each mode contains grouped pages. Each page has a UI Type (sequencer, traditional 4 parameters view). For each UI type, callback functions for drawing the screen, handling pots or encoder events are implemented.

ParameterDefinitions (parameter_definitions.h) is the central repository of everything the Shruthi needs to know about each synthesis parameter: its name, unit, descriptive string, and range. It is used by the editor, the random patch generator, to validate incoming NRPN data, and to scale incoming CC data.

MidiDispatcher (midi_dispatcher.h) is the class that will handle the reception of MIDI messages. All the handlers (NoteOn, ControlChange) will be inlined into the MIDI parsing state machine. Those handlers are responsible for updating the LCD display (status character), the Editor (step by step recording), the MIDI out and obviously the synthesis engine.

Beast

The main code is in shruthi.cc. It consists of short chunks of code, aka “tasks” called in turns. The most important task, called inbetween all the other ones, is AudioRenderingTask which fills as much as possible of the audio buffer. Very few things happens in timer ISR-land (clocked at 39kHz):

  1. Pop a sample from an audio buffer and write it to the PWM output
  2. Every 16th call, pop a nibble from the display buffer and write it to the LCD
  3. Every 16th call, pop a byte from the MIDI out buffer and write it to the UART (This means the Shruthi can only achieve 78% of the maximum MIDI bandwidth).
  4. Every 16th call, debounce the switches
  5. Every 32th call, do the math to keep track of the number of ms elapsed since boot

Releases

v1.02

This version improves the scanning rate of knobs for the “classic” (4-knob) Shruthi, and contains several minor bug fixes.

v1.01

Major code rewrite!

Synthesis

Sequencing

Performance and system

MIDI

UI

Filter board handling

v0.98

Minor bug fixes, and extended SysEx support for third party editors.

v0.97

Synthesis

Filter board handling

UI

v0.96

Synthesis

Filter board handling

Code

v0.95

Synthesis

Code

UI

v0.94

Synthesis

Code

MIDI implementation

v0.93

Synthesis

MIDI

Code

v0.92

UI

System

MIDI

Synthesis

Code

v0.91

v0.90