Weird Constructor / Posts / HexoSynth 2022 - Devlog #6 - Workflow and Oscilloscope

HexoSynth 2022 - Devlog #6 - Workflow and Oscilloscope

The HexoSynth modular synthesizer (programmed in Rust) got an awesome oscilloscope and lots of workflow improvement in the last week.

New screenshot for HexoSynth repository
New screenshot for HexoSynth repository
The new HexoSynth oscilloscope
The new HexoSynth oscilloscope

If you don't want to dig through the detailed log:

TLDR: Skip to the Devlog 6 Conclusion Section

Detailed Log

This is the day by day log of my progress. If this is too verbose for your interest, please skip down to the Devlog 6 Conclusion Section.


HexoSynth: Started implementing a very basic sample selector. Currently only the samples in the current working directory are listed. But it allowed to me finally mess around with the integrated sample player. You can listen to some stuff I did in the video section at the end.


HexoDSP: While playing around with the sample player and TsLFO I found a bug in the DSP implementation of the TriSawLFO. It was originally ported from ValleyAudio's ValleyRackFree codebase to Rust and I did not rethink the whole logic. Modulating the reverse point of the LFO now will work properly.

HexoDSP: I also sat down and write up a guide how to implement a new DSP node. Refer to the HexoDSP API Documentation - DSP node implementation guide.


HexoDSP: I've been bug hunting the last hours after hearing some artifacts in the sample player. Also the delay line interpolation is now properly tested and should sound less noisy in the reverb. While I was at it, I refactored the sample player code and the cubic interpolation function.

HexoDSP: Also wrote more documentation for some helper functions/structures in HexoDSP.

HexoSynth: Added more GUI tests for the picker LMB/RMB click workflow.


HexoTK: The DPI factor has been a long standing issue with HexoTK. Before I focus more on GUI features I feel that I need to tackle this problem first. What use is a GUI that is too small on other people monitors or with other people's screen scaling preferences? Anyways, the first step was to tell the layout to layout for smaller/larger DPI factors:

Scaling the layout of the widgets as starting point
Scaling the layout of the widgets as starting point


HexoTK: I took the approach to run the GUI layout in physical pixel dimensions. That means the layout algorithm is no longer based on the logical pixel dimensions. That worked well as you have seen in the last screenshot from 2022-07-21. However, the programmer does not know about DPI of the target machine and specifies the amount of pixels in the HexoTK widget's Style data structure in logical pixels.

This means, HexoTK needs to derive the physical pixels amounts from the Style data structure when drawing the widget. But that means I have to touch all pieces of drawing code in HexoTK and make it DPI aware. I tried doing it the easiest way first by just applying a DPI factor to all the sizes when I draw. I saw this was going the right direction, see the next screenshot:

DPI corrected widget border/shadow drawing, also a few other widgets were already ported to use DPI correction
DPI corrected widget border/shadow drawing, also a few other widgets were already ported to use DPI correction

This told me, that the approach works and I sat out to improve the code. I did not want to litter my drawing ode with a dpi_factor all over the place, with the risk of forgetting to apply the factor. So I sat down and thought about an API improvement. The idea was to wrap my current Style data structure and apply the DPI factor on the fly when accessing it from the drawing code:

DPIStyle abstraction helps to keep the code clean
DPIStyle abstraction helps to keep the code clean

The resulting code looks much cleaner and more maintainable. So the next task is to touch every piece of drawing code in HexoTK and make it DPI aware using this new DPIStyle abstraction.

Anyways, some hours later I was done with the DPI factor conversion of the GUI drawing code. Now the GUI is freely scalable. I don't have a good test setup here to check if it works with a real 4k display. And the fractional scaling on Ubuntu seems to act weird on my computer. So I will leave it at that for now, until either I or someone else can test it. At least I got the prerequisites sorted out now to ultimately do proper DPI scaling.

HexoSynth at 0.5 scale, prerequisite for DPI factor is done!
HexoSynth at 0.5 scale, prerequisite for DPI factor is done!


HexoSynth: I improved the movement of a single cell with the right mouse button. The operation will now try to keep the connection to the adjacent cell(s) alive.

Movement of a single cell will keep the connections now

HexoSynth: A while later I added the DSP chain split mouse gesture/functionality. By dragging with the right mouse button between adjacent cells, you can split up a chain of hex cells and push one part away. This allows you to easily insert new nodes anywhere you want.

Splitting DSP chains, for easy node insertion

HexoSynth: By dragging from an empty cell to a cell with a node you can create copies and new instances of the same type. You can also drag a cell to a more distant (non adjacent) cell for creating a linked copy or a new instance right at the destination.

Creating linked copies and new instances of the same type by dragging with LMB/RMB


HexoDSP: I greatly simplified building simple DSP chains for the hexagonal Matrix API today. This improves future test generation and also allows refactoring DSP chain placement in HexoSynth. With the MatrixCellChain builder API this rather complicated direct setup of hexagonal cell tiles:

let sin = NodeId::Sin(0);
let amp = NodeId::Amp(0);
let out = NodeId::Out(0);, 0, Cell::empty(sin)
                   .out(None, None, sin.out("sig")));, 1, Cell::empty(amp)
                   .input(amp.inp("inp"), None, None)
                   .out(None, None, amp.out("sig")));, 2, Cell::empty(out)
                   .input(out.inp("inp"), None, None));

Becomes much easier and straight forward to understand:

let mut matrix = Matrix::new(node_conf, 3, 3);
let mut chain = MatrixCellChain::new(CellDir::B);

chain.node_out("sin", "sig")
    .node_io("amp", "inp", "sig")
    .set_atom("gain", SAtom::param(0.25))
    .node_inp("out", "ch1")
    .place(&mut matrix, 0, 0);

HexoSynth: Next step was to integrate the new MatrixCellChain API into HexoSynth. That worked very well. The MatrixCellChain API also allowed proper error reporting, which improved creation and maintenance of the GUI test suite.

HexoTK: Implemented very basic image loading in HexoTK. Still need to make it load images from memory though. But the WichText widget can now draw images:

HexoTK can now load and draw PNG/JPG images.
HexoTK can now load and draw PNG/JPG images.

HexoSynth: The test suite now covers all the important hex matrix editing gestures.

HexoSynth: Added more actions to the context menu today too:

HexoSynth more context menu actions, to cleanup the unused ports
HexoSynth more context menu actions, to cleanup the unused ports

HexoSynth: One last addition for the day was the "Remove Chain" operation and a confirmation dialog for the destructive action.

Remove Chain command and confirmation popup dialog


HexoSynth: This morning I added multiple sources of random/unexpected inspiration. You can add random input/output nodes to an existing node or create completely random collections of nodes. The idea is to inspire you to experiment around, find interesting combinations. What about a challenge: Of using 6 randomly generated nodes to make an interesting soundscape?

HexoSynth adding random nodes to the matrix

HexoDSP: As preparation for an oscilloscope view in the GUI I added a special Scope DSP node that captures a portion of it's input signal and writes it to a shared buffer.

HexoSynth: A few hours later, and I got the first prototype of the oscilloscope working:

HexoSynth oscilloscope prototype

You can actually have up to 3 signals being displayed in the same oscilloscope:

HexoSynth oscilloscope supports 3 signals
HexoSynth oscilloscope supports 3 signals

Next I added styling API for the graphs. And then you see how important a bit of color and contrast can be:

HexoSynth oscilloscope proper styling
HexoSynth oscilloscope proper styling


HexoSynth: After styling the oscilloscope I enhanced the layout today and moved it into the panel with the pattern editor. A button for toggling it's size has been added too.

HexoSynth oscilloscope integrated into pattern editor panel

HexoSynth / HexoTK: Next I added labels to the oscilloscope, for displaying the min/max and range between min and max:

HexoSynth oscilloscope signal labels
HexoSynth oscilloscope signal labels

HexoDSP: One step further and I was able to assign more parameters to the Scope node. Such as the time setting, which allows you to zoom into the sample recording of the oscilloscope. The time setting goes from 0.1 millseconds up to 300 seconds (5 minutes) of scaling. Of course, at that scale we are seriously undersampling the signal, which is only useful if you want to look at long term CV signals anyways.

HexoSynth time setting on the oscilloscope


HexoDSP/HexoTK/HexoSynth: I've further peeked into VCV Rack version 2 source code of the Scope module. It improved waveform drawing a lot since version 1 of VCV Rack. The waveform there is now drawn using the min/max envelope of the signal. This has multiple advantages actually. It gives a more realistic view of the source signal, as all maximums and minimums are recorded properly. This allows not only to draw a nice waveform in the scope, but also displays the exact values in the labels properly!

HexoSynth scope with improved waveform drawing
HexoSynth scope with improved waveform drawing

YouTube Sound Demos

Sample player shenanigans after I got a prototyped sample selector working again:

Sounds kinda scary :-)

Devlog 6 Conclusion

This week and a few extra days has been a ride through all depths of HexoSynth' code. First the slow start with the super basic sample selector, based on my overpowered text widget (WichText). Had some fun with the sampler afterwards.

I worked a lot on the UI workflows this week. All matrix editing operations from the HexoSynth 2021 are back now. You can move whole chains of cells. You can delete chains. You can split chains, you can move around individual cells. And you can even rotate connected cells around it's source/destination cell. Most of these operations even are covered by automated GUI tests by now.

There are also more context menu functions now. You can remove unused port assignment and even generate new random nodes for inspiration.

I also went back the core of the GUI library HexoTK and implemented DPI based GUI scaling. I was not able to properly test it yet, but it's prerequisites are implemented once I or someone else can test that.

As the big changes on the GUI more or less came to an end, I wanted to tackle another big new feature for HexoSynth. I wanted a larger scale signal oscilloscope to inspect the signals in greater detail. The new scope node allows exactly that now. And the fundamentals of it are implemented well now after two days. There are still some little features and automated tests missing for it though.

Next up are the following tasks on my list:

  • Finish oscilloscope implementation, there are still tests to add and little things to implement.
  • Add more automated test cases for the UI workflow.
  • Maybe, just maybe focus on WBlockDSP again. The idea of small contained JITed DSP nodes still got a hold on me. And I kind of want to tackle all big things early on.
  • Rewrite the online help.
  • Add inserting DSP chains that are to be pre-defined. And also inserting random DSP chains for a more explorative/experimental workflow.
  • Finish the nih-plug integration. That means a better integration of HexoSynth as VST3/CLAP plugin into your favorite DAW.
  • Add back editing CV widgets.


In case you find this project interesting or have questions, you can reach me via Discord these days, check out the #hexosynth channel in the Rust Audio Discord. Optionally I'm also online on IRC (via Matrix) in the Linux Audio Developer channel #lad (nickname 'wct') on Libera.Chat:


Weird Constructor Avatar
Weird Constructor

Nice end 30 y/o guy, geek and F(L)OSS developer that messes with: Linux, Windows, Networking, Interpreters/Compilers, Games, Audio, Music, GUIs, C/C++, Rust, Scheme/Lisp, 3D printing, electronics and more.

For updates follow me on Mastodon

All the F(L)OSS development on my projects happens in my spare time. If you find what I do useful, entertaining or just want to support me, you can do this via Liberapay:

Donate using Liberapay