Some checks are pending
CI / ruff (push) Waiting to run
CI / mypy (push) Waiting to run
CI / pytest (push) Waiting to run
CI / package (push) Waiting to run
CI / Install smoke (${{ matrix.label }}) (linux, ubuntu-latest) (push) Blocked by required conditions
CI / Install smoke (${{ matrix.label }}) (macos-arm64, macos-14) (push) Blocked by required conditions
199 lines
6.6 KiB
Python
199 lines
6.6 KiB
Python
import torch
|
|
|
|
from speech_to_speech.VAD.vad_iterator import VADIterator
|
|
|
|
|
|
class _FakeVADModel:
|
|
def __init__(self, probs: list[float]) -> None:
|
|
self._probs = iter(probs)
|
|
|
|
def reset_states(self) -> None:
|
|
pass
|
|
|
|
def __call__(self, x: torch.Tensor, sampling_rate: int) -> torch.Tensor:
|
|
return torch.tensor(next(self._probs), dtype=torch.float32)
|
|
|
|
|
|
def _finish_utterance(iterator: VADIterator, silence_chunk: torch.Tensor):
|
|
spoken_utterance = None
|
|
for _ in range(5):
|
|
spoken_utterance = iterator(silence_chunk)
|
|
if spoken_utterance is not None:
|
|
break
|
|
return spoken_utterance
|
|
|
|
|
|
def test_triggering_chunk_is_kept_in_buffer() -> None:
|
|
model = _FakeVADModel([0.9, 0.9, 0.1, 0.1, 0.1, 0.1, 0.1])
|
|
iterator = VADIterator(
|
|
model=model,
|
|
threshold=0.5,
|
|
sampling_rate=16000,
|
|
min_silence_duration_ms=100,
|
|
speech_pad_ms=0,
|
|
)
|
|
|
|
first_chunk = torch.ones(512)
|
|
second_chunk = torch.ones(512) * 2
|
|
silence_chunk = torch.zeros(512)
|
|
|
|
assert iterator(first_chunk) is None
|
|
assert iterator(second_chunk) is None
|
|
spoken_utterance = _finish_utterance(iterator, silence_chunk)
|
|
|
|
assert spoken_utterance is not None
|
|
assert len(spoken_utterance) == 7
|
|
assert torch.equal(spoken_utterance[0], first_chunk)
|
|
assert torch.equal(spoken_utterance[1], second_chunk)
|
|
assert all(torch.equal(chunk, silence_chunk) for chunk in spoken_utterance[2:])
|
|
|
|
|
|
def test_pre_speech_padding_is_prepended_to_final_utterance() -> None:
|
|
model = _FakeVADModel([0.1, 0.1, 0.9, 0.9, 0.1, 0.1, 0.1, 0.1, 0.1])
|
|
iterator = VADIterator(
|
|
model=model,
|
|
threshold=0.5,
|
|
sampling_rate=16000,
|
|
min_silence_duration_ms=100,
|
|
speech_pad_ms=64,
|
|
)
|
|
|
|
first_chunk = torch.ones(512)
|
|
second_chunk = torch.ones(512) * 2
|
|
third_chunk = torch.ones(512) * 3
|
|
fourth_chunk = torch.ones(512) * 4
|
|
silence_chunk = torch.zeros(512)
|
|
|
|
assert iterator(first_chunk) is None
|
|
assert iterator(second_chunk) is None
|
|
assert iterator(third_chunk) is None
|
|
assert iterator(fourth_chunk) is None
|
|
|
|
spoken_utterance = _finish_utterance(iterator, silence_chunk)
|
|
|
|
assert spoken_utterance is not None
|
|
assert len(spoken_utterance) == 9
|
|
assert torch.equal(spoken_utterance[0], first_chunk)
|
|
assert torch.equal(spoken_utterance[1], second_chunk)
|
|
assert torch.equal(spoken_utterance[2], third_chunk)
|
|
assert torch.equal(spoken_utterance[3], fourth_chunk)
|
|
assert all(torch.equal(chunk, silence_chunk) for chunk in spoken_utterance[4:])
|
|
|
|
|
|
def test_speech_buffer_keeps_prefix_out_of_active_speech_buffer() -> None:
|
|
model = _FakeVADModel([0.1, 0.1, 0.9])
|
|
iterator = VADIterator(
|
|
model=model,
|
|
threshold=0.5,
|
|
sampling_rate=16000,
|
|
min_silence_duration_ms=100,
|
|
speech_pad_ms=32,
|
|
)
|
|
|
|
older_chunk = torch.ones(512)
|
|
latest_pre_speech_chunk = torch.ones(512) * 2
|
|
triggering_chunk = torch.ones(512) * 3
|
|
|
|
assert iterator(older_chunk) is None
|
|
assert iterator(latest_pre_speech_chunk) is None
|
|
assert iterator(triggering_chunk) is None
|
|
|
|
assert len(iterator.buffer) == 1
|
|
assert torch.equal(iterator.buffer[0], triggering_chunk)
|
|
|
|
speech_buffer = iterator.speech_buffer()
|
|
assert len(speech_buffer) == 2
|
|
assert torch.equal(speech_buffer[0], latest_pre_speech_chunk)
|
|
assert torch.equal(speech_buffer[1], triggering_chunk)
|
|
|
|
|
|
def test_final_samples_are_kept_until_vad_declares_done() -> None:
|
|
model = _FakeVADModel([0.9, 0.9, 0.1, 0.1, 0.1, 0.1, 0.1])
|
|
iterator = VADIterator(
|
|
model=model,
|
|
threshold=0.5,
|
|
sampling_rate=16000,
|
|
min_silence_duration_ms=100,
|
|
speech_pad_ms=64,
|
|
)
|
|
|
|
first_chunk = torch.ones(512)
|
|
second_chunk = torch.ones(512) * 2
|
|
trailing_chunks = [torch.ones(512) * value for value in (10, 11, 12, 13, 14)]
|
|
|
|
assert iterator(first_chunk) is None
|
|
assert iterator(second_chunk) is None
|
|
|
|
spoken_utterance = None
|
|
for chunk in trailing_chunks:
|
|
spoken_utterance = iterator(chunk)
|
|
|
|
assert spoken_utterance is not None
|
|
assert len(spoken_utterance) == 7
|
|
assert torch.equal(spoken_utterance[0], first_chunk)
|
|
assert torch.equal(spoken_utterance[1], second_chunk)
|
|
assert torch.equal(spoken_utterance[2], trailing_chunks[0])
|
|
assert torch.equal(spoken_utterance[3], trailing_chunks[1])
|
|
assert torch.equal(spoken_utterance[4], trailing_chunks[2])
|
|
assert torch.equal(spoken_utterance[5], trailing_chunks[3])
|
|
assert torch.equal(spoken_utterance[6], trailing_chunks[4])
|
|
|
|
|
|
def test_brief_silence_is_preserved_when_speech_resumes() -> None:
|
|
model = _FakeVADModel([0.9, 0.1, 0.1, 0.9, 0.1, 0.1, 0.1, 0.1, 0.1])
|
|
iterator = VADIterator(
|
|
model=model,
|
|
threshold=0.5,
|
|
sampling_rate=16000,
|
|
min_silence_duration_ms=100,
|
|
speech_pad_ms=0,
|
|
)
|
|
|
|
first_chunk = torch.ones(512)
|
|
pause_chunks = [torch.ones(512) * value for value in (8, 9)]
|
|
resumed_chunk = torch.ones(512) * 2
|
|
ending_silence = torch.zeros(512)
|
|
|
|
assert iterator(first_chunk) is None
|
|
assert iterator(pause_chunks[0]) is None
|
|
assert iterator(pause_chunks[1]) is None
|
|
assert iterator(resumed_chunk) is None
|
|
|
|
spoken_utterance = _finish_utterance(iterator, ending_silence)
|
|
|
|
assert spoken_utterance is not None
|
|
assert len(spoken_utterance) == 9
|
|
assert torch.equal(spoken_utterance[0], first_chunk)
|
|
assert torch.equal(spoken_utterance[1], pause_chunks[0])
|
|
assert torch.equal(spoken_utterance[2], pause_chunks[1])
|
|
assert torch.equal(spoken_utterance[3], resumed_chunk)
|
|
assert all(torch.equal(chunk, ending_silence) for chunk in spoken_utterance[4:])
|
|
|
|
|
|
def test_active_speech_samples_include_hysteresis_band_and_exclude_trailing_silence() -> None:
|
|
model = _FakeVADModel([0.1, 0.9, 0.4, 0.1, 0.1, 0.1, 0.1, 0.1])
|
|
iterator = VADIterator(
|
|
model=model,
|
|
threshold=0.5,
|
|
sampling_rate=16000,
|
|
min_silence_duration_ms=100,
|
|
speech_pad_ms=512,
|
|
)
|
|
|
|
pre_speech_chunk = torch.ones(512)
|
|
speech_chunk = torch.ones(512) * 2
|
|
maintained_speech_chunk = torch.ones(512) * 3
|
|
silence_chunk = torch.zeros(512)
|
|
|
|
assert iterator(pre_speech_chunk) is None
|
|
assert iterator(speech_chunk) is None
|
|
assert iterator(maintained_speech_chunk) is None
|
|
assert iterator.active_speech_samples == 1024
|
|
|
|
spoken_utterance = _finish_utterance(iterator, silence_chunk)
|
|
|
|
assert spoken_utterance is not None
|
|
assert iterator.last_utterance_active_speech_samples == 1024
|
|
assert iterator.active_speech_samples == 0
|
|
assert len(spoken_utterance) > 2
|