Polyend Tracker CE: the audio code is the fun part
Damn - so cool. Polyend just put the firmware of the Tracker family on GitHub: polyend/tracker-ce. One codebase for the original Tracker, the Tracker+ and the Tracker Mini. One of the best trackers I know.
I skipped most of the plumbing and went straight for the audio code. What does a shipped groovebox use for its reverb? How does the sample editor stretch a loop? What is inside the new CHIP synth? I didn’t flash a build yet, so everything here comes from reading the code, not from listening.
Quick note on the licence
It is source-available, not open source in the OSI sense. Firmware source and docs are PolyForm Noncommercial. Making music with it (also commercially) is explicitly OK, selling the firmware or putting it into a competing product is not. The .ptfx plugin SDK is MIT. And the factory synth engines live in precompiled libraries, so you can link them but not read them.
That means: the open audio code is the sampler/effects side, the CHIP synth, and the plugin examples.
Where the audio lives
core/synth/engines/: the new open CHIP synth.core/libs/Audio/: the real-time audio graph (a fork of the Teensy Audio Library style), sample playback, delay, reverb.core/effector/: the sample editor effects. These run offline on a selection of the sample, in blocks of 8192 samples, so the UI stays alive.core/AudioEngine/MasterEnhancer/: master bass, stereo width and EQ.editor-fx-sdk/effects/: small example plugins (kick generator, filter, delay, …).
1. CHIP: a chiptune synth in one file
Where: core/synth/engines/ChiptuneSynthVoice.cpp
About 1000 lines, and this is the file I would read first. It’s one voice class with eight “models”, and the render loop is a plain per-sample loop. Easy to follow, and full of little chiptune details:
- 2A03 Pulse, Arp Chord, Zap/Laser all use a pulse wave with PWM, made band-limited with PolyBLEP. The classic trick to get rid of the worst aliasing without wavetables:
| |
- The triangle is quantized to 16 steps, like the 4-bit triangle of the NES. The sub oscillator uses the same stepped triangle, one octave down.
- Noise is a real LFSR (a 15-bit shift register), clocked at a pitch-tracked rate. The “metallic” short mode taps another bit, like on the NES noise channel.
- FM Bell is a 2-operator FM with a list of ratios (1, 2, 3, 3.5, 4, 5, 7, 9) selected by the Shape knob. The carrier output is quantized to a 6-bit DAC character. Cheap, and it sounds right for that style.
- SID Saw/Tri blends saw, triangle and a ring-mod flavour.
- Super Square is three detuned PolyBLEP pulses.
- Chip Drum morphs from kick to snare to metallic hat with one knob: an exponential pitch drop on a stepped triangle plus LFSR noise.
- Zap/Laser has a built-in cubic pitch drop.
- The arpeggiator runs per sample inside the voice, 4 steps, default speed 50 Hz. That’s the PAL frame rate, the speed old trackers and game music used.
The signal path after the oscillator is worth a look too: bitcrush and sample-rate decimation come first, the low-pass filter after. So the filter smooths the crunchy aliasing instead of the crusher making it harsher. The filter is a Chamberlin state-variable filter with its cutoff modulated by the envelope over up to 4 octaves, and with clamps on the coefficient and the state for stability.
What we can learn: you don’t need a big engine for a characterful synth. A handful of cheap “imperfections” (steps, LFSR, quantizing) already gives an era.
2. Reverb from Mutable Instruments
Where: core/libs/Audio/Polyverb/
Nice find: the reverb (“Polyverb”) is adapted from Émilie Gillet’s reverb from the Mutable Instruments code (MIT licence, headers are kept). The comment in reverb.h says it is the Griesinger topology from the Dattorro paper: 4 allpass diffusers on the input, followed by a loop of two sections with 2 allpasses and a delay each. Two slow LFOs modulate the allpass and the long delays for a bit of shimmer.
The Tracker wrapper is small: amount, time, damping (a low-pass in the loop), diffusion and predelay. It also has an isActive() check with a threshold, so the engine can tell whether the tail is still ringing. And all delay memory is 16-bit and split into two buffers, which tells you how tight the memory is.
Also in the same folder: a tempo-synced short delay (effect_shortDelay) with a table of 19 musical rates and ping-pong mode, with its buffer in SDRAM.
3. The sample editor effects
Where: core/effector/
These are the offline effects you apply to a sample. They are short, readable implementations of classic DSP blocks. Some highlights:
Time stretch (SOLA). mtEffectTimestretch.cpp is my favourite here. It uses SOLA (synchronized overlap-add): cut the sample into overlapping chunks, then for every new chunk search a small window for the position where the cross-correlation with the previous chunk is best, and cross-fade there. That keeps the phase of the waveform aligned and avoids the typical flutter of simple granular stretching. There are two modes: Beat (fit the sample to N steps at the project tempo) and Note (a target length in seconds with a grain size in ms). I would read this one with the SOLA papers next to it.
Compressor and limiter. Both use the textbook recipe from the DAFX book: an envelope detector, a gain computer, and attack/release smoothing with 1 - exp(-2.2 / (fs * t)). The compressor works on the RMS level in dB with a slope of 1 - 1/ratio. The limiter smooths the peak and the gain separately. I didn’t see any look-ahead in it, which is a good thing to keep in mind when you use it on transients.
Overdrive. Three shapers, with links to the papers in the comments: a piecewise quadratic soft clipper (linear in the middle, a parabola near the top, then hard clip), an exponential one and a reciprocal one (x / (|x| + 1)). Three very different characters from a few lines each.
EQ. A 3-band parametric EQ based on eqfil from Soundpipe.
Flanger and chorus. These follow the Teensy audio library style: a delay line, a sine LFO from a Q15 table and linear interpolation between taps in the flanger. The chorus averages several taps from the delay line.
Bitcrusher. Bit reduction by shifting, rate reduction by holding samples. Simple and fast on integers.
Wavetable smoother. This turns a sample into a smooth wavetable by cross-fading between the windows. Great idea for making wavetables out of recordings.
4. Playback: one class per play mode
Where: core/libs/Audio/play_memory_*.cpp
The sample playback is split into one file per mode: single shot, forward / backward / ping-pong loops, slice, beat slice, granular and wavetable. That probably keeps the hot loops free of per-sample mode checks, which is a good thing on a small CPU.
Two details I liked:
- Granular windows are lookup tables for the grain envelope (square, triangle and one called “gauss”). In the code the “gauss” table is actually a half sine (
sin(pi * x)). Cheap, smooth and it works. - Wavetable mode plays single-cycle windows of 32 up to 2048 samples, where a “window position” parameter selects the cycle.
And the new modulation stuff, wavetable envelopes and LFOs with 128 factory and 128 user shapes, is a nice way to animate parameters with drawn curves instead of a plain ADSR.
5. The “master enhancer” is mostly hardware
Where: core/AudioEngine/MasterEnhancer/
I expected a DSP chain here. But it’s a thin layer over the SGTL5000 codec: bass enhance, surround/width, an auto volume control (the codec has a built-in compressor/limiter) and a 5-band graphic EQ are all features of the chip. The firmware only sets registers. A smart way to get effects for zero CPU, and a hint about why the master chain behaves as it does.
6. Plugins: tiny, readable DSP
Where: editor-fx-sdk/effects/
The six reference plugins are a good place to start. Two I would look at:
- Analog Kick Gen is the classic recipe in about 40 lines: a sine with an exponential pitch drop (from
TopFreqdown toSubFreq), an exponential amplitude decay and a 5 ms noise burst as the click. - Multi-Mode Filter uses a Cytomic (Andy Simper) TPT state-variable filter with LP 24/12, BP, HP, notch and peak modes, plus five drive types in front. The 24 dB mode is two cascaded stages.
Plugins can’t call libc directly, they get a small table of math functions (sinf, expf, powf, …) from the host. A cute design for a position-independent binary loaded from an SD card.
What I would try next
- Read the CHIP voice next to a PolyBLEP article, then change one model.
- Build a small
.ptfxgenerator, for example a snare or hat next to the kick. - Compare the SOLA time stretch with the one in your DAW on a drum loop. I’m curious how it behaves on transients.
- Look at
play_memory_granular.cppand the loop variants for how grains are positioned. - Check the unread parts:
effect_dynamics.cpp,filter_variable.cppandsynth_waveform.cppincore/libs/Audio/.
Takeaways
- Small tricks (stepped waves, LFSR, quantizing) give a lot of character.
- A lot of sound here is well-known public DSP: PolyBLEP, Griesinger reverb, SOLA, Cytomic SVF, DAFX dynamics. The firmware shows how to use them on a tight budget.
- Use the hardware you have. The codec does the master effects.
- The code is short and commented, so it makes a great study set for embedded audio.
