-
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
synthio: add 'Note' with arbitrary frequency and more #7933
Conversation
to convert notes in the MIDI 1-127 note scale to floating point Hz
This class allows much more expressive sound synthesis: * tremolo & vibrato * arbitrary frequency * different evelope & waveform per note * all properties dynamically settable from Python code
.. and account releasing notes at their sustain level until they're done. this ameliorates the effect where multiple releasing notes don't seem to actually be releasing, but stay at a constant volume.
and re-vamp overall envelope calculation again. Now, if you set a low overall attack level like 0.2 this avoids the "diminishing volume" effect when many notes sound at once. You need simply choose a maximum attack level that is appropriate for the max number of voices that will actually be played.
.. without changing the current note amplitude
this may fix a weird crash during shutdown
The internal flash cache wasn't being properly used, because `write_blocks` unconditionally performed the flash write. Fixing this so that the write's not done until `internal_flash_flush` fixes the problem in my test program with i2sout & synthio. as a future optimization, `flash_read_blocks` could learn to read out of the cache, but that's probably not super important.
Probably the separate |
per-note envelope, which I didn't test before pushing, 🤣 doesn't work 😓 |
for similar reasons as Envelope. The mandatory frequency argument can still be given as a positional argument.
the squawk-at-reload comes back if you try to use a |
.. and simplify the envelope advance logic by handling 'instant' values more intelligently.
I now believe per-note envelope, including dynamically modifying a per-note envelope, works. |
thanks Jeff for all of this! this is my first time trying out synthio so trying to catch up a bit. i'm discovering that i need to add a better speaker to my I2S breakout because i'm getting a lot of crackles but i'm using a wii classic controller and tying the values to the different properties. super fun. one thing i tried is this:
and got an error: i tried originally testing with the metro m7 but when i was uploading the UF2, the drive was disappearing so i could not get to CIRCUITPY. not sure if JP or Todd experienced the same? |
@BlitzCityDIY cracks and pops on speaker is familiar -- i get the best performance using this speaker https://www.adafruit.com/product/4445 with this amp https://www.adafruit.com/product/3006 On some projects I've given that amp 5V from a separate supply to make it happier. |
the traceback is due to use of >>> for i in range(hz_tones): print(i)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported types for __lt__: 'int', 'list'
>>> for i in hz_tones: print(i)
...
130
164
196 desktop python gives a slightly different error text that's maybe easier to understand:
since |
I've mostly been using other boards than the M7 for this, including the pygamer and now the prop feather. |
oh wow that's right, thanks Jeff. my tiredness is showing ha. i'll try to get some demo code written with the wii controller and post it up tomorrow 🙂 |
this has the side effect of making some notes more accurate, the new frequency= value in the test is closer to the true midi frequency of 830.609...Hz.
Now the vibrato 'units' are 1.0 = one octave, 1/12 = one semitone, 1/1200 = one cent. Before, the units were somewhat arbitrary and were not perceptually "symmetrical" around the base frequency. For vibrato_depth = 1/12 and base frequency of 440, before: pitch from 403.33 to 476.67Hz, not corresponding to any notes after: pitch from 415.30 to 466.16Hz, corresponding to G# and A#
This really improves the loudness of the output with multiple notes while being a nice simple algorithm to implement.
@todbot you'll appreciate this. before and after of building up a chord from 9 notes. The note envelope has an attack level of 1.0, sustain level of 0.8, and a sine waveform with a volume of 14700 While some clipping will occur, overall I think it's an improvement. In technical terms, it's a dynamic range compressor with a fixed threshold and a hard knee. https://en.wikipedia.org/wiki/Dynamic_range_compression The waveforms below are from the manual test program circuitpython-manual/synthio/note/code.py which can be run on a host computer and captured directly to a wave file, so the peaks are the actual data, not a recording. This wave sounds a bit clippy when played, but my real world example simply sounds louder than before. |
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.
Looks great! Thanks for the print/record follow up from last time too. Also interested to see if this helps RP2040 flash issues.
Here's the doc for the new class, which can be used where integer note numbers were used before:
Looking for feedback from @jedgarpark @todbot @BlitzCityDIY about this API.
I love how the tremolo & vibrato make the sounds of my 'personal chimes' project richer. However, I don't know if this is what the API should look like.
I also wonder if the
Envelope
properties should be repeated in this object, instead of this object containing an Envelope; unlike Envelope,Note
is designed so that its properties can be changed dynamically and the changes are reflected right away.I think it also fixes the "audio glitch at auto-reload" problem in rp2040, which is great if someone else can confirm it. it can still glitch if USB enumeration happens while playing audio, so there's still an 'audio while interrupts disabled/defferred' problem
Finally, it tries again to improve the situation with output levels. Now, the
sustain_level
is relative to theattack_level
, and setting anattack_level
below 1.0 can mean that the auto envelope adjustment may never have to come into effect; for example, if you're playing up to 5 notes thenattack_level=0.2
(i.e., 1/5) means no adjustment will ever happen. Of course, when you play just one note it's much less loud than before. And if you play 6 notes the adjustment down in volume will be smaller as a percentage of amplitude. But the point is, you know something about how many notes you want to play at once and can set it accordingly.From my play/test code, creating some note objects:
randomizing the vibrato and tremolo before striking the note:
Frequency sweeps can be accomplished by setting
note.frequency
from Python code in the forever-loop, there's not a built-in frequency start/stop/rate sweep. In my testing this seemed to be adequate, at least for simple programs.