Home News and Links Local News Godel 5, the Arpeggiator Dream, is Here!

Godel 5, the Arpeggiator Dream, is Here!

November 28, 2009

Finally, after seven years, the original dream of a complete polyphonic arpeggiator is ready! And here it is: Godel 5! This design has a long history. In 2002, I started building custom voice allocators in Reaktor modules, based on computer architectures I discovered while when working for Pentium I designers at Intel. It occurred to me then that voice allocation is similar to CPU cache algorithms, because the voice assignment discards least-recently used notes. In 1998 it seemed perfectly possible to use the voice age as an index into a simulated cache for voices, with the advantage the the voice age would also be available for arpeggiating notes in the order they are played. Why did it take so long to build?

Changes in the Design Components

Now Reaktor 5 has been stable for so many years, and the bug fixes continue to refine into nether regions unknown by most instrument creators, it's hard even to remember what a trial it was to build anything in Reaktor 3 and 4. Much of the documentation was a statement of intent, rather than a description of behavior. There were half finished buttons in all the panel properties, some modules didn't work at all, and meanwhile, new features regularly appeared twice a year that required a total rebuild of anything so it would work the same way. Now I for one am very glad Native Instruments fixed all these problems, but they consumed an enormous amount of wasted energy.

A number of us put many months into designing counters so that they would cycle without creating an event loop that would crash Reaktor. Crashes were frequent and with no autosave, one was obliged to remember to save a design manually, at least several times an hour. Meanwhile, various new modules appeared which appeared preferable to the originals. The Iterator module, for example, made it much simpler to construct {FOR...NEXT} conditions graphically.

Redundant Optimizations

Also at that time, the fastest computers were still less than a 10th of the speed of the cheapest laptop now sold in Walmart. With the advent of MMX (MIMD wide instructions), Native Instruments leapfrogged its competition by using the wide instructions for concurrent polyphonic voice processing. Suddenly 10 voices would run as fast as 1. That led me to a long detour into building logic polyphonically, for better performance. These days, computers are so fast the orders of magnitude in performance optimization I was then achieving would now pass by in a fraction of microsecond. But it was a great learning experience.

Originally I had intended to build data stores in table arrays, which would directly emulate a CPU cache architecture. Just as I was starting on the design transfer, Reaktor 5 emerged with core macros. Another few years went by as promises of faster core logic pressed us into rebuilding everything in core logic. But still for event processing, the overhead of conversion between primary-level and core logic is comparatively great for the amount of accleration it provides, and with some extending testing, it was clear any realizable performance gain within core is lost at the I/O conversion.

So core logic turned out to be another long detour. Finally I returned to building in primary level logic, and started to make progress again.

The Boot Problem

New problems emerged with initialization. To kick off the sorting indices, the arrays are best populated with incremental values at startup. While Reaktor documents describe event initialization, they don't describe the differences between initialization after power up, snap change, structure change, switch change, and disabling audio in the toolbar. It's like the multilevel boots in computers, where there's a hard reset, and layers of soft resets over the toip that are slightly different. In Reaktor, the initialization is vastly different when changing snaps, switches, power, structure, and restarting the application. Furthermore, as it's undocumented, the behavior can change almost imperceptibly in intermediate dot releases. After at least a months struggling with various initialization issues in Reaktor this year alone, I become less and less convinced it wouldn't be simpler to write in C++.

Discovering The Different Kinds of Voice Allocation

Putting such pettiness aside (at least one would like to think the above issues are not so important), the main and most interesting factor that delayed this design was the discovery of subtle differences between the needs of voice allocators in different situations.

For example, if two notes of the same pitch are to be sent out the instrument on MIDI, what should happen? Reaktor instruments discretely ignore a second-note on event of the same pitch, so then, sending a note-off event for one of voices with the same pitch turns them both off. So, the output voice allocation needs to discard one of the voices with the same pitch and either sustain until the last note off; or send a note off when the earlier note finishes and quickly retrigger the note with the same pitch for the later note, until the second note-off event.

After providing that as an optional choice in Husserl, it became clear that retriggering was the preferred choice, and so Godel now does that by default.

On the other hand, inside the instrument there are often notes of the same pitch arriving from different sources, and in many cases one wants to keep a record of them all, rather than discard them. Also, there is the problem of matching note-off events to note-on events. When received from an outside source, if there are multiple notes of the same pitch, it's difficult to determine which note-off events belong to which note-on events of the same pitch, when the notes arrive from outside the design. Thus a heuristic must be applied. On the other hand, inside the design there are usually additional signals and state to indicate which note-of events belong to which note-on events, and so the pairing can be known definitively rather than guessed heuristically. However, each instrument tends to have different internal information, and I am sitll converging on the best solution.

As a consequence of various permutations on the above variations of design requirements, Husserl itself contains three different voice allocator units. When starting on Godel, I thought I could at least recycle one of them, but no! Godel also needed pitch and velocity sorting, as well as age sorting, so the input allocator is different; and as Godel creates secondary notes from primary input (rather than multiple sequences in parallel, as in Husserl), the best algorithm for output voice assignment was also different. So this year, I have now shipped five different voice allocators in Reaktor logic; and voice allocation is not the simplest function to debug in Reaktor. But I guess I have got pretty good at it now!

Looking Beyond the Allocator Boundaries

With the resolution of the boundary problems for polyphonic voice input and output, I can finally start focusing on more the interesting internals: what are exactly the best parts to combine in a family of arpeggiators? I'm looking forward to hearing from musicians as to what they need, and how I could improve the interface and functionality, to provide the best possible interactive composition tools. What do you think I could improve In Godel? I look forward to hearing from you....! Meanwhile, if you are interested in picking up on Godel now, it now has its own area on this site, at this link. Enjoy :)

Comments
Only registered users can write comments!