-
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
Add loop_start and loop_end properties to synthio.Note for waveshaping and sampling capabilities. #8629
Conversation
…g and sampling capabilities.
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.
Thank you! This looks nice and simple and adds good functionality. I have two small comments that may help result in better docstrings, please take a look.
shared-bindings/synthio/Note.c
Outdated
//| loop_start: int | ||
//| """The index of where to begin looping waveform data. Must be greater than 0 and less than the total size of the waveform data.""" | ||
STATIC mp_obj_t synthio_note_get_loop_start(mp_obj_t self_in) { | ||
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in); | ||
return mp_obj_new_int(common_hal_synthio_note_get_loop_start(self)); |
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.
Can you clarify this docstring? I think it "must be greater than or equal to zero and less than ..."
shared-bindings/synthio/Note.c
Outdated
//| loop_end: int | ||
//| """The index of where to end looping waveform data. Must be greater than 0 or ``loop_start`` and less than the total size of the waveform data.""" |
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.
Can you clarify this docstring too? I think I'd add "If it's zero, or greater than or equal to the length of the waveform, the loop occurs at the end of the waveform", or something along those lines.
shared-module/synthio/Note.c
Outdated
} | ||
|
||
void common_hal_synthio_note_set_loop_start(synthio_note_obj_t *self, mp_int_t value_in) { | ||
mp_int_t val = mp_arg_validate_int_range(value_in, 0, 32767, MP_QSTR_loop_start); |
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 used 16384 as the maximum length of a waveform (which maybe should have been 16383 since the upper bound is inclusive); this should probably match, which means it would be good if there was a #define
for it in shared-module/synthio/__init__.h
.
I don't have any notes that show why I used 16384 instead of a higher value like 32767. I may have been concerned about overflow in arithmetic.
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.
Allowing a higher maximum size would definitely be a plus for sampling, but I vote we leave that for a separate PR. Creating the #define
will help make the process of changing that value in the future easier, though.
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.
Thank you! This looks like a nice bit of added functionality. I have a few small comments that I hope will lead to better docstrings, please take a look.
Hi @jepler! Thanks for chiming on this. I have no issue updating those docstrings (which were rushed initially) and creating that global #define, but after further thought, I want discuss some potential modifications first.
Notes on Testing:
|
I don't have a strong feeling about a pair of properties vs a single property that is a tuple. Here are some things to consider:
Creating a fresh tuple requires allocations, but so do many things in synthio (new envelope, new note being two obvious examples), so that's no reason to exclude it. Something else to consider: A memoryview() can be efficiently sliced, in that it slicing't create a copy of the underlying memory. So, assigning I don't mind that the behavior with I'm not particularly concerned about the performance; as you note, you didn't add anything to an inner loop. |
…form_loop_...', added 'ring_waveform_loop_...` parameters, and updated docstrings.
Let me know if you'd like to make any other adjustments, @jepler. Otherwise, I'm happy with the current iteration. |
ping @todbot @jedgarpark in case you have feedback |
I pushed some small tweaks to the documentation. |
This is a wonderful addition, thanks Cooper! I'll be loading this onto some boards shortly. |
Great job on the documentation, @jepler! I didn't want to get my hands too dirty on that one, but I think you nailed it. I'll try to put together a standalone gist soon to better demonstrate the use of synthio for sampling. Plus, I think the recent addition of |
Here's my quick sampler demonstration using the loop parameters of this build: code.py & video demonstration. |
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.
OK, let's ship it! Thanks again for your contribution.
shared-bindings/synthio/Note.c
Outdated
@@ -53,13 +57,17 @@ static const mp_arg_t note_properties[] = { | |||
//| frequency: float, | |||
//| panning: BlockInput = 0.0, | |||
//| waveform: Optional[ReadableBuffer] = None, | |||
//| waveform_loop_start: int = 0, | |||
//| waveform_loop_end: int = 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.
Now the default is not 0 anymore.
Lately, I have been experimenting with utilizing
synthio.Note
as a sampling engine. This is done by loading audio data into thenote.waveform
property and manipulating thenote.bend
in such a way that the frequency of the note object reflects the actual frequency of the audio sample data (view code).Currently, a limitation is that waveform data will always loop from 0 to buffer size - 1 (inclusive). By implementing simple
loop_start
andloop_end
properties and some basic boolean logic, you can modify the looping start and end point of the waveform data when filling the audio buffer.Additionally, when an envelope is triggered the accumulator will begin at 0 regardless of the loop settings and only start looping at
loop_start
once it's hit eitherloop_end
or the end of waveform data.Relevant Links
NOTE: This is my first attempt at a contribution to
adafruit/circuitpython
. Feel free to correct any improper formatting or language in the provided code updates.