Conversation
|
Tested it with a simple field recording. Instant blade runner. |
|
Some options can be modulated, some not. Why is that? Cpu? |
m-m-adams
left a comment
There was a problem hiding this comment.
So first off this sounds great! Awesome job and thanks for contributing
Testing this it looks like there's 2 performance issues. Using a test song that can sustain 50 voices, I lose 5 for each clip I add a grain fx too and they stay lost until the modfx is turned off (not just away from grain). E.g. going from off->stereochorus will get 48 voices, going from grain to stereochorus will get 45. This is just caused by the memory for the grain buffer. A first fix is to deallocate that buffer when it's not needed. A potential further fix could be to allocate a single global grain buffer and treat it as a pseudo send fx. Each voice could pull from it and add to it with it's own depth and rate. This is more work to implement so I haven't tested it but I think it would work and still sound cool, if a bit less 'traditional' granular.
Secondly the DSP code is more intensive than the other modfx. I think this is improvable, I've left some comments on easy wins but the bigger one is probably vectorizing the grain calculations. They're fully parallelizable so should be able to get on NEON for a 2-3x speedup
Third I think it is nicer to have the feedback scale with the mix level. There is a pulsing effect that becomes audible on sustained notes and it is alleviated with lower feedback settings. I've left a comment with the calculation I used for testing and for me it produces a more usable range of depth settings
| grainRate = std::max<int32_t>(1, grainRate); | ||
| grainRate = (kSampleRate << 1) / grainRate; | ||
| // Preset 0=default | ||
| grainPitchType = (int8_t)(multiply_32x32_rshift32_rounded( |
There was a problem hiding this comment.
The goal here is to quantize to 5 levels? It could use a comment
| // Preset 0=default | ||
| grainPitchType = (int8_t)(multiply_32x32_rshift32_rounded( | ||
| unpatchedParams->getValue(Param::Unpatched::MOD_FX_FEEDBACK), 5)); | ||
| grainPitchType = std::max<int8_t>(-2, (std::min<int8_t>(2, grainPitchType))); |
| int32_t pos = | ||
| (grains[i].startPoint + delta + kModFXGrainBufferSize) & kModFXGrainBufferIndexMask; | ||
|
|
||
| grains_l = add_saturation( |
There was a problem hiding this comment.
Multiply and accumulate to save a cycle
There was a problem hiding this comment.
multiply_accumulate_32x32_rshift32_rounded (smmlar) does not seem to take saturation operations into account.
There was a problem hiding this comment.
It doesn't, but if this is overflowing then that should be fixed instead. The saturation in saturating arithmetic is hard clipping in audio terms and not really desirable
| & kModFXGrainBufferIndexMask; | ||
| grains[i].counter = 0; | ||
| grains[i].rev = (getRandom255() < 76); | ||
| if (grainPitchType == -2) { |
There was a problem hiding this comment.
Can you use a switch for readability?
| } | ||
| else if (grainPitchType == 2) { // temopo sync | ||
| grains[i].pitch = (getRandom255() < 25) ? 512 | ||
| : (getRandom255() < 153) ? 2048 |
There was a problem hiding this comment.
You can skip a call by storing the previous random value and comparing to 176 instead
|
|
||
| int32_t grains_l = 0; | ||
| int32_t grains_r = 0; | ||
| for (int32_t i = 0; i < 8; i++) { |
There was a problem hiding this comment.
You should be able to render this in a vector to speed it up. Not critical to merge but look at the oscillator render function for an example
| } | ||
| } | ||
| } | ||
| grains[i].volScale = (2147483647 / (grains[i].length >> 1)); |
There was a problem hiding this comment.
Stick this in the previous if statement, division is expensive and there's no point in calculating it if grain length is 0
| grainShift = 44 * 300; //(kSampleRate / 1000) * 300; | ||
| // Size | ||
| grainSize = 44 * ((((unpatchedParams->getValue(Param::Unpatched::MOD_FX_OFFSET) >> 1) + 1073741824) >> 21)); | ||
| grainSize = std::max<int32_t>(440, std::min<int32_t>(35280, grainSize)); //10ms - 800ms |
There was a problem hiding this comment.
std::Clamp instead (applies in a bunch of places)
| if (!grainInitialized && modFXGrainBufferWriteIndex >= 65536) { | ||
| grainInitialized = true; | ||
| } | ||
| *postFXVolume = multiply_32x32_rshift32(*postFXVolume, 1518500250) << 1; // Divide by sqrt(2) |
There was a problem hiding this comment.
define a constant for root 2 in fixedpoint.h (ONE_OVER_SQRT2_Q31) and use that
| (multiply_32x32_rshift32_rounded(multiply_32x32_rshift32_rounded(grainVol, grainVol), grainVol) << 2) | ||
| + 2147483648; //Cubic | ||
| grainVol = std::max<int32_t>(0, std::min<int32_t>(2147483647, grainVol)); | ||
| grainDryVol = |
There was a problem hiding this comment.
It would be nice to have feedback scale with the mix level - grainFeedbackVol = grainVol >> 3; works well in my experimentation. The current feedback makes a pulsing sound with sustained notes and I'd like to be able to have lower mixes without it
|
It also might be good to swap the feedback and depth pads |
- Fixing buffer dealloc timing - Using MAC - std::clamp - grainFeedbackVol = grainVol >> 3
Since the modulatable parameters are only Depth and Rate, I believe Depth would be suitable for controlling the volume of the grain. |
|
You can just make it modulatable by changing it from a Param::Unpatched to a Param::Global. Automating it works fine with the current modfx so modulating should be fine as well |
Thank you! However, the change seems to be static and permanent, and I don't want it to affect the entire system. Is there a way to dynamically change the patch type only when ModFxType is set to Grain? |
m-m-adams
left a comment
There was a problem hiding this comment.
Good to go, the lshift by 0 should be removed but it's optimized out anyway
| << 2, | ||
| grains_r); | ||
| grains_l = multiply_accumulate_32x32_rshift32_rounded( | ||
| grains_l, multiply_32x32_rshift32(modFXGrainBuffer[pos].l, vol) << 0, grains[i].panVolL); |
There was a problem hiding this comment.
<< 0 is a nop but it's optimized out anyway
|
No problem - it can always be made patched in the future |

Added Grain FX to Mod FX
https://youtu.be/9W70SZYMKXo
Parameters:
Mod Depth: Controls Grain Volume / Dry Wet Mix
Mod Offset: Adjusts Grain Size
Mod Rate: Sets Grain Rate
Mod Feedback: Selects Grain Type
Note:
This effect is somewhat resource-intensive, so please use only one instance per song.