Skip to content

Conversation

paulfd
Copy link
Member

@paulfd paulfd commented Nov 22, 2020

This reworks the opcode parsing, with 3 main goals:

  • Have default constants in a .cpp file
  • Avoid the overloaded/templated setXXXfromOpcode methods which have to be duplicated for different use cases.
  • Have a finer "clamping" behavior for out-of-bounds values (i.e. ignore the value or clamp it)

The opcodes defaults are now OpcodeSpecs, with the following signature:

template<class T>
struct OpcodeSpec
{
    T value;
    Range<T> bounds;
    int flags; // can be kIgnoreOOB, kCanBeNote, kEnforceLowerBound, kEnforceUpperBound
};

These signatures are in Default.cpp for all relevant opcodes. Some opcodes share a spec, most notably the key one that spans hikey, lokey, pitch_keycenter, amp_keycenter, ...

Some defaults still have to live outside of this paradigm, for example default ranges for crossfades and enums.
I also kept the readBooleanFromOpcode for now.
I also moved the effect defaults into Defaults.h.

The "new" way to parse an opcode is now to use the member function read(OpcodeSpec<T> spec), either as

if (auto value = opcode.read(Default::loopRange))
   loopRange.setEnd(*value);

or with the single-line shortcut for simple plain values that could be "replaced" by the new opcode.

delay = opcode.read(Default::delay).value_or(delay);

The member method properly dispatches between int/float specs, and handles the opcode as specified by the flags.

This builds on top of #555. I still need to activate some of the tests, and I will add tests and OSC interfaces for the effects also.

@jpcima
Copy link
Collaborator

jpcima commented Nov 22, 2020

This looks fantastic, but now all builds are failing at generating the editor UI.
An idea why that is? I will check on my side.

@paulfd
Copy link
Member Author

paulfd commented Nov 22, 2020 via email

@jpcima
Copy link
Collaborator

jpcima commented Nov 22, 2020

The reason of this problem is that the final commit would remove the generated file.
As consequence, the target directory would be empty, git does not record this directory, generating fails because of the missing directory.

@jpcima
Copy link
Collaborator

jpcima commented Nov 22, 2020

The flag kIgnoreOOB feels like an anomaly.
It's better imho kEnforceBounds = kEnforceLowerBound|kEnforceUpperBound, and not enforcing bounds being default.
This makes also the code more straightforward, the conditionals kIgnoreOOB can go altogether.

@jpcima
Copy link
Collaborator

jpcima commented Nov 22, 2020

Nevermind I understood it wrong. I take it that kIgnoreOOB must be for enumerated values.
It raises some questions though, why is it set some parameters which are not enumerated?
(eg. compThreshold, compRatio)

@paulfd
Copy link
Member Author

paulfd commented Nov 22, 2020

No actually it's more the "ignoring vs clamping" question when you get a value out of bounds, so not for enumerated. For example what to do if you get offset=40 offset=-1? Do you keep offset at 40 or "clamp" the -1 to zero?

@jpcima
Copy link
Collaborator

jpcima commented Nov 22, 2020

Do you keep offset at 40 or "clamp" the -1 to zero?

Imho a maximum of the opcode reading process should be encoded into the spec.
That includes "-1" special values, textual enumerations, normalizations to %, and so on.

@paulfd
Copy link
Member Author

paulfd commented Nov 23, 2020

I'll try to put in more.

Regarding the out-of-bounds situation, it seems sforzando has varying behaviors. For example, negative values for ampeg_attack will cause the value to be reset to the default one. Negative values for pitch_keycenter seem to screw up everything however. I think I'll remove the kIgnoreOOB flag, and in case the value is out of an enforced bound, then the read method returns nullopt. In the current parsing most of the time this will leave the value unchanged rather than reset to default, it seems better like this.

@jpcima jpcima force-pushed the region-parsing branch 2 times, most recently from 45db083 to d19de33 Compare December 12, 2020 13:34
@paulfd
Copy link
Member Author

paulfd commented Feb 4, 2021

Hi,

A bit tired of this PR tbh, but here goes. Now the OpcodeSpec handles everything in the background. The prototype is as follows:

template<class T>
struct OpcodeSpec
{
    T defaultInputValue;
    Range<T> bounds;
    int flags;
    /**
     * @brief Normalizes an input as needed for the spec
     *
     * @param input
     * @return T
     */
    T normalizeInput(T input) const { .... }

    operator T() const { return normalizeInput(defaultInputValue); }
};

Flags can be

enum OpcodeFlags : int {
    kCanBeNote = 1,
    kEnforceLowerBound = 1 << 1,
    kEnforceUpperBound = 1 << 2,
    kNormalizePercent = 1 << 3,
    kNormalizeMidi = 1 << 4,
    kNormalizeBend = 1 << 5,
    kWrapPhase = 1 << 6,
    kDb2Mag = 1 << 7,
};

In usage for parsing, opcodes are initialized as

int64_t offsetRandom { Default::offsetRandom }; // offset_random

and parsed as

crossfadeVelCurve = opcode.read(Default::crossfadeCurve);

or if you need an unset optional as default

downKeyswitch = opcode.readOptional(Default::key);

The read method basically calls readOptional, and readOptional sends back nullopt if the value is e.g. out of bounds. The read method then sends back the default value if this happens. This matches ARIA's behavior. All the switching and normalization appears behind the scene. I also tried to tidy up the effects and put their defaults with the others as much as possible.

Some cleanups, commenting and testing to do still.

@paulfd paulfd requested a review from jpcima February 4, 2021 20:40
@paulfd paulfd merged commit f9eb818 into sfztools:develop Feb 12, 2021
@paulfd paulfd deleted the region-parsing branch January 21, 2022 10:24
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