Closed relic-se closed 1 week ago
Here's my initial test of this problem. It seems that everything is actually calculating out correctly. It may be that the max_size
property is too for the sample in question (examples/test.wav
) and it's sounding too short because of that. It could also be that the update routine needs to run very often or else it'll stop the note too late.
import asyncio
import audiobusio
import board
import math
import synthio
import time
from synthvoice.sample import Sample
synth = synthio.Synthesizer(sample_rate=44100)
voice = Sample(synth, looping=False, file="/test.wav")
audio = audiobusio.I2SOut(board.GP0, board.GP1, board.GP2)
audio.play(synth)
print("frames: {:d}, sample_rate: {:d}, source duration: {:f}".format(len(voice._note.waveform), voice._sample_rate, voice._source_duration))
print("fftfreq: {:f}, source tuning: {:f}".format(voice._root, voice._source_tune))
print("initial duration: {:f}".format(initial := voice.duration))
async def test(ratio: float) -> None:
voice.press(1) # Force triggers glide envelope
print("\ndesired ratio = {:f}".format(ratio))
voice.frequency = voice._root * ratio
await asyncio.sleep(1) # Wait for glide to finish (should be very fast)
print("frequency: {:f}, duration: {:f}, ratio: {:f}".format(voice.frequency, voice.duration, voice.duration / initial))
voice.release()
async def run_tests() -> None:
for i in range(2, -3, -1):
await test(math.pow(2, i))
async def update_voice() -> None:
while True:
voice.update()
await asyncio.sleep(0.001)
async def main() -> None:
await asyncio.gather(run_tests(), update_voice())
asyncio.run(main())
I also added a print statement within synthvoice.sample.Sample.update
to output the timing of the release
call.
Output:
frames: 4096, sample_rate: 11025, source duration: 0.371519
fftfreq: 124.301453, source tuning: -5.529207
initial duration: 0.104955
desired ratio = 4.000000
desired = 0.026239, actual = 0.027344
frequency: 2.000000, duration: 0.026239, ratio: 0.250000
desired ratio = 2.000000
desired = 0.052478, actual = 0.054688
frequency: 1.000000, duration: 0.052478, ratio: 0.500000
desired ratio = 1.000000
desired = 0.104955, actual = 0.105469
frequency: 0.000000, duration: 0.104955, ratio: 1.000000
desired ratio = 0.500000
desired = 0.209911, actual = 0.210938
frequency: -1.000000, duration: 0.209911, ratio: 2.000000
desired ratio = 0.250000
desired = 0.419822, actual = 0.421875
frequency: -2.000000, duration: 0.419822, ratio: 4.000000
The release call lags slightly behind. This could potentially be mediated with one of the following:
await asyncio.sleep(self.duration)
release
(ie: 1ms before)voice.release_time
as an offset. Any overflow would likely be cut out by the envelope.This PR resolves this issue: https://github.com/relic-se/CircuitPython_SynthVoice/pull/4
Using something like voice.release_time=0.01
helps smooth out sample playback.
The calculation within
Sample.duration
is incorrect and produces undesirable playback length whenSample.looping
is enabled.https://github.com/dcooperdalrymple/CircuitPython_SynthVoice/blob/0c3a7b7fb29a2b8385cfef551e38d269dee93eda/synthvoice/sample.py#L193-L203