Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal changes for the modulations and processing #142

Merged
merged 72 commits into from
Mar 31, 2020

Conversation

paulfd
Copy link
Member

@paulfd paulfd commented Mar 29, 2020

List of the changes:

  • The midi state now tracks time through block duration; you need to call advanceTime(numSamples) at each callback.
  • The midi state now stores all CC and pitch events that happened during the block.
  • Added group polyphony and note_polyphony support, as well as note_selfmask support.
  • Multiple CCs can modulate pan, amplitude, width, position rather than one.
  • The voices do not store CC history for the modulation targets and rely on the midi state to provide the events that happened during the last block.
  • Removed the EventEnvelopes; they're replaced by generic free functions that take as input an EventVector from the midi state; old tests migrated.
  • Added support for tune_cc and pitch_cc related opcodes.
  • In all this process, I also removed most of the per-voice temporary buffers and preallocation to replace everything by a BufferPool that distributes buffers around, and can track the maximum buffer usage in debug mode. The number of concurrent buffers are set at compile-time, so it must be adapted to the current processing needs.

paulfd added 30 commits March 28, 2020 23:41
@paulfd
Copy link
Member Author

paulfd commented Mar 31, 2020

Ok it should be in good shape now. The idiom for modifiers and CCs is as follows:

for (const auto& mod : modifierSource) {
    const auto events = midiState.getCCEvents(mod.cc);
    multiplicativeEnvelope(events, modifiedCCSpan, [&](float normalizedCCValue) { return func(normalizedCCValue); });
    // Use the span, for example applyGain<float>(modifiedCCSpan, modulationSpan);
}

You can see in ddeecf4 how it's used e.g. for the new support of pitch_cc/tune_cc opcodes.

@paulfd paulfd changed the title [WIP] Internal changes for the modulations and processing Internal changes for the modulations and processing Mar 31, 2020
@paulfd
Copy link
Member Author

paulfd commented Mar 31, 2020

Side note: _smoothccN will still require some work, but the _curveccN and _stepccN should be pretty easy since they just change the function of the modifier. I plan to add a curveIndex and steps field to the modifiers, so that we'd just need to update the lambdas in the envelope computation.

@paulfd paulfd requested a review from jpcima March 31, 2020 08:56
@paulfd
Copy link
Member Author

paulfd commented Mar 31, 2020

So given also the conversation on #48, for now the envelopes are computed for each modulation target and the "base" envelope is not memoized anywhere. The memoization could come later, but I expected the gains to be quite marginal. My reasoning is as follows:

  • Assume a simple case with 1 amplitude-type envelope (linear) and volume-type envelope (multiplicative), for which you can have n_a and n_b modifiers respectively.
  • In the first case (1) we proceed like here, and each CC modulating each target will generate a linear/multiplicative envelope respectively based on the CC values registered in the state. Assuming generating these envelopes have cost E_lin and E_mul, the cost equal to n_a * E_lin + n_b * E_mul per voice.
  • In the second case (2), the midistate will memoize the envelope for all CCs. Let's assume that this memoization is free. The amplitude-type envelopes will probably require multiplicating by a constant, an operation that costs E_gain, while the multiplicative types envelope will require multiplying by a constant, applying an exponential-or-equivalent function with cost E_exp, and remultiplying by another constant. Overall, the cost would be n_a * E_gain + n_b * ( E_exp + 2 * E_gain ) for each voice -- once again assuming generating the CC envelopes has no cost.
  • In most cases, E_lin costs around 4 * E_gain if there is at least an event in the block, and around 2 * E_gain if there is no events which is the commonest case. Similarly, E_mul costs about 6 * E_gain if there is at least an event, and 2 * E_gain if not. On the other hand, E_exp is always quite expensive at around 10 E_g. The total cost of (1) would probably be around E_gain * (2 * n_a + 4 * n_b) per voice assuming CCs are all trigger every other block, whereas 2 would cost E_gain * ( n_a + N * n_b ), where N = 12 with the naive exp. We may be able to reduce this through table lookup or something but it will probably won't go too much below 4 or 5.

Also note that we're talking sub-µs for all of these in reasonable block sizes. The machine I have now has E_gain = 25 ns, E_lin = 100ns, E_mul = 125 ns and E_exp = 350 ns for a block of size 256.

In the end, I chose to forgo memoization for now since it would complicate the logic a bit for marginal gains in performance. If this is a problem down the line it won't be too hard to change: we can store the pre-computed envelopes in the MidiState and replace

for (const auto& mod : modifierSource) {
    const auto events = midiState.getCCEvents(mod.cc);
    multiplicativeEnvelope(events, modifiedCCSpan, [&](float normalizedCCValue) { return func(normalizedCCValue, mod.value); });
    // Use the span, for example applyGain<float>(modifiedCCSpan, modulationSpan);
}

by something along the lines of

for (const auto& mod : modifierSource) {
    midiState.getCCEenvelope(mod.cc, modifiedCCSpan);
    applyModifier(modifiedCCSpan, [&](float normalizedCCValue) { return func(normalizedCCValue, mod.value); });
    // Use the span, for example applyGain<float>(modifiedCCSpan, modulationSpan);
}

@paulfd
Copy link
Member Author

paulfd commented Mar 31, 2020

To give perspective too, there are 5 modifier targets with linear-type behavior and 2 modifier targets with multiplication-type behavior now.

@paulfd paulfd merged commit 2f2de8c into sfztools:develop Mar 31, 2020
@paulfd paulfd deleted the midi-state-block-processing branch January 21, 2022 10:23
essej pushed a commit to essej/sfizz that referenced this pull request Sep 23, 2023
Added a new Container Clip clip type that can contain other audio/MIDI clips and play them back in a looped/repeated style
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.

1 participant