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

audiofilters and block inputs #9872

Open
jepler opened this issue Dec 5, 2024 · 2 comments · May be fixed by #9776
Open

audiofilters and block inputs #9872

jepler opened this issue Dec 5, 2024 · 2 comments · May be fixed by #9776
Labels
Milestone

Comments

@jepler
Copy link
Member

jepler commented Dec 5, 2024

CircuitPython version

main

Code/REPL

# https://gist.github.com/relic-se/77e48df1920b20d3e50762cbc06d1bb9

Behavior

It depends on LFO & math block side effects that are unintended

Description

No response

Additional information

audiofilters should have their own independent LFOs and should properly "tick" them every 256 samples when generating output samples. LFOs and other BlockInputs should never be shared between different audio-generating objects.

What happens right now is:

  • audiomixer will generate 512 (?) output samples from the synthesizer
  • during this time the synthesizer will tick the LFOs twice
  • audiomixer will generate 512 (?) output samples from the audiofilter
  • during this time the LFOs will tick zero times

so while this sort-of works, the LFO's effect on the filter is changing less frequently than it should, though at the same overall rate.

If the audiofilter correctly ticked its block inputs, but they were shared with the synthesizer, then it would advance at double the rate, ticked twice for synthesizer and twice for audiofilter; each user of the LFO would see different values and the values would be "jumpy".

There seems to be no way to force the "update LFOs every 256 samples" behavior through the whole stack; for instance imagine you have an audiomixer that goes into another audiomixer just so you can control audio levels of some channels in unison. Each audiomixer wants to produce some number of samples, not necessarily 256. And in fact a big point of audiomixer is to "re-block" audio output and reduce underruns.

This all feels kinda shabby and it's my design and my fault... but we need to do the best with it we can, and it feels like audiofilter needs to "do better", by properly ticking the block inputs that it uses. (by factoring some code out of synthio to be shared between it and audiofilters! .. and probably by forcing audiofilters to always work in SYNTHIO_MAX_DUR samples between ticks, or else making the new factored out layer work in scaled seconds instead of samples and reciprocal sample rate)

Sadly, this will end up incompatibly changing code that relied on the illegal side effect of using the synthesizer to tick them.

I would love a better solution if someone has one.

@jepler jepler added the bug label Dec 5, 2024
@jepler
Copy link
Member Author

jepler commented Dec 5, 2024

@relic-se @gamblor21

@jepler jepler added this to the 9.x.x milestone Dec 5, 2024
@relic-se
Copy link

relic-se commented Dec 5, 2024

@jepler Properly fixing this situation would require a rewrite of the audiosample API to always fill buffers in 256 sample segments and process BlockInput objects globally in between these segments. This is possible, but I wouldn't call it ideal and it would definitely be a breaking-change to existing code. Buffer sizes would also need to be validated to ensure that they fit this scheme.

It still may not be the best solution, but adding a blocks property to all modules which use BlockInput and leaving it up to the user to decide where BlockInput objects should be allocated should resolve this issue. Each module can then process the blocks they've been designated every 256 samples in the same manner as synthio.Synthesizer (refactoring as needed). This would allow existing code using these features to continue to function with the side effects you've identified, but present the opportunity to fix these side effects with proper implementation.

In some cases, it may be desirable to share an LFO object between audiosample objects which implement BlockInput and the side effect of one object stepping BlockInput objects at appropriate intervals and the other only between buffer fills may be an issue worth ignoring in these edge cases. I for one haven't noticed this error by ear up to this point.

@jepler jepler linked a pull request Dec 5, 2024 that will close this issue
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants