add GranularPitchShift class#11121
Conversation
# Conflicts: # py/circuitpy_defns.mk
|
I disabled |
|
The or even |
|
Ooh, yeah using |
|
@relic-se Would you be willing to take a look at this? One question I have is whether having both |
relic-se
left a comment
There was a problem hiding this comment.
Only noticed one odd thing on my first visual review of the code. I'll have to run some audio tests for a subjective opinion and to make sure that everything functions properly.
| } | ||
|
|
||
| void common_hal_audiodelays_granular_pitch_shift_set_spread(audiodelays_granular_pitch_shift_obj_t *self, mp_float_t spread) { | ||
| if (spread < MICROPY_FLOAT_CONST(0.0)) { |
There was a problem hiding this comment.
I'll have to look around, but isn't there some form of "clamp" macro that would clean this up a bit? If not, maybe something like MIN(MAX(spread, MICROPY_FLOAT_CONST(0.0)), MICROPY_FLOAT_CONST(1.0)). Not necessary, but something to think about.
There was a problem hiding this comment.
There are helpers that validate a value is within a range and raise ValueError if it isn't. I'm not sure if there is one that clamps to the ends of the range instead of raising though.
Arguably this clamping logic is really argument validation and might be more appropriate on the shared-bindings side. I may refactor it over to there, if so I'll check for a relevant helper function, or use the MIN/MAX syntax that you suggested. I do think it's a bit nicer than the longer form if blocks.
There was a problem hiding this comment.
There are a bunch of validators that could be used, and if you don't see the exact one, you could add one in argcheck.c. I merged already, but another PR is fine.
|
@dhalbert In order to do a proper subjective assessment, I'll have to generate some dry and wet audio samples using both algorithms ( The two algorithms do appear similar in their functionality, but with multiple read points (grains) instead of the single circular read which the windowed algorithm uses in |
|
Here are the results of my testing. Codeimport time
from audiocore import WaveFile
from audiodelays import PitchShift, GranularPitchShift
from audiofilewriter import AudioFileWriter
from synthio import LFO
import adafruit_wave
INPUT = "/StreetChicken.wav"
# read wave duration
info = adafruit_wave.open(INPUT)
duration = info.getnframes() / info.getframerate()
info.close()
# open wave file as sample
wav = WaveFile(INPUT)
def write(effect_class, semitones: int, lfo: bool = False) -> None:
filename = "/sd/{:s}{:+d}{:s}.wav".format(
effect_class.__name__,
semitones,
"_lfo" if lfo else ""
)
effect = effect_class(
semitones=LFO(scale=semitones) if lfo else semitones,
mix=1.0,
channel_count=wav.channel_count,
sample_rate=wav.sample_rate,
)
effect.play(wav)
print("Writing to {} for {}s...".format(filename, duration))
start = time.monotonic()
with open(filename, "wb") as f:
writer = AudioFileWriter(f)
writer.play(effect)
time.sleep(duration)
writer.stop()
print("Complete!")
effect.stop()
effect.deinit()
write(PitchShift, -5)
write(PitchShift, 5)
write(PitchShift, 3, True)
write(GranularPitchShift, -5)
write(GranularPitchShift, 5)
write(GranularPitchShift, 3, True)OutputAll files have been converted from WAV to MP3 at 192kbps for compatibility with GitHub. PitchShift-5.mp3 GranularPitchShift-5.mp3 CommentsI am surprised by the outcome here, and I don't think that the result is as obvious as I would have anticipated. In fact, I think the new algorithm does better in some areas and the old algorithm does better in others. Issues with Testing
LFO (BlockInput)When reviewing the Output FrequencyI think I need to do some FFT analysis here to determine the "winner". Ideally, there would be a perfectly shifted frequency spectrum from the input to the output proportional to From my ear, I'd say that TransientsBoth effects have a tendency to either cause delay to some transients (ie: kick and snare hits). I don't think this is something that can be easily overcome without utilizing more advanced algorithms. ConclusionI don't think there is a clear winner. I would love some comment from @FoamyGuy on the matter as how best to utilize the new algorithm and demonstrate its advances over |
|
@relic-se So it sounds like it's not worthwhile to choose just one. That is OK with me. We can have both. The extra code size is not so large. |
|
@relic-se I am not really familiar with the audio theory behind the different algorithms so I could not say with much confidence which types of audio each are best suited for. The concepts were new to me, I didn't know how it would sound compared to the existing implementation before I started. I was given the quest to implement the granular synthesis shift algorithm by ladyada. In experimenting with it while developing, some of the biggest differences I noticed were with larger values for I do think it's worth having both options as well since they can create different sounding effects. |
This adds a new class
audiodelays.GranularPitchShift. It uses granular synthesis for pitch shifting, producing a an effect that sounds different from the existing PitchShift implementation, and offers thedensityandspreadlevers to further change the sound.I have tested mostly on Fruit Jam using variations of the synthio based example code included in the docstrings, as well as a more complex sampler project code that plays back recording samples at different pitches.