-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
audiofilters: Add Distortion effect and implement LFO ticking #9776
base: main
Are you sure you want to change the base?
Conversation
…ule based on Godot's AudioEffectDistortion class.
I think we need to add more fluff for the full effects package as well.
|
I don't know exactly what you mean by "fluff", but the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, the added file(s) in this module need to be separately listed in the UNIX port (ports/unix/variant/coverage/mpconfigvariant.mk). This is the cause of the CI failures that prevent per-board builds from starting.
It would be nice if at least one basic test of the distortion functionality is added to tests/circuitpython so that this functionality is smoke tested during CI.
} | ||
|
||
// get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required | ||
mp_float_t drive = MIN(MAX(synthio_block_slot_get(&self->drive), 0.0), 1.0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as detailed in #9872 the distortion class needs to take responsibility for ticking the blocks it uses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to wait until we have a clear resolution for block ticking outside of synthio before including it within this PR. I will keep this conversation open in case that occurs before this PR is merge-able.
Thank you for the review, @jepler ! I'll dig in as soon as I get the chance. |
…oint calculations.
…CROPY_FLOAT_CONST, MP_ROM_INT, and synthio_block_slot_get_limited.
I've noticed a few areas within |
[copied from Discord] I don't know that these other objects need a
For the first case, having some object that is not a synthesizer/audio source at all might be the right solution. For the second case, I don't know if that is applicable to the e.g., an audio echo effect. (*) |
Thank you for the explanation. I don't think I properly understood the purpose of the
As for your note on the variable number of samples, I think that 256 samples is a generally ideal pace, but if the buffer size is not a factor of 256, it would cause uneven ticking of the blocks. Yet, using the entire buffer size may not be ideal either because the block inputs may not increment at a desirable pace. We may need a separate accumulator within |
@jepler I've taken some time to look into how the block ticking is actually handled, and I still don't think that the block system is ready to have this update to audioeffects classes.
In the example layout above, it would make sense to include
In either of the two examples above, the global block ticking variables (ie: I think the solution is to store these global variables locally on each object and add them as arguments to That all said, I still don't plan to include this fix here, and it should instead be addressed in a separate PR connected to #9872. |
This is why the rule exists that a block can only be associated with one audio source and associating it with more than one audio source is an undiagnosed error or undefined behavior, however you want to call it. Here's why (I think that) a single global tick is sufficient, given that rule: As a footnote, this also ignores the possibility that typedef struct synthio_block_base {
mp_obj_base_t base;
uint8_t last_tick;
mp_float_t value;
} synthio_block_base_t; |
so with two synthesizers, a block used in synth A will see Why does the |
@jepler I see what you mean and why the uneven I put in some work to separate those three global variables into a struct that is passed through to all necessary block functions: main...relic-se:circuitpython:local_block_ticking. I'm good to scrap this in favor of your approach. I've also changed my mind on the 256 sample increments, and I think I'll make the audioeffects just tick the block inputs based on the size of the buffer. It'll make for a much simpler update. |
@jepler I've updated I've tested the following script with and without the update and can confirm that it works now:
Same goes for this test with a
|
If you test the above examples with a large buffer_size on the effect, let's say 16384, you can clearly hear the stepping in the delay time of the output. Though it is very unlikely that a user would use a buffer_size of this amount, it does bring up a valid case for keeping lfo ticking to |
…AX_DUR` intervals.
I've moved the block value calls around in order to force
These changes will like cause merge conflicts with #9876 regarding |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple small comments on the code. I'll test this on hardware shortly. Sorry for the delay!
@@ -296,16 +289,24 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o | |||
} | |||
} | |||
|
|||
// tick all block inputs | |||
shared_bindings_synthio_lfo_tick(self->sample_rate, length / self->channel_count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this and line 306 be factored up out of the if/else block? The compiler probably does anyways.
mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); | ||
mp_float_t decay = synthio_block_slot_get_limited(&self->decay, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); | ||
|
||
uint32_t delay_ms = (uint32_t)synthio_block_slot_get(&self->delay_ms); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may want to check that delay >= 0. Never did in the original code (my bad) but a LFO could go negative and weird things happen.
mp_float_t mix = synthio_block_slot_get_limited(&self->mix, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)); | ||
|
||
// LOFI mode bit mask | ||
uint32_t word_mask = 0xFFFFFFFF ^ ((1 << (uint32_t)MICROPY_FLOAT_C_FUN(round)(drive * MICROPY_FLOAT_CONST(14.0))) - 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth this being done only if we know we are in LOFI mode?
New audio effects class,
audiofilters.Distortion
, to distort audio samples using one of the available modes available in theaudiofilters.DistortionMode
enum.Todo:
DistortionMode.CLIP
,DistortionMode.OVERDRIVE
andDistortionMode.WAVESHAPE
algorithms.Comments:
audiofilters.Filter
effect? (ie:filter.play(distortion.play(sample))
)pre_gain
andpost_gain
properties are currently measured in decibels which requires the addition of adb_to_linear
function to make those values linear. Would it be more ideal to forego decibels altogether and make those properties linear?Parameters, documentation copy and algorithms are mostly credited to Godot Engine under the MIT License.
Example Code:
Closes #9872.