ContiguousArray reallocates itself after every append

@michelf Ok, that's definitely the cause then. Any suggestions for reading the original array without triggering copy-on-write?

You're not casting. You're calling an initializer of an Array to produce a new Array out of an arbitrary Sequence. Doing so requires the array to materialize the sequence (run through its iterator, copying/retaining all of the elements that it spits out) to save them into a new buffer.

1 Like

Does the renderer run in parallel to the looping? Like are there 2 threads simultaneously accessing this array? If so, that's a big no-no, that would require a concurrent queue to synchronize, barrier blocks to safely to do writes.

As I see it, your options are:

You will need some kind of synchronisation to ensure the loop doesn't also mutate the array while the renderer is doing this.

  1. Don't hold a reference to the array in the waveform renderer. Again, this will require synchronisation and some kind of scoped access (i.e. in a closure).

At that point, though, it's becoming so finicky and prone to breakage that you're better off using an UnsafeMutableBufferPointer in the loop, and synchronising while you copy a version out to the waveform renderer (which could be in an Array).

1 Like

@Karl @AlexanderM there is a concurrent queue in place for thread safety. Using AEMessageQueue from The Amazing Audio Engine to grab the array and compute the drawing parameters, and then doing final rendering on the main thread.

I'd suggest avoiding using a COW container, perhaps by using UnsafeMutableBufferPointer. Since you are only appending to that container and it won't ever be reallocated, perhaps you could share the pointer between threads pass the new length to the other thread in a message after writing some values. I don't think you'll get race conditions reading the values this way, but even if you do it shouldn't cause crashes with a primitive type like a Float, only some garbage values.

2 Likes

Another option would be to use smaller chunks and have the loop pass them on to the renderer once they're full (with the looper creating a new chunk and releasing any references to the old one). Somewhere down the chain you can put them in to a linked-list structure.

Perhaps it would be worth writing and using a simple little (manually managed) SampleBuffer for holding the samples?

1 Like

Yes, some sort of fixed-size circular buffer that the renderer reads from.

Just tried this. Works great, problem solved. Thank you!

1 Like

Chunking as @Karl suggest would also be a good idea if you want to maintain fidelity of the data. The fact that you want to read data while in transit is somewhat fishy.

This would work great for the initial render while recording, but often the waveform needs to be re-rendered completely (ie when the view bounds changes), which requires accessing all of the samples and recalculating drawing parameters. The pointer solution @michelf suggested works well and is simple, so I will most likely stick with that.