Skip to content

DistrhoPluginAU: Do not treat lossy conversion as failure (#552) - #554

Open
polluxsynth wants to merge 1 commit into
DISTRHO:mainfrom
polluxsynth:dev-loadfix
Open

polluxsynth wants to merge 1 commit into
DISTRHO:mainfrom
polluxsynth:dev-loadfix

Conversation

@polluxsynth

Copy link
Copy Markdown

When parsing values in plist, do not bail out if the conversion is "lossy", i.e cannot be represented exactly as a float32. This will normally never happen if a preset is restored after having been previously saved by DPF, but can happen if the preset has been generated in other ways, like running an application which converts it from some other format, such as LV2..

Disclosure: This patch was created with the help of AI. I have found it useful for my MiMi-d plugin, where I have the factory presets in LV2 format, and convert them to .aupreset files when building the AU format version of the plugin.

When parsing <real> values in plist, do not bail out if the
conversion is "lossy", i.e cannot be represented exactly as a
float32. This will normally never happen if a preset is
restored after having been previously saved by DPF, but
can happen if the preset has been generated in other ways,
like running an application which converts it from some
other format, such as LV2.
@falkTX

falkTX commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

This doesnt make sense, if the type is not float32 then we shouldn't give a pointer to a float.
With the check above we already know its a number, so if this is about float vs double we should change the value data type to match.

@polluxsynth

polluxsynth commented Oct 12, 2026 •

Copy link
Copy Markdown
Author

The problem that I've seen is that the numbers in the .aupreset files are written in decimal (naturally, since it is a text based format), yet there are decimal numbers which cannot be represented exactly as floats (e.g. 1.1), and in such a case CFNumberGetValue() returns false because the conversion from text to float32 is considered 'lossy', i.e. cannot be represented exactly. Thus the problem is not that the numbers are not float32's, it's a question of how exact their representation is.

This has happened to me when converting .lv2 files to .aupreset files by simply copying the text values from the .lv2 file to the .aupreset file, and it seems rather fragile for the .aupreset load procedure to be picky about exact conversion of numbers when the actual difference in value would seem insignificant. At any rate, I think it would be better to perform a best effort load rather than bail out completely.

Of course, a different solution is for whatever program performing the conversion to .aupreset to recalculate the numbers so that the textual representation exactly can be represented as a float32, but given that the tags for values in the .aupreset files is <real>, it doesn't really imply that the values need to be exact float32 values, but that any real number should suffice, with the implicit understanding that real numbers aren't necessarily exact anyway.

@falkTX

falkTX commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

well floating point numbers are decimal numbers.

the check above the code you changed already checks if it's a number

DISTRHO_SAFE_ASSERT_BREAK(valueRef != nullptr && CFGetTypeID(valueRef) == CFNumberGetTypeID());

in order to pass this line we know for sure the value is a valid number.

if the conversion to float32 cant be used, we can convert to float64 instead and then do the "lossy" part ourselves.
or keep the changes as-is, but then how do we check for errors?

@falkTX

falkTX commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

The API docs for the call: https://developer.apple.com/documentation/corefoundation/cfnumbergetvalue(_:_:_:)?language=objc

If the argument type differs from the return type, and the conversion is lossy or the return value is out of range, then this function passes back an approximate value in valuePtr and returns false.

so the float32 type is the wrong one to use here.
try with kCFNumberFloat64Type where value is a double, does it pass without errors?

@polluxsynth

Copy link
Copy Markdown
Author

The point here is that there are decimal numbers that cannot be represented exactly as either float32 or float64 . One such value is decimal 1.1, which in binary becomes 1.00011001100110011001... with an infinitely repeating fractional part, so whatever binary form you convert it to, it will be considered 'lossy'.

In fact what the documentation says is that the CFNumberGetValue() will always return a value of the desired type, but it will be an approximation if it cannot be represented exactly, either because the number is out of range, or because the conversion is 'lossy'. So while bailing out on the previous lines if the value is not a number is viable, because that would be an error in the .aupreset file, but there is no reason to bail out because of a lossy conversion - the conversion error for a 'lossy' conversion will be too small to have any impact on any sane plugin, and even if that were the case, the resulting value is still a valid float32.

@polluxsynth

Copy link
Copy Markdown
Author

Any further comments on this? As I mentioned the problem is not really a lack of precision, rather it is that certain valid numbers in the aupreset files cannot be exactly represented in any binary format, because the former are written using decimal numbers. This can be a problem when converting presets from another format, rather than saving them from state. I ran into this use case when converting from LV2 to aupreset, if the LV2 files have been tweaked manually (this causes no issues when loading them), and I can't really see any problem, given that the function in question will always return a valid number.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants