-
Having done some profiling of Hydrogen's audio processing, the current implementation of attack-decay-sustain-release calculation is surprisingly expensive. For the most part, at least with the kits that ship with Hydrogen, the instruments are configured without any envelope shaping so the processing time for this can be almost eliminated. But whenever ADSR is used, the processing time gets really unnecessarily expensive. The current implementation uses a couple of lookup tables, which sounds like it should be fast but actually isn't due to the way the code is structured implying multiple divisions per frame, and the tables themselves occupying a cache-busting 32kb of memory. There's some improvement that can be made to indexing those tables, but they are large and inefficient, and don't lend themselves well to vectorisation. By contrast, actually computing a real exponential curve for applying ADSR is very, very cheap on modern hardware (particularly if you write it in a vectorisable way) so I set about reverse-engineering the contents of After scratching my head a bit and wondering if the curve-fitter I'd just written was somehow buggy, I eventually realised that these tables are not exponentials, despite the name (and ADSR being typically thought of as an exponential function). They're actually polynomials (x^2.71828 or so) closely approximating an exponential curve! So, to come eventually to my point. I would like to propose changing the ADSR envelope to exponential functions, which will result in a slightly different envelope. The audible difference should be barely noticable, and in return the CPU processing requirements for any songs that actually use ADSR will reduce significantly. Does anyone have any opinions on this? |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 7 replies
-
That's totally strange. Can you think of a reason it could have been designed that way? Maybe a buggy implementation of the exponential/logarithmic function in the language used to create these tables? But on the other hand, these are amongst the very basics of any language at least partially concerned with math.
Sounds good. Nice find by the way. |
Beta Was this translation helpful? Give feedback.
-
So, actually it turns out there is a fast way to approximate this curve that's only a bit more expensive than doing an exponential decay, but my googling didn't find it, I had to sit down and think it through. I need to check the accuracy is okay, but if it is, I'll use that rather than exponential decay for the sake of keeping things as-they-are. There will be some differences in the calculated waveforms, but way smaller than those for a different envelope shape. |
Beta Was this translation helpful? Give feedback.
-
After some investigation, the approximation method necessary for the attack phase is still very expensive. But also, having looked at the envelope, I don't think the current profile's attack is particularly close to what most people would expect or want, as it peaks sharply and instantaneously before decaying, so I've come to the conclusion that a more classical exponential function isn't just quicker to calculate, it's also more desirable. With some curve fitting to find parameters that give something close to the current envelope shape, I have an implementation that gives this envelope shape ("new.data") in comparison with the old one ("old.data"): Benchmarking just the ADSR time for non-trivial ADSR calculations, on an M1 Macbook Pro (and enabling -ffast-math for that routine), we get:
x86 doesn't scale quite as well, being only about 5x as fast on my x86 machines, but might do better on something more modern. |
Beta Was this translation helpful? Give feedback.
-
Fun fact: our ASDR implementation used to be... funny. When I migrated our unit tests to CppUnit, I wrote tests for ADSR and it turned out that our envelopes looked like this: ;) |
Beta Was this translation helpful? Give feedback.
After some investigation, the approximation method necessary for the attack phase is still very expensive. But also, having looked at the envelope, I don't think the current profile's attack is particularly close to what most people would expect or want, as it peaks sharply and instantaneously before decaying, so I've come to the conclusion that a more classical exponential function isn't just quicker to calculate, it's also more desirable.
With some curve fitting to find parameters that give something close to the current envelope shape, I have an implementation that gives this envelope shape ("new.data") in comparison with the old one ("old.data"):
Benchmarking just the ADSR time for n…