Live data from Hacker News

C++ audio mixing library design

lisyarus.github.io

41–50 of 54 posts

Re: C++ audio mixing library design

#41

Earlier quoted context omitted.

Lambdas and some other complicated looking extensions have been on the plate for quite some time, and it seems like none of them made it into C23. Lambdas in particular have been promoted for the most part by a single person AFAICT. "C is becoming into everything C++ has" is a drastic misrepresentation.

I think I take the middle position between you and GP, C is slowly working it’s way towards unnecessary complexity. Most of the ‘X language is a C replacement’ posts and threads here on HN seem to disregard C’s development (particularly from 2017 onward) through committee standards releases. Everyone keeps arguing about Rust, Zig, Odin, etc and claiming one or the other is clearly more C like and the others are too c…

I agree, there is some proposals in there that I doubt they fit well with the culture. However what has actually been accepted that makes you think C in the heads is not C in 2022? In my head, the biggest practical change from 1990 C is declare-anywhere (including declare-in-for-loop) which in my estimation has afforded a lot of added ergonomics at practically zero semantic cost. The next most relevant one is standardization of memory models, which is a good one AFAICT (but I used it only a little, so far). One unfortunate addition from 1999, VLAs, has even been demoted in the following revision.

In 2022 I can jump into basically any C codebase and be immediately productive. Which can't be said of some other languages.

Re: C++ audio mixing library design

#42
post #40

Earlier quoted context omitted.

Because NULL is dogshit. It's a #define 0. That's not one way to do an operation, that's one way to do an operation, horribly badly. That int on your stack ? Sure it can equal NULL. Hope that wasn't the result of (2 - 2).

In C the NULL macro can be defined as either (void*)0 or 0. It's only mandated as 0 in C++. The nullptr concept was introduced into C to fix a type ambiguity when NULL is used with generic selection or varargs functions. The ambiguity could have been solved by mandating that NULL be defined as (void*)0. My issue with nullptr is its an overkill solution that unnecessarily duplicates the concept of NULL in the language…

I agree, it should have been (void*)0. I doubt that nullptr_t will see much use (as much as _Generic is a fringe addition), but we'll find out.

Re: C++ audio mixing library design

#43

Earlier quoted context omitted.

I think I take the middle position between you and GP, C is slowly working it’s way towards unnecessary complexity. Most of the ‘X language is a C replacement’ posts and threads here on HN seem to disregard C’s development (particularly from 2017 onward) through committee standards releases. Everyone keeps arguing about Rust, Zig, Odin, etc and claiming one or the other is clearly more C like and the others are too c…

I agree, there is some proposals in there that I doubt they fit well with the culture. However what has actually been accepted that makes you think C in the heads is not C in 2022? In my head, the biggest practical change from 1990 C is declare-anywhere (including declare-in-for-loop) which in my estimation has afforded a lot of added ergonomics at practically zero semantic cost. The next most relevant one is standar…

I don’t think there is any one addition to the C that has moved C to far up the complexity hierarchy. It is the cumulative effect of all the proposals submitted, some of which are approved, and the movement to continue to add more. Like I said, C still enables developers to restrict themselves to a simple and long existing subset of features and idioms making coding in C a productive exercise for experienced developers. But the continued push from all directions, and with varying likelihood of being accepted, to change the language wear on me, to the point I continually want to just make a C99 clone with my preferred improvements and just use that instead.

Also, I suspect I phrased the ‘C in head CS C in 2022’ line backwards from what I thought I was saying. In my mind I was saying the arguments for the simplicity of C had less to do with the language as it exists now and more to do with the closeness of the language to some ISA/actual machine’s operating characteristic in the past. It was that closeness that enabled (or necessitated) C’s simplicity.

It wasn’t about the changes the committee has made to the language since the 1990’s. The tie in point was (in my mind) implied to be that now that C has become even less of a mapping to actual hardware the simplicity sought in a C replacement should look different, but commenters seem to be looking at a very surface level.

Re: C++ audio mixing library design

#44

- A stream is something you can play as a sound. It may be loaded from a file or generated on the fly. It may be finite or infinite. One important thing is that a stream is single-shot: it doesn’t have a restart method or anything like that. - A channel is something where a stream is actually played. You can request the channel to stop, or you can replace the stream this channel is playing. Am I mistaken, or isn't th…

Yeah, very similar model for sure. I say this having used GStreamer in anger though: the complexity of Glib and GObject makes OP’s API quite a bit nicer to use. There are tradeoffs though; while GStreamer is complex and error-prone, it does have two big pieces that OP’s approach doesn’t:

- metadata in the streams. GStreamer streams have rich data attached to them that allows both sides of the chain to automatically negotiate the specific format and properties of the data in the stream.

- introspection. Given an opaque/genetic GObject pointer to an element in the processing graph, you can query its configuration and sources/sinks without knowing what the object is up front. This ultimately can allow you to walk the entire graph if you want and inspect all of the elements.

Re: C++ audio mixing library design

#45

- A stream is something you can play as a sound. It may be loaded from a file or generated on the fly. It may be finite or infinite. One important thing is that a stream is single-shot: it doesn’t have a restart method or anything like that. - A channel is something where a stream is actually played. You can request the channel to stop, or you can replace the stream this channel is playing. Am I mistaken, or isn't th…

Yeah, very similar model for sure. I say this having used GStreamer in anger though: the complexity of Glib and GObject makes OP’s API quite a bit nicer to use. There are tradeoffs though; while GStreamer is complex and error-prone, it does have two big pieces that OP’s approach doesn’t: - metadata in the streams. GStreamer streams have rich data attached to them that allows both sides of the chain to automatically n…

> the complexity of Glib and GObject makes OP’s API quite a bit nicer to use

Yeah, Glib and GObject are a massive PITA, thankfully both have C++ bindings and can be used from Vala, so it's not that bad in practice. The C API is a nightmare though.

Re: C++ audio mixing library design

#46

Earlier quoted context omitted.

I agree, there is some proposals in there that I doubt they fit well with the culture. However what has actually been accepted that makes you think C in the heads is not C in 2022? In my head, the biggest practical change from 1990 C is declare-anywhere (including declare-in-for-loop) which in my estimation has afforded a lot of added ergonomics at practically zero semantic cost. The next most relevant one is standar…

I don’t think there is any one addition to the C that has moved C to far up the complexity hierarchy. It is the cumulative effect of all the proposals submitted, some of which are approved, and the movement to continue to add more. Like I said, C still enables developers to restrict themselves to a simple and long existing subset of features and idioms making coding in C a productive exercise for experienced develope…

Consider that some of the C23 changes have in fact cleared up ambiguities and removed support for ancient hardware. So I find that the renewed pushes behind the standard are not all promises of eventual disintegration. There are a few accomplished and considerate people in WG14. If you visit the page I linked above you'll get a sense of that.

I never particularly cared about ISAs - when I think about the "simplicity" and "closeness to hardware" I mean the control over data layout and control flow. Data layout and architecture of the global data flow is often the key to 90-100% of the performance gains you can expect; and more or less stable binary interfaces mean interopability and modularity, minimizing ecosystems lock-in. Where you need to use a certain instruction is the rare case, and you are recommended to code it in assembly or using compiler extensions.

Re: C++ audio mixing library design

#47
post #7

I love audio programming, it's one of those things that's provided me with a lot of joy. I've been working on our in-house audio library for our game engine as well, and designing custom effects for it. In our case, we wanted an 80s DSP sound, so it was fun diving into the old literature. In the end, a simple design won out: https://twitter.com/JasperRLZ/status/1567546255454371842

Do you have any references for the old literature you looked at while trying to capture that sound. I would love to add some of it to my reading list. Also, the processing and track itself in your video are fantastic.

My code has these links as comments for reading material:

https://www.soundonsound.com/techniques/springs-plates-bucke...

https://www.dsprelated.com/freebooks/pasp/Schroeder_Reverber...

http://www.music.mcgill.ca/~gary/courses/papers/Moorer-Rever...

https://www.dsprelated.com/freebooks/pasp/Freeverb_Main_Loop...

Most reverbs these days are convolutional. I looked at those too! But they require an expensive FFT (usually run in a thread), and the quality sounded "too good" for the audio target I wanted to hit.

After trying a lot of stuff, my main primitive ended up being a delay line hooked up to a low-pass. The output sample is a delay line going through a low-pass. That output is mixed with the incoming sample, and shoved back into the delay. Effectively, this makes a comb filter. I run eight of these in parallel at slightly different delay lengths to comb different frequencies. I was trying to shoot for "digital blur" that had a bit of crunch and aliasing that I wanted, while still sounding like a reverb.

And the track was done by Jake Kaufmann, our composer at Yacht Club Games! I can't take any credit for it.

Re: C++ audio mixing library design

#48
post #15
post #2

Very nice article, however in 2022, why using C arrays as parameters in C++ code, when std::span, std::vector exist?

std::span is new enough to not be in muscle memory of C++ programmers. Even the standard library commits a similar sin with `from_chars` and not taking a `string_view`.

string_view would definitely have been wrong.

std::span did not exist in the Standard at the time std::from_chars was adopted.

An overload that takes std::span would be easy, and harmless. But as std::from_chars is only ever used from within some other abstraction, any benefit would be minimal.

Re: C++ audio mixing library design

#49
post #30

Earlier quoted context omitted.

It is conservative, except for nullptr which duplicates NULL. This violates C's own charter of "provide only one way to do an operation."

Because NULL is dogshit. It's a #define 0. That's not one way to do an operation, that's one way to do an operation, horribly badly. That int on your stack ? Sure it can equal NULL. Hope that wasn't the result of (2 - 2).

Well, since 0 is guaranteed to compare equal to the null pointer, my current code compare my pointers to it directly:

  if (ptr != 0) { foo(*ptr); }
The type mismatch is ugly, but that saves me an include (this particular code minimises its dependencies to maximise portability).

Re: C++ audio mixing library design

#50

- A stream is something you can play as a sound. It may be loaded from a file or generated on the fly. It may be finite or infinite. One important thing is that a stream is single-shot: it doesn’t have a restart method or anything like that. - A channel is something where a stream is actually played. You can request the channel to stop, or you can replace the stream this channel is playing. Am I mistaken, or isn't th…

Yeah, very similar model for sure. I say this having used GStreamer in anger though: the complexity of Glib and GObject makes OP’s API quite a bit nicer to use. There are tradeoffs though; while GStreamer is complex and error-prone, it does have two big pieces that OP’s approach doesn’t: - metadata in the streams. GStreamer streams have rich data attached to them that allows both sides of the chain to automatically n…

True! But also my use case it quite different compared to GStreamer, and I don't really need all that metadata & introspection.
Post reply on HN