SoC Projects
All Projects
polysyn
8-voice touch/MIDI polysynth with scope.
Pitch / Touch Audio / CV ┌────┐ C2 touch0 │in0 │◄─ phase modulation G2 touch1 │in1 │◄─ filter envelope C3 touch2 │in2 │◄─ drive Eb3 touch3 │in3 │◄─ - └────┘ ┌────┐ G3 touch4 │out0│─► - C4 touch5 │out1│─► - - touch6 │out2│─► audio out (L) - touch7 │out3│─► audio out (R) └────┘
Voices are controlled through touching jacks 0-5 or using a MIDI keyboard (TRS MIDI or USB host is supported). The control source must be selected in the menu system (MISC page).
Voice mix is sent to output channels 2 and 3 (last 2 jacks).
For touch, the touch magnitude controls the filter envelopes of each voice. For MIDI, the velocity of each note and mod wheel affects the filter envelopes.
When a jack is patched into input 0, 1 or 2, CV can be used to modulate all voices simultaneously up to audio rate (phase mod, filter cutoff and drive). Patch an LFO into phase CV for retro ‘tape-wow’ like detuning. Patch an oscillator into phase CV for FM effects, or into drive for AM effect.
Each voice is hard panned left or right in the stereo field, with 2 end-of-chain effects: distortion and diffusion (delay), both of which can be mixed in with the UI.
USB MIDI TRS MIDI ─────┐ ┌───── (`usb-host` setting) ┌─────▼─▼─────┐ touch──►│Voice Tracker├──►┌─────────┐ └─────────────┘ │ Voices │ │ (x8) │ │ │ │ Wavetbl │ │ ▼ │ │ ADSR │ (ADSR sets filter cutoff) │ ▼ │ │ Filter │ (resonance = └─────────┘ `reso` setting) ┌────▼────┐ │Mix L/R │ │(stereo) │ └────┬────┘ ┌────▼────┐ (wet/dry mix = │Diffuser │ `diffuse` setting) └────┬────┘ ┌────▼────┐ │ Drive │ (`drive` setting) └────┬────┘ ┌────┴───┐ │Audio │ │OUT (4x)├──────► out2 (L) └────────┴──────► out3 (R)
The VOICE page allows selection between many wavetables. The ‘proc’ option allows applying effects (e.g. saturation, wavefolding, rectify) to the wavetable before it hits the voice filter. When no CV is patched into input 0, a built-in sine LFO modulates the phase of all voices (rate and depth are adjustable on the VOICE page).
The following MIDI CC mappings are supported:
CC Parameter Mode ── ───────── ──── 1 mod wheel filter cutoff 64 sustain pedal hold voices 22 waveform prev 23 waveform next 24 proc mode prev 25 proc mode next 93 proc amount absolute 71 resonance absolute 76 lfo depth absolute 77 lfo rate absolute 73 attack absolute 75 decay absolute 79 sustain absolute 72 release absolute 74 drive absolute 17 diffuse absolutePitch bend is also supported.
- class top.polysyn.top.PolySoc(*args, src_loc_at=0, **kwargs)
8-voice touch/MIDI polysynth with scope.
Pitch / Touch Audio / CV ┌────┐ C2 touch0 │in0 │◄─ phase modulation G2 touch1 │in1 │◄─ filter envelope C3 touch2 │in2 │◄─ drive Eb3 touch3 │in3 │◄─ - └────┘ ┌────┐ G3 touch4 │out0│─► - C4 touch5 │out1│─► - - touch6 │out2│─► audio out (L) - touch7 │out3│─► audio out (R) └────┘Voices are controlled through touching jacks 0-5 or using a MIDI keyboard (TRS MIDI or USB host is supported). The control source must be selected in the menu system (MISC page).
Voice mix is sent to output channels 2 and 3 (last 2 jacks).
For touch, the touch magnitude controls the filter envelopes of each voice. For MIDI, the velocity of each note and mod wheel affects the filter envelopes.
When a jack is patched into input 0, 1 or 2, CV can be used to modulate all voices simultaneously up to audio rate (phase mod, filter cutoff and drive). Patch an LFO into phase CV for retro ‘tape-wow’ like detuning. Patch an oscillator into phase CV for FM effects, or into drive for AM effect.
Each voice is hard panned left or right in the stereo field, with 2 end-of-chain effects: distortion and diffusion (delay), both of which can be mixed in with the UI.
USB MIDI TRS MIDI ─────┐ ┌───── (`usb-host` setting) ┌─────▼─▼─────┐ touch──►│Voice Tracker├──►┌─────────┐ └─────────────┘ │ Voices │ │ (x8) │ │ │ │ Wavetbl │ │ ▼ │ │ ADSR │ (ADSR sets filter cutoff) │ ▼ │ │ Filter │ (resonance = └─────────┘ `reso` setting) ┌────▼────┐ │Mix L/R │ │(stereo) │ └────┬────┘ ┌────▼────┐ (wet/dry mix = │Diffuser │ `diffuse` setting) └────┬────┘ ┌────▼────┐ │ Drive │ (`drive` setting) └────┬────┘ ┌────┴───┐ │Audio │ │OUT (4x)├──────► out2 (L) └────────┴──────► out3 (R)The VOICE page allows selection between many wavetables. The ‘proc’ option allows applying effects (e.g. saturation, wavefolding, rectify) to the wavetable before it hits the voice filter. When no CV is patched into input 0, a built-in sine LFO modulates the phase of all voices (rate and depth are adjustable on the VOICE page).
The following MIDI CC mappings are supported:
CC Parameter Mode ── ───────── ──── 1 mod wheel filter cutoff 64 sustain pedal hold voices 22 waveform prev 23 waveform next 24 proc mode prev 25 proc mode next 93 proc amount absolute 71 resonance absolute 76 lfo depth absolute 77 lfo rate absolute 73 attack absolute 75 decay absolute 79 sustain absolute 72 release absolute 74 drive absolute 17 diffuse absolute
Pitch bend is also supported.
xbeam
Vectorscope/oscilloscope with menu system, USB audio and tunable delay lines.
In vectorscope mode, rasterize X/Y, intensity and color to a simulated CRT, with adjustable beam settings, scale and offset for each channel.
In oscilloscope mode, all 4 input channels are plotted simultaneosly with adjustable timebase, trigger settings and so on.
The channels are assigned as follows:
Vectorscope │ Oscilloscope ┌────┐ │ │in0 │◄─ x │ channel 0 + trig │in1 │◄─ y │ channel 1 │in2 │◄─ intensity │ channel 2 │in3 │◄─ color │ channel 3 └────┘
A USB audio interface, tunable delay lines, and series of switches is included in the signal path to open up more applications. The overall signal flow looks like this:
in0/x ───────►┌───────┐ in1/y ───────►│Audio │ in2/i ───────►│IN (4x)│ in3/c ───────►└───┬───┘ ▼ ┌───◄─[SPLIT]─►────┐ │ │ ▼ │ ▼ ┌──────────────┐ ┌────────┐ │ │ │4in/4out USB ├────►│Computer│ │ │ │Audio I/F │◄────│(USB2) │ │ │ └──────┬───────┘ └────────┘ │ └───┐ ┌───┘ │ usb=bypass ▼ ▼ usb=enabled │ [MUX] │ ┌──────────────┐ │ │4x Delay Lines│ (tunable) │ └──────┬───────┘ │ ▼ └────┐ ┌─◄─[SPLIT]─►────┐ │ │ │ src=inputs ▼ ▼ src=outputs │ [MUX] │ │ ▼ ┌─────▼──────┐ ┌────────┬──────► out0 │Vectorscope/│ │Audio ├──────► out1 │Oscilloscope│ │OUT (4x)├──────► out2 └────────────┘ └────────┴──────► out3
The [MUX] elements pictured above can be switched by the menu system, for
viewing different parts of the signal path (i.e inputs or outputs to delay
lines, USB streams). Some usage ideas:
With
plot_src=inputsandusb_mode=bypass, we can visualize our analog audio inputs.With
plot_src=outputsandusb_mode=bypass, we can visualize our analog audio inputs after being affected by the delay lines (this is fun to get patterns out of duplicated mono signals)With
plot_src=outputsandusb_mode=enable, we can visualize a USB audio stream as it is sent to the analog outputs. This is perfect for visualizing oscilloscope music being streamed from a computer.With
plot_src=inputsandusb_mode=enable, we can visualize what we are sending back to the computer on our analog inputs.Note
The USB audio interface will always enumerate if it is connected to a computer, however it is only part of the signal flow if
usb_mode=enabledin the menu system.Note
By default, this core builds for
48kHz/16bitsampling. However, Tiliqua is shipped with--fs-192khzenabled, which provides much higher fidelity plots. If you’re feeling adventurous, you can also synthesize with the environment variableTILIQUA_ASQ_WIDTH=24to use a completely 24-bit audio path. This mostly works, but might break the scope triggering and use a bit more FPGA resources.
- class top.xbeam.top.XbeamSoc(*args, src_loc_at=0, **kwargs)
Vectorscope/oscilloscope with menu system, USB audio and tunable delay lines.
In vectorscope mode, rasterize X/Y, intensity and color to a simulated CRT, with adjustable beam settings, scale and offset for each channel.
In oscilloscope mode, all 4 input channels are plotted simultaneosly with adjustable timebase, trigger settings and so on.
The channels are assigned as follows:
Vectorscope │ Oscilloscope ┌────┐ │ │in0 │◄─ x │ channel 0 + trig │in1 │◄─ y │ channel 1 │in2 │◄─ intensity │ channel 2 │in3 │◄─ color │ channel 3 └────┘
A USB audio interface, tunable delay lines, and series of switches is included in the signal path to open up more applications. The overall signal flow looks like this:
in0/x ───────►┌───────┐ in1/y ───────►│Audio │ in2/i ───────►│IN (4x)│ in3/c ───────►└───┬───┘ ▼ ┌───◄─[SPLIT]─►────┐ │ │ ▼ │ ▼ ┌──────────────┐ ┌────────┐ │ │ │4in/4out USB ├────►│Computer│ │ │ │Audio I/F │◄────│(USB2) │ │ │ └──────┬───────┘ └────────┘ │ └───┐ ┌───┘ │ usb=bypass ▼ ▼ usb=enabled │ [MUX] │ ┌──────────────┐ │ │4x Delay Lines│ (tunable) │ └──────┬───────┘ │ ▼ └────┐ ┌─◄─[SPLIT]─►────┐ │ │ │ src=inputs ▼ ▼ src=outputs │ [MUX] │ │ ▼ ┌─────▼──────┐ ┌────────┬──────► out0 │Vectorscope/│ │Audio ├──────► out1 │Oscilloscope│ │OUT (4x)├──────► out2 └────────────┘ └────────┴──────► out3The
[MUX]elements pictured above can be switched by the menu system, for viewing different parts of the signal path (i.e inputs or outputs to delay lines, USB streams). Some usage ideas:With
plot_src=inputsandusb_mode=bypass, we can visualize our analog audio inputs.With
plot_src=outputsandusb_mode=bypass, we can visualize our analog audio inputs after being affected by the delay lines (this is fun to get patterns out of duplicated mono signals)With
plot_src=outputsandusb_mode=enable, we can visualize a USB audio stream as it is sent to the analog outputs. This is perfect for visualizing oscilloscope music being streamed from a computer.With
plot_src=inputsandusb_mode=enable, we can visualize what we are sending back to the computer on our analog inputs.
Note
The USB audio interface will always enumerate if it is connected to a computer, however it is only part of the signal flow if
usb_mode=enabledin the menu system.Note
By default, this core builds for
48kHz/16bitsampling. However, Tiliqua is shipped with--fs-192khzenabled, which provides much higher fidelity plots. If you’re feeling adventurous, you can also synthesize with the environment variableTILIQUA_ASQ_WIDTH=24to use a completely 24-bit audio path. This mostly works, but might break the scope triggering and use a bit more FPGA resources.
macro_osc
‘Macro-Oscillator’ runs a downsampled version of the DSP code from a famous Eurorack module (credits below), on a softcore, to demonstrate the compute capabilities available if you do everything in software, using a really large CPU that has big caches and an FPU.
┌────┐ │in0 │◄─ frequency modulation │in1 │◄─ trigger │in2 │◄─ timbre modulation │in3 │◄─ morph modulation └────┘ ┌────┐ │out0│─► - │out1│─► - │out2│─► 'out' output (mono) │out3│─► 'aux' output (mono) └────┘
Most engines are available for tweaking and patching via the UI. A couple of engines use a bit more compute and may cause the UI to slow down or audio to glitch, so these ones are disabled. A scope and vectorscope is included and hooked up to the oscillator outputs so you can visualize exactly what the softcore is spitting out.
(write samples to) ┌─────────────────┐ │ ▼ poll ┌────┴─────┐┌───────────────────┐ audio/ ────►│VexiiRiscv││AudioFifoPeripheral│ CV └──────────┘└──────────┬────────┘ (plaits ▼ engines) ┌──[SPLIT]────────► │ (audio out) ▼ ┌────────────┐ │Vectorscope/│ │Oscilloscope│ └────────────┘
The original module was designed to run at 48kHz. Here, we instantiate a powerful (rv32imafc) softcore (this one includes an FPU), which is enough to run most engines at ~24kHz-48kHz, however with the video and menu system running simultaneously, it’s necessary to clock this down to 24kHz. Surprisingly, most engines still sound reasonable. The resampling from 24kHz <-> 48kHz is performed in hardware below.
There is quite some heavy compute here and RAM usage, as a result, the audio buffers are too big to fit in BRAM. In this demo, both the firmware and the DSP buffers are allocated from external PSRAM.
Credits to Emilie Gillet for the original Plaits module and firmware.
- Credits to Oliver Rockstedt for the Rust port of said firmware:
The Rust port is what is running on this softcore.
- class top.macro_osc.top.MacroOscSoc(*args, src_loc_at=0, **kwargs)
‘Macro-Oscillator’ runs a downsampled version of the DSP code from a famous Eurorack module (credits below), on a softcore, to demonstrate the compute capabilities available if you do everything in software, using a really large CPU that has big caches and an FPU.
┌────┐ │in0 │◄─ frequency modulation │in1 │◄─ trigger │in2 │◄─ timbre modulation │in3 │◄─ morph modulation └────┘ ┌────┐ │out0│─► - │out1│─► - │out2│─► 'out' output (mono) │out3│─► 'aux' output (mono) └────┘
Most engines are available for tweaking and patching via the UI. A couple of engines use a bit more compute and may cause the UI to slow down or audio to glitch, so these ones are disabled. A scope and vectorscope is included and hooked up to the oscillator outputs so you can visualize exactly what the softcore is spitting out.
(write samples to) ┌─────────────────┐ │ ▼ poll ┌────┴─────┐┌───────────────────┐ audio/ ────►│VexiiRiscv││AudioFifoPeripheral│ CV └──────────┘└──────────┬────────┘ (plaits ▼ engines) ┌──[SPLIT]────────► │ (audio out) ▼ ┌────────────┐ │Vectorscope/│ │Oscilloscope│ └────────────┘The original module was designed to run at 48kHz. Here, we instantiate a powerful (rv32imafc) softcore (this one includes an FPU), which is enough to run most engines at ~24kHz-48kHz, however with the video and menu system running simultaneously, it’s necessary to clock this down to 24kHz. Surprisingly, most engines still sound reasonable. The resampling from 24kHz <-> 48kHz is performed in hardware below.
There is quite some heavy compute here and RAM usage, as a result, the audio buffers are too big to fit in BRAM. In this demo, both the firmware and the DSP buffers are allocated from external PSRAM.
Credits to Emilie Gillet for the original Plaits module and firmware.
- Credits to Oliver Rockstedt for the Rust port of said firmware:
The Rust port is what is running on this softcore.
sid
This example instantiates a SID chip, which can be modulated via CV.
┌────┐ │in0 │◄─ modulation source 0 │in1 │◄─ modulation source 1 │in2 │◄─ modulation source 2 │in3 │◄─ modulation source 3 └────┘ ┌────┐ │out0│─► voice 0 (solo) │out1│─► voice 1 (solo) │out2│─► voice 2 (solo) │out3│─► voices 0-2 (sum) └────┘
Using the menu system, each input channel can be assigned to a modulation target (i.e pitch / gate of specific voices or multiple voices).
The soft CPU then uses this mapping to redirect CV to perform specific register writes on the SID chip. To add new modulation types or for more complex modulation, only the rust firmware needs to be changed.
The audio routing out the SID chip to the audio outputs however is pure gateware. The softcore is only used for register writes.
┌──────────┐ ┌───┐ (poll CV) ─────►│VexiiRiscv│ │SID│ ─────► (audio out) └────┬─────┘ └───┘ │ ▲ └──────────┘ (register writes)
There is a lot of design space left to explore here. For example, adding MIDI input, more modulation sources, adding end of chain effects and so on…
- class top.sid.top.SIDSoc(*args, src_loc_at=0, **kwargs)
This example instantiates a SID chip, which can be modulated via CV.
┌────┐ │in0 │◄─ modulation source 0 │in1 │◄─ modulation source 1 │in2 │◄─ modulation source 2 │in3 │◄─ modulation source 3 └────┘ ┌────┐ │out0│─► voice 0 (solo) │out1│─► voice 1 (solo) │out2│─► voice 2 (solo) │out3│─► voices 0-2 (sum) └────┘
Using the menu system, each input channel can be assigned to a modulation target (i.e pitch / gate of specific voices or multiple voices).
The soft CPU then uses this mapping to redirect CV to perform specific register writes on the SID chip. To add new modulation types or for more complex modulation, only the rust firmware needs to be changed.
The audio routing out the SID chip to the audio outputs however is pure gateware. The softcore is only used for register writes.
┌──────────┐ ┌───┐ (poll CV) ─────►│VexiiRiscv│ │SID│ ─────► (audio out) └────┬─────┘ └───┘ │ ▲ └──────────┘ (register writes)There is a lot of design space left to explore here. For example, adding MIDI input, more modulation sources, adding end of chain effects and so on…
sampler
3-channel sampler with CV+touch control.
Record audio into a single shared sample buffer (~5 sec). Three independent grain channels read from different positions in the same buffer. Toggle recording from the DELAYLINE page.
┌────┐ │in0 │◄─ audio in (record source) │in1 │◄─ gate ch0 (CV or touch) │in2 │◄─ gate ch1 (CV or touch) │in3 │◄─ gate ch2 (CV or touch) └────┘ ┌────┐ │out0│──► channel 0 │out1│──► channel 1 │out2│──► channel 2 │out3│──► mix (ch0+ch1+ch2) └────┘
Each ‘Grain Channel’ is independent, with a start and stop position and playback mode. The behaviour of touch/CV is different in each mode:
Gate: Play while gate is high. Stop and reset on release.
Oneshot: Trigger full grain on rising edge.
Loop: Continuously loop from start to end (gated by touch/CV).
LoopOn: Loop with gate stuck on. Touch/CV controls playback speed.
Bounce: Ping-pong between start and end (gated by touch/CV).
BounceOn: Bounce with gate stuck on. Touch/CV controls playback speed.
ScrubFast: CV scrubs position within grain.
ScrubSlow: CV scrubs position (with one-pole filter)
When no cable is plugged into a gate input, the corresponding jack captouch (1/2/3) acts as a gate. If a jack is plugged in, gate trigger is 2V with 1V hysteresis. Jack CV may also be used to control pitch or scrub grain position, depending on the playback mode.
Record may be toggled ON and OFF at any time, to bring in new material and freeze the sample buffer. Alternatively, record may be left permanently ON and gates triggered while new material is arriving. This can be used for slewable delayline and Karplus-strong effects (especially in SCRUB mode).
Note
WARN: saving / loading settings and playback buffers happens to a fixed region in SPI flash at the moment, which is a bit slow!
Note
WARN: pop prevention is not implemented yet, you might need to fiddle with the grain start/end positions to get clean gates.
- class top.sampler.top.SamplerSoc(*args, src_loc_at=0, **kwargs)
3-channel sampler with CV+touch control.
Record audio into a single shared sample buffer (~5 sec). Three independent grain channels read from different positions in the same buffer. Toggle recording from the DELAYLINE page.
┌────┐ │in0 │◄─ audio in (record source) │in1 │◄─ gate ch0 (CV or touch) │in2 │◄─ gate ch1 (CV or touch) │in3 │◄─ gate ch2 (CV or touch) └────┘ ┌────┐ │out0│──► channel 0 │out1│──► channel 1 │out2│──► channel 2 │out3│──► mix (ch0+ch1+ch2) └────┘
Each ‘Grain Channel’ is independent, with a start and stop position and playback mode. The behaviour of touch/CV is different in each mode:
Gate: Play while gate is high. Stop and reset on release.
Oneshot: Trigger full grain on rising edge.
Loop: Continuously loop from start to end (gated by touch/CV).
LoopOn: Loop with gate stuck on. Touch/CV controls playback speed.
Bounce: Ping-pong between start and end (gated by touch/CV).
BounceOn: Bounce with gate stuck on. Touch/CV controls playback speed.
ScrubFast: CV scrubs position within grain.
ScrubSlow: CV scrubs position (with one-pole filter)
When no cable is plugged into a gate input, the corresponding jack captouch (1/2/3) acts as a gate. If a jack is plugged in, gate trigger is 2V with 1V hysteresis. Jack CV may also be used to control pitch or scrub grain position, depending on the playback mode.
Record may be toggled ON and OFF at any time, to bring in new material and freeze the sample buffer. Alternatively, record may be left permanently ON and gates triggered while new material is arriving. This can be used for slewable delayline and Karplus-strong effects (especially in SCRUB mode).
Note
WARN: saving / loading settings and playback buffers happens to a fixed region in SPI flash at the moment, which is a bit slow!
Note
WARN: pop prevention is not implemented yet, you might need to fiddle with the grain start/end positions to get clean gates.
selftest
Collect some information about Tiliqua health, display it on the video output and log it over serial. This is mostly used to check for hardware issues and for calibration.