A tiny tale of woe... (aka what happened build 4400?)

Hopefully you’ll be finished by Sunday and my last gig of the year is over! :wink:

1 Like

That’s one consequence of using Claude that I’m also noticing - a dramatic increase in automated testing. I’m sooo lazy when it comes to writing unit testing…

But typically a lot of that is stuff that is only valuable ONCE - when I write and debug the actual functions. After that, all these unit tests just run through happily on every change, creating more informational noise…

I wish, but yeah nah.

That means you’re human and no an AI.

I think this depends on the project, I always find them more useful than annoying.

1 Like

Today was a case of one step back, one step forward.

I realized the real-time heap that I built last week needs to be used for various VST 3 objects whose life-time is outside the control of the engine - meaning a rogue plugin not releasing something could really mess things up.

So, today I threw away the old heap implementation and started again… again. It’s fractionally less efficient, but still very fast, still lock free but much more reliable.

The new approach uses more typical heap allocator (loosely based on the gnu-c malloc allocator) which removes the fragility of using a highwater heap. It uses a large virtual memory reservation and a committed range for the heap. There’s one of these heaps per core audio thread (so no locks needed) and frees are handled through a return queue back to the allocating thread.

A low priority background service thread also monitors the heap vs commit size of each core’s heap and can commit more memory if/when necessary.

Also these heaps are preserved and re-used across engine restarts so a heap that still has live allocations isn’t stuck in memory unable to be used.

All in all a much more reliable setup.

4 Likes

Wow - that’s really getting down into the plumbing. I typically take standard implementations of stuff like memory allocation, garbage collect etc. for granted - until they let me down…

But for time-critical stuff like real-time audio, I guess that there’s a ton more to handle individually in order to not be blindsided by system ideosynchrasies…

Yep, allocating memory on a real time audio thread is a big no-no. And since vst3 plugins can ask the host to create object instance and because they can and do on the audio thread this has to be solved properly.

It’s also used for other things like midi event lists, sysex blobs etc.

1 Like

Whilst maybe not suited to a Cantabile type application, this is why I love Java and its incremental Garbage collector approach. I have not had to think about malloc() or free() or memory leaks in over a quarter of a century of Java development.

Still at least we can allocate memory. My work background is rooted in real time safety critical applications where heaps are a huge no-no as you cannot statically analyse their behaviour, so you need to pre-allocate the arrays. The trade off is in SPARK ADA you can statically prove that your code has zero run time errors - very powerful for that type of application….

Yep garbage collection is awesome, but also something I don’t want the audio engine touching. It’s why the audio engine is written in C++ and not C# and why the audio threads never call out to C# code - specifically to keep it away from .NET’s garbage collector - because if it runs in the context of an audio thread, that audio thread will stall and you will get glitches.

1 Like

Good progress today.

Started porting the actual execution nodes: AudioBuffer, MidiBuffer, BackEdgeNode, MidiMuxer and MidiGate are done, with unit tests:

MidiFilter and all the Midi Filter item types are ported, but untested.

Past half way! (kind of)

8 Likes

It really does instil me with confidence for the future of Cantabile with all this fundamental work under way!

4 Likes

Baby steps…

  • finished unit test for MidiFilter,
  • ported and unit tests for MidiRouter, MidiInjector, MidiExtractor and MidiHook.

4 Likes

Today I planned to get started on some of the transport related nodes, but after half a day of getting nowhere (insufficient brain power) decided to park it and knock over something easier.

Finished with tests: AudioLimiter, AudioPeakReader, AudioPeakMixer, AudioPeakProxy.

5 Likes

Sometimes when stuck it is best to do some deliberate yet productive displacement activity to give the neurons a chance to do some assimilation of the problem! :slight_smile:

2 Likes

Slowly but surely…

AudioMixer, AudioMixerChannel, AudioMixerEntry, Transport, TransportController, MidiClockListener and MetronomeSequencer.

I’ve also decided to re-work the way transports are handled because the old way was too complex. New simpler system is “done” - but probably doesn’t support everything I need it to. I’ll probably need to revisit this as I port the MIDI and Audio players and update Cantabile to use the new engine.

4 Likes

Young punk. I turn 75 on Wednesday. :rofl:

Most of my professional programming was done in assembler, and later in C. My first pro programming after the Army and 7 years of university was in octal assembler with a 3-pass paper tape assembler on a PDP-8e (the last model with a wire wrapped motherboard) in 1978-79. 1 MB unsealed 14" hard drive, 32K x 12 core memory, TED line editor, ADM-3 terminal. Paddle switches to load the bootloader one 12-bit word at a time. Those were NOT the days. :zany_face:

That was my tale of woe. Now back to Cantabile.

3 Likes

Voicemeeter Audio Callback, please :folded_hands:

Please explain in detail.

In Voicemeeter any ASIO client (DAW, Cantabile) can apply effects on Inputs (inputs can be WDM/MME mic, input of ASIO hardware or virtual input) it’s called Asio Patch Insert. But it does not have access to Outputs (can be WDM/MME Speakers/output, ASIO hardware output or virtual output).

So with Cantabile I can apply effects for mic and guitar with low realtime latency but to apply Headphones HRTF vst plugin (Dsoniq Realphones, Waves NX) I have to use other program, there are only 1 or 2 of them. There is API in Voicemeeter, called Audio Callback where all channels for inputs and outputs can be accessed, Equalizer APO supports it (mode that only expose Outputs channels) and there some other software that I dont like, looks vibecoded. I have to use Equalizer APO that have some problems with hosting vst plugins like 6-10 seconds delay after plugin changes affect sound. Equalizer APO even have fork with better VST support but it still not enough. Not even mentioning multithread support, choosing number of cores, etc.

Here example and struct used with C# unsafe code showing simplest possible processing and API itself

This is outside the scope of what I’m currently working on, and not even sure it belongs in Cantabile’s audio engine - it’s more like a driver level responsibility.

Done: MidiDeviceManager, MidiDeviceIn, MidiDeviceOut, MetronomeSounds

Also, some revisions to ThreadPool so class so it can be used for two different thread pools - the main audio graph execution and as a background worker pool.

Also, ported the 1ms real-time timer (used to schedule outgoing midi events)

Less than 20kloc to go:

(also just to be clear this countdown of work remaining is just to port the audio engine - it doesn’t cover actually updating Cantabile to use it).

2 Likes