amanetize commited on
Commit
dc98a34
·
verified ·
1 Parent(s): 8377598

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +28 -0
  2. README.md +26 -7
  3. app.py +23 -0
  4. backend/__init__.py +0 -0
  5. backend/audio_utils.py +13 -0
  6. backend/capabilities.py +64 -0
  7. backend/easy_turn.py +7 -0
  8. backend/encoders.py +79 -0
  9. backend/fusion.py +21 -0
  10. backend/gate.py +67 -0
  11. backend/heads.py +56 -0
  12. backend/orchestrator.py +117 -0
  13. backend/pooling.py +26 -0
  14. backend/presets.py +46 -0
  15. backend/semantic.py +97 -0
  16. backend/smart_turn_onnx.py +50 -0
  17. backend/streaming_semantic.py +59 -0
  18. backend/types.py +65 -0
  19. config.py +33 -0
  20. database/audio/sample_clips/english_fleurs_1511.wav +3 -0
  21. database/audio/sample_clips/english_fleurs_1512.wav +3 -0
  22. database/audio/sample_clips/english_fleurs_1545.wav +3 -0
  23. database/audio/sample_clips/english_fleurs_1546.wav +3 -0
  24. database/audio/sample_clips/english_fleurs_1554.wav +0 -0
  25. database/audio/sample_clips/english_fleurs_1578.wav +3 -0
  26. database/audio/sample_clips/english_fleurs_1595.wav +3 -0
  27. database/audio/sample_clips/english_fleurs_1637.wav +3 -0
  28. database/audio/sample_clips/english_fleurs_1639.wav +3 -0
  29. database/audio/sample_clips/english_fleurs_1645.wav +3 -0
  30. database/audio/sample_clips/hindi_fleurs_1524.wav +3 -0
  31. database/audio/sample_clips/hindi_fleurs_1526.wav +3 -0
  32. database/audio/sample_clips/hindi_fleurs_1540.wav +3 -0
  33. database/audio/sample_clips/hindi_fleurs_1549.wav +3 -0
  34. database/audio/sample_clips/hindi_fleurs_1560.wav +3 -0
  35. database/audio/sample_clips/hindi_fleurs_1581.wav +3 -0
  36. database/audio/sample_clips/hindi_fleurs_1609.wav +3 -0
  37. database/audio/sample_clips/hindi_fleurs_1620.wav +3 -0
  38. database/audio/sample_clips/hindi_fleurs_1641.wav +3 -0
  39. database/audio/sample_clips/hindi_fleurs_1645.wav +3 -0
  40. database/audio/sample_clips/hinglish_hiacc_AD09072.wav +3 -0
  41. database/audio/sample_clips/hinglish_hiacc_AD13003.wav +0 -0
  42. database/audio/sample_clips/hinglish_hiacc_AD22092.wav +3 -0
  43. database/audio/sample_clips/hinglish_hiacc_AD23025.wav +3 -0
  44. database/audio/sample_clips/hinglish_hiacc_AD36007.wav +3 -0
  45. database/audio/sample_clips/hinglish_hiacc_AD36064.wav +3 -0
  46. database/audio/sample_clips/hinglish_hiacc_AD40049.wav +3 -0
  47. database/audio/sample_clips/hinglish_hiacc_AD40103.wav +3 -0
  48. database/audio/sample_clips/hinglish_hiacc_AD40158.wav +3 -0
  49. database/audio/sample_clips/hinglish_hiacc_AD60096.wav +3 -0
  50. database/audio/sample_clips/manifest.json +232 -0
.gitattributes CHANGED
@@ -33,3 +33,31 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ database/audio/sample_clips/english_fleurs_1511.wav filter=lfs diff=lfs merge=lfs -text
37
+ database/audio/sample_clips/english_fleurs_1512.wav filter=lfs diff=lfs merge=lfs -text
38
+ database/audio/sample_clips/english_fleurs_1545.wav filter=lfs diff=lfs merge=lfs -text
39
+ database/audio/sample_clips/english_fleurs_1546.wav filter=lfs diff=lfs merge=lfs -text
40
+ database/audio/sample_clips/english_fleurs_1578.wav filter=lfs diff=lfs merge=lfs -text
41
+ database/audio/sample_clips/english_fleurs_1595.wav filter=lfs diff=lfs merge=lfs -text
42
+ database/audio/sample_clips/english_fleurs_1637.wav filter=lfs diff=lfs merge=lfs -text
43
+ database/audio/sample_clips/english_fleurs_1639.wav filter=lfs diff=lfs merge=lfs -text
44
+ database/audio/sample_clips/english_fleurs_1645.wav filter=lfs diff=lfs merge=lfs -text
45
+ database/audio/sample_clips/hindi_fleurs_1524.wav filter=lfs diff=lfs merge=lfs -text
46
+ database/audio/sample_clips/hindi_fleurs_1526.wav filter=lfs diff=lfs merge=lfs -text
47
+ database/audio/sample_clips/hindi_fleurs_1540.wav filter=lfs diff=lfs merge=lfs -text
48
+ database/audio/sample_clips/hindi_fleurs_1549.wav filter=lfs diff=lfs merge=lfs -text
49
+ database/audio/sample_clips/hindi_fleurs_1560.wav filter=lfs diff=lfs merge=lfs -text
50
+ database/audio/sample_clips/hindi_fleurs_1581.wav filter=lfs diff=lfs merge=lfs -text
51
+ database/audio/sample_clips/hindi_fleurs_1609.wav filter=lfs diff=lfs merge=lfs -text
52
+ database/audio/sample_clips/hindi_fleurs_1620.wav filter=lfs diff=lfs merge=lfs -text
53
+ database/audio/sample_clips/hindi_fleurs_1641.wav filter=lfs diff=lfs merge=lfs -text
54
+ database/audio/sample_clips/hindi_fleurs_1645.wav filter=lfs diff=lfs merge=lfs -text
55
+ database/audio/sample_clips/hinglish_hiacc_AD09072.wav filter=lfs diff=lfs merge=lfs -text
56
+ database/audio/sample_clips/hinglish_hiacc_AD22092.wav filter=lfs diff=lfs merge=lfs -text
57
+ database/audio/sample_clips/hinglish_hiacc_AD23025.wav filter=lfs diff=lfs merge=lfs -text
58
+ database/audio/sample_clips/hinglish_hiacc_AD36007.wav filter=lfs diff=lfs merge=lfs -text
59
+ database/audio/sample_clips/hinglish_hiacc_AD36064.wav filter=lfs diff=lfs merge=lfs -text
60
+ database/audio/sample_clips/hinglish_hiacc_AD40049.wav filter=lfs diff=lfs merge=lfs -text
61
+ database/audio/sample_clips/hinglish_hiacc_AD40103.wav filter=lfs diff=lfs merge=lfs -text
62
+ database/audio/sample_clips/hinglish_hiacc_AD40158.wav filter=lfs diff=lfs merge=lfs -text
63
+ database/audio/sample_clips/hinglish_hiacc_AD60096.wav filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,13 +1,32 @@
1
  ---
2
- title: Turn Detection
3
- emoji: 🌖
4
- colorFrom: yellow
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 6.26.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Turn Detection Live Dashboard
3
+ emoji: 🎙️
4
+ colorFrom: blue
5
+ colorTo: yellow
6
  sdk: gradio
7
+ sdk_version: 6.25.0
 
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
  ---
12
 
13
+ # Turn Detection Live Dashboard
14
+
15
+ A real-time dashboard for audio turn detection: deciding whether a speaker is
16
+ finished talking or just pausing. Speak into the mic or replay a bundled clip
17
+ and watch several models score the decision live, plotted against the
18
+ waveform.
19
+
20
+ ## Models
21
+ Smart Turn v3.2 (zero-shot), Whisper with trained heads, a Qwen-based semantic
22
+ judge, the LiveKit end-of-turn detector, acoustic + semantic fusion, and a
23
+ streaming semantic branch.
24
+
25
+ ## How to use
26
+ 1. Select one or more models.
27
+ 2. Record from the mic, or replay a sample clip / upload a recording.
28
+ 3. Each model's "turn complete" probability is drawn against the waveform; the
29
+ dashed line is the decision threshold.
30
+
31
+ Trained head checkpoints and demo clips are bundled. Other model weights
32
+ download on first use.
app.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import traceback
2
+ import numpy as np
3
+ from backend.orchestrator import run_pipeline
4
+ from backend.presets import list_presets
5
+ from frontend.layout import build_app
6
+
7
+ def _warmup() -> None:
8
+ sr = 16000
9
+ dummy = np.zeros(sr, dtype=np.float32)
10
+ for preset in list_presets():
11
+ if not preset.get('available'):
12
+ continue
13
+ try:
14
+ run_pipeline(preset['config'], dummy, sample_rate=sr, preset_label=preset['label'])
15
+ print(f"[warmup] ready: {preset['label']}")
16
+ except Exception as exc:
17
+ print(f"[warmup] skipped {preset['label']}: {exc}")
18
+ traceback.print_exc()
19
+ print('[warmup] done')
20
+ if __name__ == '__main__':
21
+ _warmup()
22
+ demo = build_app()
23
+ demo.launch(share=False)
backend/__init__.py ADDED
File without changes
backend/audio_utils.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import numpy as np
3
+ import config
4
+
5
+ def truncate_or_left_pad(audio: np.ndarray, n_seconds: int=config.SMART_TURN_WINDOW_SECONDS, sample_rate: int=config.SAMPLE_RATE) -> np.ndarray:
6
+ audio = np.asarray(audio, dtype=np.float32)
7
+ max_samples = n_seconds * sample_rate
8
+ if len(audio) > max_samples:
9
+ return audio[-max_samples:]
10
+ if len(audio) < max_samples:
11
+ padding = max_samples - len(audio)
12
+ return np.pad(audio, (padding, 0), mode='constant', constant_values=0)
13
+ return audio
backend/capabilities.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import json
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+ import config
6
+ STATIC_CAPABILITIES: dict[str, dict] = {'gate.webrtcvad': {'available': True, 'reason': None, 'provenance': 'real_checkpoint'}, 'gate.silero_vad': {'available': True, 'reason': None, 'provenance': 'real_checkpoint'}, 'gate.none': {'available': True, 'reason': None, 'provenance': 'rule'}, 'acoustic.smart_turn_onnx': {'available': True, 'reason': None, 'provenance': 'real_checkpoint'}, 'semantic.qwen_local': {'available': True, 'reason': None, 'provenance': 'real_checkpoint'}, 'semantic.livekit_eou': {'available': True, 'reason': None, 'provenance': 'real_checkpoint'}, 'semantic.qwen_local_streaming': {'available': True, 'reason': None, 'provenance': 'architecture_reimplemented'}, 'semantic.groq_api': {'available': False, 'reason': 'no API key configured - set GROQ_API_KEY to enable (see docs/decision-log.md #17)', 'provenance': 'unavailable'}, 'semantic.openrouter_api': {'available': False, 'reason': 'no API key configured - set OPENROUTER_API_KEY to enable (see docs/decision-log.md #17)', 'provenance': 'unavailable'}, 'fusion.weighted_vote': {'available': True, 'reason': None, 'provenance': 'rule'}, 'fusion.easy_turn': {'available': False, 'reason': "Easy Turn's linguistic branch needs its own ASR component we haven't wired yet - checkpoint downloaded for reference only (see docs/decision-log.md #19)", 'provenance': 'unavailable'}, 'mode.full_duplex_bypass.moshi': {'available': False, 'reason': 'needs GPU VRAM not available on this machine; API routing deferred (see docs/decision-log.md #6, #17)', 'provenance': 'unavailable'}, 'mode.full_duplex_bypass.human1': {'available': False, 'reason': 'needs GPU VRAM not available on this machine; API routing deferred (see docs/decision-log.md #6, #17)', 'provenance': 'unavailable'}}
7
+
8
+ @dataclass
9
+ class CapabilityInfo:
10
+ key: str
11
+ available: bool
12
+ reason: Optional[str]
13
+ provenance: Optional[str]
14
+
15
+ def _head_checkpoint_path(encoder: str, pooling: str, head: str) -> tuple:
16
+ stem = f'{encoder}_{pooling}_{head}'
17
+ ckpt = config.CHECKPOINTS_DIR / f'{stem}.pt'
18
+ meta = config.CHECKPOINTS_DIR / f'{stem}.metadata.json'
19
+ return (ckpt, meta)
20
+
21
+ def head_capability_key(encoder: str, pooling: str, head: str) -> str:
22
+ return f'acoustic.head.{encoder}.{pooling}.{head}'
23
+
24
+ def _check_trained_head(encoder: str, pooling: str, head: str) -> CapabilityInfo:
25
+ key = head_capability_key(encoder, pooling, head)
26
+ ckpt, meta = _head_checkpoint_path(encoder, pooling, head)
27
+ if not (ckpt.exists() and meta.exists()):
28
+ return CapabilityInfo(key=key, available=False, reason=f'not trained yet - run experiments/train_head.py to produce database/checkpoints/{encoder}_{pooling}_{head}.pt', provenance='unavailable')
29
+ try:
30
+ metadata = json.loads(meta.read_text())
31
+ except (OSError, json.JSONDecodeError) as exc:
32
+ return CapabilityInfo(key=key, available=False, reason=f'checkpoint metadata unreadable ({exc}) - retrain via experiments/train_head.py', provenance='unavailable')
33
+ if metadata.get('encoder') != encoder or metadata.get('pooling') != pooling or metadata.get('head') != head:
34
+ return CapabilityInfo(key=key, available=False, reason='checkpoint metadata does not match the requested (encoder, pooling, head) combination', provenance='unavailable')
35
+ return CapabilityInfo(key=key, available=True, reason=None, provenance='trained_by_us')
36
+
37
+ def get(key: str) -> CapabilityInfo:
38
+ if key.startswith('acoustic.head.'):
39
+ _, _, encoder, pooling, head = key.split('.')
40
+ return _check_trained_head(encoder, pooling, head)
41
+ if key in STATIC_CAPABILITIES:
42
+ entry = STATIC_CAPABILITIES[key]
43
+ return CapabilityInfo(key=key, **entry)
44
+ raise KeyError(f'unknown capability key: {key!r}')
45
+
46
+ def is_available(key: str) -> bool:
47
+ return get(key).available
48
+
49
+ def list_trained_heads() -> list[CapabilityInfo]:
50
+ found = []
51
+ if not config.CHECKPOINTS_DIR.exists():
52
+ return found
53
+ for meta_path in sorted(config.CHECKPOINTS_DIR.glob('*.metadata.json')):
54
+ try:
55
+ metadata = json.loads(meta_path.read_text())
56
+ except (OSError, json.JSONDecodeError):
57
+ continue
58
+ encoder, pooling, head = (metadata.get('encoder'), metadata.get('pooling'), metadata.get('head'))
59
+ if not all([encoder, pooling, head]):
60
+ continue
61
+ info = _check_trained_head(encoder, pooling, head)
62
+ if info.available:
63
+ found.append(info)
64
+ return found
backend/easy_turn.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from backend.capabilities import get
3
+ from backend.types import StageResult
4
+
5
+ def run(*_args, **_kwargs) -> StageResult:
6
+ info = get('fusion.easy_turn')
7
+ return StageResult(stage='fusion.easy_turn', timing_ms=0.0, output=None, available=False, reason=info.reason, provenance='unavailable')
backend/encoders.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import math
3
+ import time
4
+ from typing import Literal
5
+ import numpy as np
6
+ import torch
7
+ import config
8
+ from backend.audio_utils import truncate_or_left_pad
9
+ from backend.types import StageResult
10
+ EncoderName = Literal['whisper_tiny', 'whisper_base', 'wav2vec2']
11
+ ENCODER_WINDOW_SECONDS = {'whisper_tiny': 30, 'whisper_base': 30, 'wav2vec2': config.SMART_TURN_WINDOW_SECONDS}
12
+ _ENCODER_OUTPUT_HOP_SAMPLES = 320
13
+ _device = 'mps' if torch.backends.mps.is_available() else 'cpu'
14
+ _whisper_models: dict[str, tuple] = {}
15
+ _wav2vec2_model = None
16
+ _wav2vec2_processor = None
17
+
18
+ def _get_whisper(model_id: str):
19
+ if model_id not in _whisper_models:
20
+ from transformers import WhisperFeatureExtractor, WhisperModel
21
+ feature_extractor = WhisperFeatureExtractor(chunk_length=30)
22
+ model = WhisperModel.from_pretrained(model_id).to(_device).eval()
23
+ _whisper_models[model_id] = (feature_extractor, model)
24
+ return _whisper_models[model_id]
25
+
26
+ def _get_wav2vec2():
27
+ global _wav2vec2_model, _wav2vec2_processor
28
+ if _wav2vec2_model is None:
29
+ from transformers import Wav2Vec2FeatureExtractor, Wav2Vec2Model
30
+ _wav2vec2_processor = Wav2Vec2FeatureExtractor.from_pretrained(config.WAV2VEC2_ID)
31
+ _wav2vec2_model = Wav2Vec2Model.from_pretrained(config.WAV2VEC2_ID).to(_device).eval()
32
+ return (_wav2vec2_processor, _wav2vec2_model)
33
+
34
+ def _valid_output_frames(original_num_samples: int, window_seconds: int, sample_rate: int) -> int:
35
+ real_samples = min(original_num_samples, window_seconds * sample_rate)
36
+ return max(1, math.ceil(real_samples / _ENCODER_OUTPUT_HOP_SAMPLES))
37
+
38
+ @torch.inference_mode()
39
+ def _encode_whisper(model_id: str, audio: np.ndarray, sample_rate: int) -> tuple[np.ndarray, int]:
40
+ feature_extractor, model = _get_whisper(model_id)
41
+ window_seconds = ENCODER_WINDOW_SECONDS['whisper_tiny']
42
+ window_samples = window_seconds * sample_rate
43
+ valid_frames = _valid_output_frames(len(audio), window_seconds, sample_rate)
44
+ padded = truncate_or_left_pad(audio, window_seconds, sample_rate)
45
+ inputs = feature_extractor(padded, sampling_rate=sample_rate, return_tensors='pt', padding='max_length', max_length=window_samples, truncation=True, do_normalize=True)
46
+ input_features = inputs.input_features.to(_device)
47
+ encoder = model.get_encoder()
48
+ hidden_states = encoder(input_features).last_hidden_state
49
+ hidden_states = hidden_states.squeeze(0).float().cpu().numpy()
50
+ valid_frames = min(valid_frames, hidden_states.shape[0])
51
+ return (hidden_states, valid_frames)
52
+
53
+ @torch.inference_mode()
54
+ def _encode_wav2vec2(audio: np.ndarray, sample_rate: int) -> tuple[np.ndarray, int]:
55
+ processor, model = _get_wav2vec2()
56
+ window_seconds = ENCODER_WINDOW_SECONDS['wav2vec2']
57
+ valid_frames = _valid_output_frames(len(audio), window_seconds, sample_rate)
58
+ padded = truncate_or_left_pad(audio, window_seconds, sample_rate)
59
+ inputs = processor(padded, sampling_rate=sample_rate, return_tensors='pt')
60
+ input_values = inputs.input_values.to(_device)
61
+ hidden_states = model(input_values).last_hidden_state
62
+ hidden_states = hidden_states.squeeze(0).float().cpu().numpy()
63
+ valid_frames = min(valid_frames, hidden_states.shape[0])
64
+ return (hidden_states, valid_frames)
65
+
66
+ def extract_hidden_states(encoder: EncoderName, audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE) -> tuple[np.ndarray, int]:
67
+ if encoder == 'whisper_tiny':
68
+ return _encode_whisper(config.WHISPER_TINY_ID, audio, sample_rate)
69
+ if encoder == 'whisper_base':
70
+ return _encode_whisper(config.WHISPER_BASE_ID, audio, sample_rate)
71
+ if encoder == 'wav2vec2':
72
+ return _encode_wav2vec2(audio, sample_rate)
73
+ raise ValueError(f'unknown encoder: {encoder!r}')
74
+
75
+ def run(encoder: EncoderName, audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE) -> StageResult:
76
+ start = time.perf_counter()
77
+ hidden_states, valid_length = extract_hidden_states(encoder, audio, sample_rate)
78
+ timing_ms = (time.perf_counter() - start) * 1000
79
+ return StageResult(stage=f'encoder.{encoder}', timing_ms=timing_ms, output={'hidden_states': hidden_states, 'valid_length': valid_length}, available=True, provenance='real_checkpoint')
backend/fusion.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import time
3
+ from typing import Optional
4
+ from backend.types import StageResult
5
+
6
+ def weighted_vote(acoustic_probability: float, semantic_verdict: Optional[str]=None, semantic_probability: Optional[float]=None, acoustic_weight: float=0.6) -> dict:
7
+ if semantic_verdict == 'wait':
8
+ return {'decision': 'incomplete', 'probability': min(acoustic_probability, 0.2), 'rule': "semantic 'wait' verdict overrides acoustic signal"}
9
+ if semantic_probability is not None:
10
+ semantic_score = semantic_probability
11
+ else:
12
+ semantic_score = 1.0 if semantic_verdict == 'complete' else 0.0
13
+ fused_probability = acoustic_weight * acoustic_probability + (1 - acoustic_weight) * semantic_score
14
+ decision = 'complete' if fused_probability > 0.5 else 'incomplete'
15
+ return {'decision': decision, 'probability': fused_probability, 'rule': f'weighted_vote(acoustic_weight={acoustic_weight})'}
16
+
17
+ def run(acoustic_probability: float, semantic_verdict: Optional[str]=None, semantic_probability: Optional[float]=None, acoustic_weight: float=0.6) -> StageResult:
18
+ start = time.perf_counter()
19
+ result = weighted_vote(acoustic_probability, semantic_verdict, semantic_probability, acoustic_weight)
20
+ timing_ms = (time.perf_counter() - start) * 1000
21
+ return StageResult(stage='fusion.weighted_vote', timing_ms=timing_ms, output=result, available=True, provenance='rule')
backend/gate.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import time
3
+ import numpy as np
4
+ import webrtcvad
5
+ import config
6
+ from backend.types import StageResult
7
+ _silero_model = None
8
+ _silero_utils = None
9
+
10
+ def _get_silero():
11
+ global _silero_model, _silero_utils
12
+ if _silero_model is None:
13
+ import torch
14
+ _silero_model, _silero_utils = torch.hub.load(repo_or_dir='snakers4/silero-vad', model='silero_vad', trust_repo=True)
15
+ return (_silero_model, _silero_utils)
16
+
17
+ def _float_audio(audio: np.ndarray) -> np.ndarray:
18
+ audio = np.asarray(audio)
19
+ if audio.dtype.kind == 'i':
20
+ return audio.astype(np.float32) / 32768.0
21
+ return audio.astype(np.float32)
22
+
23
+ def _pcm16_bytes(audio_f32: np.ndarray) -> bytes:
24
+ clipped = np.clip(audio_f32, -1.0, 1.0)
25
+ return (clipped * 32767.0).astype(np.int16).tobytes()
26
+
27
+ def run_webrtcvad(audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE, aggressiveness: int=2) -> StageResult:
28
+ start = time.perf_counter()
29
+ audio_f32 = _float_audio(audio)
30
+ pcm = _pcm16_bytes(audio_f32)
31
+ frame_ms = 30
32
+ frame_bytes = int(sample_rate * (frame_ms / 1000.0)) * 2
33
+ vad = webrtcvad.Vad(aggressiveness)
34
+ speech_frames = 0
35
+ total_frames = 0
36
+ for offset in range(0, len(pcm) - frame_bytes + 1, frame_bytes):
37
+ frame = pcm[offset:offset + frame_bytes]
38
+ total_frames += 1
39
+ if vad.is_speech(frame, sample_rate):
40
+ speech_frames += 1
41
+ speech_detected = total_frames > 0 and speech_frames / total_frames > 0.1
42
+ timing_ms = (time.perf_counter() - start) * 1000
43
+ return StageResult(stage='gate.webrtcvad', timing_ms=timing_ms, output={'speech_detected': speech_detected, 'speech_frame_ratio': speech_frames / total_frames if total_frames else 0.0}, available=True, provenance='real_checkpoint')
44
+
45
+ def run_silero(audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE, threshold: float=0.5) -> StageResult:
46
+ import torch
47
+ start = time.perf_counter()
48
+ model, utils = _get_silero()
49
+ get_speech_timestamps = utils[0]
50
+ audio_f32 = _float_audio(audio)
51
+ tensor = torch.from_numpy(audio_f32)
52
+ timestamps = get_speech_timestamps(tensor, model, sampling_rate=sample_rate, threshold=threshold)
53
+ speech_detected = len(timestamps) > 0
54
+ timing_ms = (time.perf_counter() - start) * 1000
55
+ return StageResult(stage='gate.silero_vad', timing_ms=timing_ms, output={'speech_detected': speech_detected, 'speech_segments': timestamps}, available=True, provenance='real_checkpoint')
56
+
57
+ def run_none(audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE) -> StageResult:
58
+ return StageResult(stage='gate.none', timing_ms=0.0, output={'speech_detected': True}, available=True, provenance='rule')
59
+
60
+ def run(gate: str, audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE, **kwargs) -> StageResult:
61
+ if gate == 'webrtcvad':
62
+ return run_webrtcvad(audio, sample_rate, aggressiveness=kwargs.get('vad_aggressiveness', 2))
63
+ if gate == 'silero_vad':
64
+ return run_silero(audio, sample_rate)
65
+ if gate == 'none':
66
+ return run_none(audio, sample_rate)
67
+ raise ValueError(f'unknown gate: {gate!r}')
backend/heads.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import json
3
+ from pathlib import Path
4
+ from typing import Optional
5
+ import torch
6
+ import torch.nn as nn
7
+ import config
8
+
9
+ class LinearHead(nn.Module):
10
+
11
+ def __init__(self, input_dim: int):
12
+ super().__init__()
13
+ self.linear = nn.Linear(input_dim, 1)
14
+
15
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
16
+ return self.linear(x).squeeze(-1)
17
+
18
+ class MLPHead(nn.Module):
19
+
20
+ def __init__(self, input_dim: int, hidden_dim: int=128):
21
+ super().__init__()
22
+ self.net = nn.Sequential(nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, 1))
23
+
24
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
25
+ return self.net(x).squeeze(-1)
26
+
27
+ def build_head(head_type: str, input_dim: int) -> nn.Module:
28
+ if head_type == 'linear':
29
+ return LinearHead(input_dim)
30
+ if head_type == 'mlp':
31
+ return MLPHead(input_dim)
32
+ raise ValueError(f'unknown head_type: {head_type!r}')
33
+
34
+ def checkpoint_paths(encoder: str, pooling: str, head: str) -> tuple[Path, Path]:
35
+ stem = f'{encoder}_{pooling}_{head}'
36
+ return (config.CHECKPOINTS_DIR / f'{stem}.pt', config.CHECKPOINTS_DIR / f'{stem}.metadata.json')
37
+
38
+ def save(encoder: str, pooling: str, head_type: str, input_dim: int, head_state_dict: dict, metrics: dict, pooling_state_dict: Optional[dict]=None, trained_on: Optional[str]=None) -> None:
39
+ config.CHECKPOINTS_DIR.mkdir(parents=True, exist_ok=True)
40
+ ckpt_path, meta_path = checkpoint_paths(encoder, pooling, head_type)
41
+ torch.save({'head_state_dict': head_state_dict, 'pooling_state_dict': pooling_state_dict, 'input_dim': input_dim}, ckpt_path)
42
+ metadata = {'encoder': encoder, 'pooling': pooling, 'head': head_type, 'input_dim': input_dim, 'metrics': metrics}
43
+ if trained_on is not None:
44
+ metadata['trained_on'] = trained_on
45
+ meta_path.write_text(json.dumps(metadata, indent=2))
46
+
47
+ def load(encoder: str, pooling: str, head_type: str) -> tuple[nn.Module, Optional[dict], dict]:
48
+ ckpt_path, meta_path = checkpoint_paths(encoder, pooling, head_type)
49
+ if not (ckpt_path.exists() and meta_path.exists()):
50
+ raise FileNotFoundError(f'no trained checkpoint at {ckpt_path} - run experiments/train_head.py first (see backend/capabilities.py for the exact command).')
51
+ metadata = json.loads(meta_path.read_text())
52
+ blob = torch.load(ckpt_path, map_location='cpu', weights_only=True)
53
+ head = build_head(head_type, blob['input_dim'])
54
+ head.load_state_dict(blob['head_state_dict'])
55
+ head.eval()
56
+ return (head, blob.get('pooling_state_dict'), metadata)
backend/orchestrator.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import time
3
+ from typing import Optional
4
+ import numpy as np
5
+ import torch
6
+ import config as cfg
7
+ from backend import encoders, fusion, gate, heads, pooling, semantic, smart_turn_onnx, streaming_semantic
8
+ from backend.capabilities import get as get_capability
9
+ from backend.capabilities import head_capability_key
10
+ from backend.types import PipelineConfig, PipelineResult, StageResult, StageUnavailableError
11
+
12
+ def _require(capability_key: str) -> None:
13
+ info = get_capability(capability_key)
14
+ if not info.available:
15
+ raise StageUnavailableError(f'{capability_key} is not available: {info.reason}')
16
+
17
+ def _run_custom_acoustic_branch(pc: PipelineConfig, audio: np.ndarray, sample_rate: int) -> tuple[StageResult, StageResult]:
18
+ head_key = head_capability_key(pc.encoder, pc.pooling, pc.head)
19
+ _require(head_key)
20
+ encoder_result = encoders.run(pc.encoder, audio, sample_rate)
21
+ hidden_states = encoder_result.output['hidden_states']
22
+ valid_length = encoder_result.output['valid_length']
23
+ head_module, pooling_state_dict, metadata = heads.load(pc.encoder, pc.pooling, pc.head)
24
+ pool_start = time.perf_counter()
25
+ with torch.inference_mode():
26
+ if pc.pooling == 'mean':
27
+ pooled = pooling.mean_pool(hidden_states, valid_length)
28
+ pooled_tensor = torch.from_numpy(pooled).float().unsqueeze(0)
29
+ elif pc.pooling == 'cross_attention':
30
+ attn_pool = pooling.CrossAttentionPool(hidden_dim=hidden_states.shape[-1])
31
+ attn_pool.load_state_dict(pooling_state_dict)
32
+ attn_pool.eval()
33
+ hs_tensor = torch.from_numpy(hidden_states).float().unsqueeze(0)
34
+ vl_tensor = torch.tensor([valid_length])
35
+ pooled_tensor = attn_pool(hs_tensor, vl_tensor)
36
+ else:
37
+ raise ValueError(f'unknown pooling: {pc.pooling!r}')
38
+ logit = head_module(pooled_tensor)
39
+ probability = torch.sigmoid(logit).item()
40
+ pool_head_ms = (time.perf_counter() - pool_start) * 1000
41
+ decision = 'complete' if probability > 0.5 else 'incomplete'
42
+ head_result = StageResult(stage=f'acoustic.custom.{pc.encoder}.{pc.pooling}.{pc.head}', timing_ms=pool_head_ms, output={'decision': decision, 'probability': probability, 'metrics_at_train_time': metadata.get('metrics')}, available=True, provenance='trained_by_us')
43
+ return (encoder_result, head_result)
44
+
45
+ def run_pipeline(pc: PipelineConfig, audio: np.ndarray, sample_rate: int=cfg.SAMPLE_RATE, preset_label: Optional[str]=None) -> PipelineResult:
46
+ if pc.full_duplex != 'off':
47
+ _require(f'mode.full_duplex_bypass.{pc.full_duplex}')
48
+ raise StageUnavailableError('full-duplex bypass is not implemented in this pass')
49
+ stage_results: list[StageResult] = []
50
+ start = time.perf_counter()
51
+ _require(f'gate.{pc.gate}')
52
+ gate_result = gate.run(pc.gate, audio, sample_rate, vad_aggressiveness=pc.vad_aggressiveness)
53
+ stage_results.append(gate_result)
54
+ acoustic_result = None
55
+ acoustic_probability = None
56
+ acoustic_provenance = None
57
+ if pc.encoder == 'none':
58
+ pass
59
+ elif pc.encoder == 'smart_turn_onnx':
60
+ _require('acoustic.smart_turn_onnx')
61
+ acoustic_result = smart_turn_onnx.run(audio, sample_rate)
62
+ stage_results.append(acoustic_result)
63
+ acoustic_probability = acoustic_result.output['probability']
64
+ acoustic_provenance = 'real_checkpoint'
65
+ else:
66
+ encoder_result, head_result = _run_custom_acoustic_branch(pc, audio, sample_rate)
67
+ stage_results.extend([encoder_result, head_result])
68
+ acoustic_result = head_result
69
+ acoustic_probability = head_result.output['probability']
70
+ acoustic_provenance = 'trained_by_us'
71
+ transcript = None
72
+ semantic_verdict = None
73
+ semantic_probability = None
74
+ if pc.semantic != 'off':
75
+ _require(f'semantic.{pc.semantic}')
76
+ if pc.semantic == 'qwen_local':
77
+ semantic_result = semantic.run(audio, sample_rate, temperature=pc.semantic_temperature)
78
+ stage_results.append(semantic_result)
79
+ transcript = semantic_result.output['transcript']
80
+ semantic_verdict = semantic_result.output['verdict']
81
+ elif pc.semantic == 'qwen_local_streaming':
82
+ semantic_result = streaming_semantic.run(audio, sample_rate, temperature=pc.semantic_temperature)
83
+ stage_results.append(semantic_result)
84
+ transcript = semantic_result.output['transcript']
85
+ semantic_verdict = semantic_result.output['verdict']
86
+ elif pc.semantic == 'livekit_eou':
87
+ semantic_result = semantic.run_livekit(audio, sample_rate)
88
+ stage_results.append(semantic_result)
89
+ transcript = semantic_result.output['transcript']
90
+ semantic_probability = semantic_result.output['probability']
91
+ else:
92
+ raise StageUnavailableError(f'semantic provider {pc.semantic!r} is not implemented yet')
93
+ if pc.fusion == 'weighted_vote':
94
+ _require('fusion.weighted_vote')
95
+ fusion_result = fusion.run(acoustic_probability, semantic_verdict, semantic_probability, pc.acoustic_weight)
96
+ stage_results.append(fusion_result)
97
+ decision = fusion_result.output['decision']
98
+ probability = fusion_result.output['probability']
99
+ result_provenance = 'rule'
100
+ elif pc.fusion == 'easy_turn':
101
+ _require('fusion.easy_turn')
102
+ raise StageUnavailableError('unreachable')
103
+ elif pc.encoder == 'none':
104
+ if semantic_probability is not None:
105
+ decision = 'complete' if semantic_probability > 0.5 else 'incomplete'
106
+ probability = semantic_probability
107
+ result_provenance = 'real_checkpoint'
108
+ else:
109
+ decision = 'complete' if semantic_verdict == 'complete' else 'incomplete'
110
+ probability = None
111
+ result_provenance = 'architecture_reimplemented' if pc.semantic == 'qwen_local_streaming' else 'real_checkpoint'
112
+ else:
113
+ decision = acoustic_result.output['decision']
114
+ probability = acoustic_probability
115
+ result_provenance = acoustic_provenance
116
+ total_latency_ms = (time.perf_counter() - start) * 1000
117
+ return PipelineResult(config=pc, decision=decision, probability=probability, transcript=transcript, semantic_verdict=semantic_verdict, stage_results=stage_results, total_latency_ms=total_latency_ms, provenance=result_provenance, preset_label=preset_label)
backend/pooling.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import numpy as np
3
+ import torch
4
+ import torch.nn as nn
5
+
6
+ def mean_pool(hidden_states: np.ndarray, valid_length: int) -> np.ndarray:
7
+ valid_length = max(1, min(valid_length, hidden_states.shape[0]))
8
+ return hidden_states[-valid_length:].mean(axis=0)
9
+
10
+ class CrossAttentionPool(nn.Module):
11
+
12
+ def __init__(self, hidden_dim: int):
13
+ super().__init__()
14
+ self.hidden_dim = hidden_dim
15
+ self.query = nn.Parameter(torch.randn(hidden_dim) * hidden_dim ** (-0.5))
16
+
17
+ def forward(self, hidden_states: torch.Tensor, valid_lengths: torch.Tensor) -> torch.Tensor:
18
+ batch, seq_len, dim = hidden_states.shape
19
+ positions = torch.arange(seq_len, device=hidden_states.device).unsqueeze(0)
20
+ start_idx = (seq_len - valid_lengths).unsqueeze(1)
21
+ mask = positions >= start_idx
22
+ scores = hidden_states @ self.query / dim ** 0.5
23
+ scores = scores.masked_fill(~mask, float('-inf'))
24
+ weights = torch.softmax(scores, dim=-1)
25
+ pooled = torch.einsum('bt,btd->bd', weights, hidden_states)
26
+ return pooled
backend/presets.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ from backend.capabilities import get as get_capability
4
+ from backend.capabilities import head_capability_key
5
+ from backend.types import PipelineConfig, Provenance
6
+
7
+ @dataclass
8
+ class Preset:
9
+ label: str
10
+ config: PipelineConfig
11
+ approximates: str
12
+ static_provenance: Provenance
13
+
14
+ def _required_capability_keys(pc: PipelineConfig) -> list[str]:
15
+ keys = [f'gate.{pc.gate}']
16
+ if pc.encoder == 'smart_turn_onnx':
17
+ keys.append('acoustic.smart_turn_onnx')
18
+ elif pc.encoder != 'none':
19
+ keys.append(head_capability_key(pc.encoder, pc.pooling, pc.head))
20
+ if pc.semantic != 'off':
21
+ keys.append(f'semantic.{pc.semantic}')
22
+ if pc.fusion != 'off':
23
+ keys.append(f'fusion.{pc.fusion}')
24
+ return keys
25
+ _PRESET_DEFS: list[Preset] = [Preset(label='Smart Turn v3.2 (zero-shot)', config=PipelineConfig(gate='silero_vad', encoder='smart_turn_onnx', semantic='off', fusion='off'), approximates='Smart Turn v3.2 (Pipecat)', static_provenance='real_checkpoint'), Preset(label='Whisper-Tiny + Mean-Pool + Linear (trained)', config=PipelineConfig(gate='silero_vad', encoder='whisper_tiny', pooling='mean', head='linear', semantic='off', fusion='off'), approximates="Smart Turn v2's recipe (mean pool + linear), retrained by us", static_provenance='trained_by_us'), Preset(label='Whisper-Tiny + Mean-Pool + MLP (trained)', config=PipelineConfig(gate='silero_vad', encoder='whisper_tiny', pooling='mean', head='mlp', semantic='off', fusion='off'), approximates='', static_provenance='trained_by_us'), Preset(label='Whisper-Tiny + Cross-Attn + Linear (trained)', config=PipelineConfig(gate='silero_vad', encoder='whisper_tiny', pooling='cross_attention', head='linear', semantic='off', fusion='off'), approximates="Smart Turn v3.2's recipe (cross-attn pool + linear), retrained by us on real Hindi/Hinglish audio (HiACC + FLEURS) after the same architecture scored worst OOD (1/5) trained on the challenge's synthetic TTS data - real data fixed it outright (5/5). See docs/decision-log.md #38, experiments/results/dataset_comparison.md.", static_provenance='trained_by_us'), Preset(label='TEN Turn Detection (prompted)', config=PipelineConfig(gate='silero_vad', encoder='none', semantic='qwen_local', fusion='off'), approximates='TEN Turn Detection - approximated with local Qwen2.5-0.5B instead of their 7B (docs/decision-log.md #11)', static_provenance='real_checkpoint'), Preset(label='LiveKit End-of-Turn Detector (zero-shot)', config=PipelineConfig(gate='silero_vad', encoder='none', semantic='livekit_eou', fusion='off'), approximates='livekit/turn-detector - a real checkpoint, not an approximation (docs/decision-log.md #33)', static_provenance='real_checkpoint'), Preset(label='Acoustic + Semantic Fusion (rule-based)', config=PipelineConfig(gate='silero_vad', encoder='whisper_tiny', pooling='mean', head='linear', semantic='livekit_eou', fusion='weighted_vote'), approximates="A zero-training combination of our trained acoustic head with LiveKit's real EOU probability (docs/decision-log.md #33) - NOT Easy Turn's actual jointly-trained fusion, and no 'wait' handling since LiveKit's model has no such class (traded away when this preset moved off qwen_local)", static_provenance='rule'), Preset(label='FastTurn Streaming (reimplemented)', config=PipelineConfig(gate='silero_vad', encoder='smart_turn_onnx', semantic='qwen_local_streaming', fusion='weighted_vote'), approximates="FastTurn's described streaming-fusion mechanism, reimplemented by us for Hindi/Hinglish with a CTC checkpoint (theainerd/Wav2Vec2-large-xlsr-hindi) - NOT their code or reported numbers, which were never released (docs/decision-log.md #23)", static_provenance='architecture_reimplemented'), Preset(label='Easy Turn (unavailable)', config=PipelineConfig(gate='silero_vad', encoder='whisper_tiny', pooling='mean', head='linear', semantic='qwen_local', fusion='easy_turn'), approximates="Easy Turn's real joint acoustic+linguistic fusion - NOT wired up this pass", static_provenance='unavailable')]
26
+
27
+ def list_presets() -> list[dict]:
28
+ out = []
29
+ for preset in _PRESET_DEFS:
30
+ try:
31
+ infos = [get_capability(key) for key in _required_capability_keys(preset.config)]
32
+ failing = [i for i in infos if not i.available]
33
+ if failing:
34
+ available = False
35
+ reason = '; '.join((f'{i.key}: {i.reason}' for i in failing))
36
+ provenance = 'unavailable'
37
+ else:
38
+ available = True
39
+ reason = None
40
+ provenance = preset.static_provenance
41
+ except (KeyError, ValueError) as exc:
42
+ available = False
43
+ reason = f'preset is not runnable as configured: {exc}'
44
+ provenance = 'unavailable'
45
+ out.append({'label': preset.label, 'config': preset.config, 'approximates': preset.approximates, 'available': available, 'reason': reason, 'provenance': provenance})
46
+ return out
backend/semantic.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import re
3
+ import time
4
+ import unicodedata
5
+ import numpy as np
6
+ import torch
7
+ import config
8
+ from backend.types import StageResult
9
+ _device = 'mps' if torch.backends.mps.is_available() else 'cpu'
10
+ _asr_pipeline = None
11
+ _qwen_model = None
12
+ _qwen_tokenizer = None
13
+ _livekit_model = None
14
+ _livekit_tokenizer = None
15
+ _livekit_im_end_id = None
16
+ PROMPT_TEMPLATE = 'You are analyzing a snippet of transcribed speech from a Hindi-English (Hinglish) conversation with a voice assistant. Code-switching between Hindi and English mid-sentence is normal and not a sign of incompleteness. Filler words like "matlab", "toh", "haan", "wo kya bolte hain", "um", "uh" indicate the speaker is still thinking and has NOT completed their turn.\n\nTranscript: "{transcript}"\n\nClassify whether the speaker\'s turn is:\n- complete: the utterance is a complete thought, the speaker is done\n- incomplete: the utterance is grammatically or semantically incomplete, more is coming\n- wait: the speaker is explicitly asking for a pause (e.g. "ek second", "hold on", "wait")\n\nRespond with exactly one word: complete, incomplete, or wait.'
17
+ _VALID_LABELS = ('complete', 'incomplete', 'wait')
18
+
19
+ def _get_asr_pipeline():
20
+ global _asr_pipeline
21
+ if _asr_pipeline is None:
22
+ from transformers import pipeline
23
+ _asr_pipeline = pipeline('automatic-speech-recognition', model=config.WHISPER_TINY_ID, device=_device if _device != 'mps' else -1)
24
+ return _asr_pipeline
25
+
26
+ def _get_qwen():
27
+ global _qwen_model, _qwen_tokenizer
28
+ if _qwen_model is None:
29
+ from transformers import AutoModelForCausalLM, AutoTokenizer
30
+ _qwen_tokenizer = AutoTokenizer.from_pretrained(config.QWEN_LOCAL_ID)
31
+ _qwen_model = AutoModelForCausalLM.from_pretrained(config.QWEN_LOCAL_ID).to(_device).eval()
32
+ return (_qwen_model, _qwen_tokenizer)
33
+
34
+ def _get_livekit():
35
+ global _livekit_model, _livekit_tokenizer, _livekit_im_end_id
36
+ if _livekit_model is None:
37
+ from transformers import AutoModelForCausalLM, AutoTokenizer
38
+ _livekit_tokenizer = AutoTokenizer.from_pretrained(config.LIVEKIT_TURN_DETECTOR_ID)
39
+ _livekit_model = AutoModelForCausalLM.from_pretrained(config.LIVEKIT_TURN_DETECTOR_ID).to(_device).eval()
40
+ _livekit_im_end_id = _livekit_tokenizer.convert_tokens_to_ids('<|im_end|>')
41
+ return (_livekit_model, _livekit_tokenizer, _livekit_im_end_id)
42
+ _PUNCTUATION_RE = re.compile("[^\\w\\s'-]", re.UNICODE)
43
+ _WHITESPACE_RE = re.compile('\\s+')
44
+
45
+ def _normalize_for_livekit(transcript: str) -> str:
46
+ text = unicodedata.normalize('NFKC', transcript).lower()
47
+ text = _PUNCTUATION_RE.sub(' ', text)
48
+ return _WHITESPACE_RE.sub(' ', text).strip()
49
+
50
+ def transcribe(audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE) -> str:
51
+ asr = _get_asr_pipeline()
52
+ result = asr({'raw': np.asarray(audio, dtype=np.float32), 'sampling_rate': sample_rate})
53
+ return result['text'].strip()
54
+
55
+ def _parse_label(raw_text: str) -> str:
56
+ lowered = raw_text.lower()
57
+ for label in _VALID_LABELS:
58
+ if re.search(f'\\b{label}\\b', lowered):
59
+ return label
60
+ return 'incomplete'
61
+
62
+ @torch.inference_mode()
63
+ def classify_transcript(transcript: str, temperature: float=0.2) -> dict:
64
+ model, tokenizer = _get_qwen()
65
+ messages = [{'role': 'user', 'content': PROMPT_TEMPLATE.format(transcript=transcript)}]
66
+ prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
67
+ inputs = tokenizer(prompt, return_tensors='pt').to(_device)
68
+ do_sample = temperature > 0
69
+ output_ids = model.generate(**inputs, max_new_tokens=8, do_sample=do_sample, temperature=temperature if do_sample else None, pad_token_id=tokenizer.eos_token_id)
70
+ generated = output_ids[0][inputs['input_ids'].shape[1]:]
71
+ raw_text = tokenizer.decode(generated, skip_special_tokens=True)
72
+ label = _parse_label(raw_text)
73
+ return {'verdict': label, 'raw_response': raw_text.strip()}
74
+
75
+ def run(audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE, temperature: float=0.2) -> StageResult:
76
+ start = time.perf_counter()
77
+ transcript = transcribe(audio, sample_rate)
78
+ result = classify_transcript(transcript, temperature)
79
+ timing_ms = (time.perf_counter() - start) * 1000
80
+ return StageResult(stage='semantic.qwen_local', timing_ms=timing_ms, output={'transcript': transcript, 'verdict': result['verdict'], 'raw_response': result['raw_response']}, available=True, provenance='real_checkpoint')
81
+
82
+ @torch.inference_mode()
83
+ def classify_transcript_livekit(transcript: str) -> float:
84
+ model, tokenizer, im_end_id = _get_livekit()
85
+ normalized = _normalize_for_livekit(transcript)
86
+ prompt = f'<|im_start|><|user|>{normalized}'
87
+ inputs = tokenizer(prompt, return_tensors='pt').to(_device)
88
+ logits = model(**inputs).logits[0, -1, :]
89
+ probs = torch.softmax(logits, dim=-1)
90
+ return probs[im_end_id].item()
91
+
92
+ def run_livekit(audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE) -> StageResult:
93
+ start = time.perf_counter()
94
+ transcript = transcribe(audio, sample_rate)
95
+ probability = classify_transcript_livekit(transcript)
96
+ timing_ms = (time.perf_counter() - start) * 1000
97
+ return StageResult(stage='semantic.livekit_eou', timing_ms=timing_ms, output={'transcript': transcript, 'probability': probability}, available=True, provenance='real_checkpoint')
backend/smart_turn_onnx.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import time
3
+ from pathlib import Path
4
+ import numpy as np
5
+ import onnxruntime as ort
6
+ from transformers import WhisperFeatureExtractor
7
+ import config
8
+ from backend.audio_utils import truncate_or_left_pad
9
+ from backend.types import StageResult
10
+ _session: ort.InferenceSession | None = None
11
+ _feature_extractor: WhisperFeatureExtractor | None = None
12
+
13
+ def _onnx_path() -> Path:
14
+ return config.MODELS_DIR / config.SMART_TURN_ONNX_FILENAME
15
+
16
+ def _get_session() -> ort.InferenceSession:
17
+ global _session
18
+ if _session is None:
19
+ path = _onnx_path()
20
+ if not path.exists():
21
+ from huggingface_hub import hf_hub_download
22
+ config.MODELS_DIR.mkdir(parents=True, exist_ok=True)
23
+ hf_hub_download(repo_id=config.SMART_TURN_REPO, filename=config.SMART_TURN_ONNX_FILENAME, local_dir=config.MODELS_DIR)
24
+ _session = ort.InferenceSession(str(path))
25
+ return _session
26
+
27
+ def _get_feature_extractor() -> WhisperFeatureExtractor:
28
+ global _feature_extractor
29
+ if _feature_extractor is None:
30
+ _feature_extractor = WhisperFeatureExtractor(chunk_length=config.SMART_TURN_WINDOW_SECONDS)
31
+ return _feature_extractor
32
+
33
+ def preprocess(audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE) -> np.ndarray:
34
+ if sample_rate != config.SAMPLE_RATE:
35
+ raise ValueError(f'Smart Turn expects {config.SAMPLE_RATE}Hz mono PCM, got sample_rate={sample_rate}. Resample before calling this function.')
36
+ audio = truncate_or_left_pad(audio, config.SMART_TURN_WINDOW_SECONDS, sample_rate)
37
+ fe = _get_feature_extractor()
38
+ inputs = fe(audio, sampling_rate=sample_rate, return_tensors='np', padding='max_length', max_length=config.SMART_TURN_WINDOW_SAMPLES, truncation=True, do_normalize=True)
39
+ input_features = inputs.input_features.squeeze(0).astype(np.float32)
40
+ return np.expand_dims(input_features, axis=0)
41
+
42
+ def run(audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE) -> StageResult:
43
+ start = time.perf_counter()
44
+ session = _get_session()
45
+ input_features = preprocess(audio, sample_rate)
46
+ outputs = session.run(None, {'input_features': input_features})
47
+ probability = float(outputs[0].flatten()[0])
48
+ prediction = 'complete' if probability > 0.5 else 'incomplete'
49
+ timing_ms = (time.perf_counter() - start) * 1000
50
+ return StageResult(stage='acoustic.smart_turn_onnx', timing_ms=timing_ms, output={'decision': prediction, 'probability': probability}, available=True, provenance='real_checkpoint')
backend/streaming_semantic.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ import math
3
+ import time
4
+ import numpy as np
5
+ import torch
6
+ import config
7
+ from backend.semantic import classify_transcript
8
+ from backend.types import StageResult
9
+ _device = 'mps' if torch.backends.mps.is_available() else 'cpu'
10
+ _ctc_model = None
11
+ _ctc_processor = None
12
+ DEFAULT_CHUNK_MS = 1000
13
+ MAX_STEPS = 10
14
+
15
+ def _get_ctc():
16
+ global _ctc_model, _ctc_processor
17
+ if _ctc_model is None:
18
+ from transformers import Wav2Vec2ForCTC, Wav2Vec2Processor
19
+ _ctc_processor = Wav2Vec2Processor.from_pretrained(config.HINDI_CTC_ID)
20
+ _ctc_model = Wav2Vec2ForCTC.from_pretrained(config.HINDI_CTC_ID).to(_device).eval()
21
+ return (_ctc_processor, _ctc_model)
22
+
23
+ @torch.inference_mode()
24
+ def transcribe_ctc_chunk(audio_prefix: np.ndarray, sample_rate: int=config.SAMPLE_RATE) -> str:
25
+ processor, model = _get_ctc()
26
+ inputs = processor(audio_prefix, sampling_rate=sample_rate, return_tensors='pt', padding=True)
27
+ input_values = inputs.input_values.to(_device)
28
+ logits = model(input_values).logits
29
+ predicted_ids = torch.argmax(logits, dim=-1)
30
+ text = processor.batch_decode(predicted_ids)[0]
31
+ return text.strip()
32
+
33
+ def run(audio: np.ndarray, sample_rate: int=config.SAMPLE_RATE, chunk_ms: int=DEFAULT_CHUNK_MS, temperature: float=0.2) -> StageResult:
34
+ start = time.perf_counter()
35
+ audio = np.asarray(audio, dtype=np.float32)
36
+ total_audio_ms = len(audio) / sample_rate * 1000
37
+ chunk_samples = int(sample_rate * chunk_ms / 1000)
38
+ history: list[dict] = []
39
+ first_decisive_audio_ms = None
40
+ num_steps = min(MAX_STEPS, max(1, len(audio) // chunk_samples))
41
+ stride = max(chunk_samples, math.ceil(len(audio) / num_steps))
42
+ for step in range(1, num_steps + 1):
43
+ prefix_len = min(len(audio), step * stride)
44
+ prefix = audio[:prefix_len]
45
+ elapsed_audio_ms = prefix_len / sample_rate * 1000
46
+ step_start = time.perf_counter()
47
+ transcript_so_far = transcribe_ctc_chunk(prefix, sample_rate)
48
+ verdict = 'incomplete'
49
+ if transcript_so_far:
50
+ verdict = classify_transcript(transcript_so_far, temperature)['verdict']
51
+ step_ms = (time.perf_counter() - step_start) * 1000
52
+ history.append({'elapsed_audio_ms': round(elapsed_audio_ms, 1), 'transcript_so_far': transcript_so_far, 'verdict': verdict, 'step_processing_ms': round(step_ms, 1)})
53
+ if first_decisive_audio_ms is None and verdict != 'incomplete':
54
+ first_decisive_audio_ms = elapsed_audio_ms
55
+ if prefix_len >= len(audio):
56
+ break
57
+ final = history[-1]
58
+ timing_ms = (time.perf_counter() - start) * 1000
59
+ return StageResult(stage='semantic.qwen_local_streaming', timing_ms=timing_ms, output={'transcript': final['transcript_so_far'], 'verdict': final['verdict'], 'history': history, 'total_audio_ms': round(total_audio_ms, 1), 'first_decisive_audio_ms': first_decisive_audio_ms, 'eou_delay_saved_ms': round(total_audio_ms - first_decisive_audio_ms, 1) if first_decisive_audio_ms is not None else None}, available=True, provenance='architecture_reimplemented')
backend/types.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from typing import Any, Literal, Optional
4
+ Provenance = Literal['real_checkpoint', 'trained_by_us', 'rule', 'architecture_reimplemented', 'unavailable']
5
+ GateChoice = Literal['webrtcvad', 'silero_vad', 'none']
6
+ EncoderChoice = Literal['whisper_tiny', 'whisper_base', 'wav2vec2', 'smart_turn_onnx', 'none']
7
+ PoolingChoice = Literal['mean', 'cross_attention']
8
+ HeadChoice = Literal['linear', 'mlp']
9
+ SemanticChoice = Literal['off', 'qwen_local', 'qwen_local_streaming', 'livekit_eou', 'groq_api', 'openrouter_api']
10
+ FusionChoice = Literal['off', 'weighted_vote', 'easy_turn']
11
+ OutputClasses = Literal['binary', '3class', '4class']
12
+ FullDuplexChoice = Literal['off', 'moshi', 'human1']
13
+
14
+ class StageUnavailableError(RuntimeError):
15
+ pass
16
+
17
+ @dataclass
18
+ class StageResult:
19
+ stage: str
20
+ timing_ms: float
21
+ output: Any = None
22
+ available: bool = True
23
+ reason: Optional[str] = None
24
+ provenance: Optional[Provenance] = None
25
+
26
+ @dataclass
27
+ class PipelineConfig:
28
+ gate: GateChoice = 'silero_vad'
29
+ encoder: EncoderChoice = 'whisper_tiny'
30
+ pooling: PoolingChoice = 'mean'
31
+ head: HeadChoice = 'linear'
32
+ semantic: SemanticChoice = 'off'
33
+ fusion: FusionChoice = 'off'
34
+ output_classes: OutputClasses = 'binary'
35
+ full_duplex: FullDuplexChoice = 'off'
36
+ vad_aggressiveness: int = 2
37
+ silence_trigger_ms: int = 400
38
+ semantic_temperature: float = 0.2
39
+ acoustic_weight: float = 0.6
40
+
41
+ def __post_init__(self) -> None:
42
+ if self.encoder == 'none':
43
+ if self.semantic == 'off':
44
+ raise ValueError("encoder='none' requires the semantic branch to be on (nothing would decide).")
45
+ if self.fusion != 'off':
46
+ raise ValueError("encoder='none' has no acoustic score to fuse with - set fusion='off'.")
47
+ if self.encoder == 'smart_turn_onnx':
48
+ if self.pooling != 'mean' or self.head != 'linear':
49
+ raise ValueError("encoder='smart_turn_onnx' is a single opaque preset and cannot be combined with a separate pooling/head choice (see docs/decision-log.md #18).")
50
+ if self.fusion != 'off' and self.semantic == 'off':
51
+ raise ValueError('fusion requires the semantic branch to be on.')
52
+ if self.full_duplex != 'off':
53
+ pass
54
+
55
+ @dataclass
56
+ class PipelineResult:
57
+ config: PipelineConfig
58
+ decision: Optional[str] = None
59
+ probability: Optional[float] = None
60
+ transcript: Optional[str] = None
61
+ semantic_verdict: Optional[str] = None
62
+ stage_results: list[StageResult] = field(default_factory=list)
63
+ total_latency_ms: float = 0.0
64
+ provenance: Optional[Provenance] = None
65
+ preset_label: Optional[str] = None
config.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ ROOT = Path(__file__).resolve().parent
3
+ DATABASE_DIR = ROOT / 'database'
4
+ AUDIO_DIR = DATABASE_DIR / 'audio'
5
+ SAMPLE_CLIPS_DIR = AUDIO_DIR / 'sample_clips'
6
+ EVAL_DIR = AUDIO_DIR / 'eval'
7
+ OOD_TEST_CLIPS_DIR = EVAL_DIR / 'ood_test_clips'
8
+ TRUNCATION_PILOT_DIR = EVAL_DIR / 'truncation_pilot'
9
+ LIVE_RUNS_DIR = AUDIO_DIR / 'live_runs'
10
+ CHECKPOINTS_DIR = DATABASE_DIR / 'checkpoints'
11
+ MODELS_DIR = DATABASE_DIR / 'models'
12
+ EASY_TURN_DIR = MODELS_DIR / 'easy_turn'
13
+ CACHE_DIR = DATABASE_DIR / 'cache'
14
+ DATA_CACHE_DIR = CACHE_DIR / 'dataset'
15
+ OOD_CACHE_DIR = CACHE_DIR / 'ood_test'
16
+ EXPERIMENTS_DIR = ROOT / 'experiments'
17
+ EMBEDDINGS_CACHE_DIR = EXPERIMENTS_DIR / 'embeddings_cache'
18
+ RESULTS_DIR = EXPERIMENTS_DIR / 'results'
19
+ SAMPLE_RATE = 16000
20
+ SMART_TURN_WINDOW_SECONDS = 8
21
+ SMART_TURN_WINDOW_SAMPLES = SMART_TURN_WINDOW_SECONDS * SAMPLE_RATE
22
+ WHISPER_TINY_ID = 'openai/whisper-tiny'
23
+ WHISPER_BASE_ID = 'openai/whisper-base'
24
+ WAV2VEC2_ID = 'facebook/wav2vec2-base'
25
+ QWEN_LOCAL_ID = 'Qwen/Qwen2.5-0.5B-Instruct'
26
+ HINDI_CTC_ID = 'theainerd/Wav2Vec2-large-xlsr-hindi'
27
+ LIVEKIT_TURN_DETECTOR_ID = 'livekit/turn-detector'
28
+ SMART_TURN_REPO = 'pipecat-ai/smart-turn-v3'
29
+ SMART_TURN_ONNX_FILENAME = 'smart-turn-v3.2-cpu.onnx'
30
+ DATASET_REPO = 'pipecat-ai/smart-turn-data-v3.2-train'
31
+ DATASET_NUM_SHARDS_TOTAL = 83
32
+ EASY_TURN_REPO = 'ASLP-lab/Easy-Turn'
33
+ ENCODER_IDS = {'whisper_tiny': WHISPER_TINY_ID, 'whisper_base': WHISPER_BASE_ID, 'wav2vec2': WAV2VEC2_ID}
database/audio/sample_clips/english_fleurs_1511.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a9dde919c8a85ece3116cf42fa3f2d052a9174319a4062c975631c848594961d
3
+ size 180524
database/audio/sample_clips/english_fleurs_1512.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c30bddf019ff7abf8f81f0d6a0fc018b186613a9b2d67c3f3df24ae255e0b6b1
3
+ size 345644
database/audio/sample_clips/english_fleurs_1545.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ee23300897063f00999fb4a5f3bfd7521152b596ad91c6bf4758be54a855c2ba
3
+ size 245164
database/audio/sample_clips/english_fleurs_1546.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:be0fb2731a5012de1902223605b2a5110828482b65aa48b869eebaf319d2cdaf
3
+ size 391724
database/audio/sample_clips/english_fleurs_1554.wav ADDED
Binary file (86.4 kB). View file
 
database/audio/sample_clips/english_fleurs_1578.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:933a4569cc229f58e56cf680fa4cd3c7660b1e73cf6ef734ed906699bb81b1f9
3
+ size 265644
database/audio/sample_clips/english_fleurs_1595.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d736aa4815a6de4ecc1f2864c4e6f9c67906b8eb37e8df103a4107d95d257377
3
+ size 320684
database/audio/sample_clips/english_fleurs_1637.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8bad0d5ef9019d1c9cbbc4aac7abe0ea49f5b4fd7956f92a369f70bc4946ae5f
3
+ size 1015724
database/audio/sample_clips/english_fleurs_1639.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:78af7de3a38af8b7a368096b7047650fc387491601bfe48221806cf46d94ec92
3
+ size 291884
database/audio/sample_clips/english_fleurs_1645.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:15a992f941349e2a03fafca3d96ea1a2d86ffb6aadf8833369095b21423e6042
3
+ size 218924
database/audio/sample_clips/hindi_fleurs_1524.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e00d95d250c2ba7a3f2186b26f82a126e80b61bb667bd0ee2af712287c620bef
3
+ size 382124
database/audio/sample_clips/hindi_fleurs_1526.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f1b686404e5dadd0cdadc91c20737c9d5846b0ceb981f533576f1c1ec1c49cd1
3
+ size 453164
database/audio/sample_clips/hindi_fleurs_1540.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e2053febb046fab7e62066d77ecefa597f80db86e02843517400d4192d2df270
3
+ size 276524
database/audio/sample_clips/hindi_fleurs_1549.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:855c89aa564662385d32b05d35e28edc60a6d05974735320886f913c87c493ee
3
+ size 506924
database/audio/sample_clips/hindi_fleurs_1560.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6c24145d731abae2b8e1323ff08edd944f8a2a1ed05035ebf43770f8190f8f9f
3
+ size 192044
database/audio/sample_clips/hindi_fleurs_1581.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7c58925c65f89f1ddeba6055d59b27e37ae49bf1ca0233ae0a1d1747f60a3168
3
+ size 224684
database/audio/sample_clips/hindi_fleurs_1609.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4871a032b62c48f1d286d0b977b8218856c1205360359f96a273304870007e7c
3
+ size 303404
database/audio/sample_clips/hindi_fleurs_1620.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:41801b96eb739e2f6a0850c7f9cc75adb36468a9a81613922996276ac4bd4f38
3
+ size 610604
database/audio/sample_clips/hindi_fleurs_1641.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1c81049dd7cb390aa3267410ac4e1e2e95622d6085366fe3ff088471324cd8a5
3
+ size 361004
database/audio/sample_clips/hindi_fleurs_1645.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7f1aee1a6596889197682524a5ad105141d38b54318ed03e1bf3580b64f36c39
3
+ size 169004
database/audio/sample_clips/hinglish_hiacc_AD09072.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cd452e54e2e1432586f68f05d2cd25d0411d83dafc8f4dac000ff4cb59bc9597
3
+ size 512084
database/audio/sample_clips/hinglish_hiacc_AD13003.wav ADDED
Binary file (96.8 kB). View file
 
database/audio/sample_clips/hinglish_hiacc_AD22092.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:77a8459e9696d11be084b1671b8bca53d0c5275f52f4eb2a1c834d94992a94fd
3
+ size 227710
database/audio/sample_clips/hinglish_hiacc_AD23025.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:599e5b5ccd3c1dbf321963d158eda124e3e6ca2c582b03e1786474732da7ddcb
3
+ size 104730
database/audio/sample_clips/hinglish_hiacc_AD36007.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c8223b79e5de802db3adb45d4bf724c9eadaf79701d20c357b1a56ea189dccc0
3
+ size 137886
database/audio/sample_clips/hinglish_hiacc_AD36064.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f90d8b109474071f39fd8767c50d4afb5d7b22dd83e91f0ece9c3edb707426d7
3
+ size 121006
database/audio/sample_clips/hinglish_hiacc_AD40049.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5234def68e6e0823873e2dc63f2586a3986f2ddf4ecb53a2d6d31945198ee34e
3
+ size 289802
database/audio/sample_clips/hinglish_hiacc_AD40103.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1dc4b1146f75f1540b9348c02b4277d205189c41ebf1a65c8ba3ce939fefa607
3
+ size 193346
database/audio/sample_clips/hinglish_hiacc_AD40158.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:96eeca3c3bd867f0b4f953843004127cc1d6f486dcc6d637a6740d4cdd29a0a9
3
+ size 173452
database/audio/sample_clips/hinglish_hiacc_AD60096.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:778f8fe8a0fa53585b93e61d94270f34cc681297ca5da4d2f0c9fec53d8a6a3f
3
+ size 153124
database/audio/sample_clips/manifest.json ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "filename": "english_fleurs_1554.wav",
4
+ "bucket": "english",
5
+ "source": "fleurs_en_us",
6
+ "duration_seconds": 2.7,
7
+ "transcript": "he built a wifi door bell he said"
8
+ },
9
+ {
10
+ "filename": "english_fleurs_1511.wav",
11
+ "bucket": "english",
12
+ "source": "fleurs_en_us",
13
+ "duration_seconds": 5.64,
14
+ "transcript": "soon officers equipped with riot gear entered the yard and cornered the inmates with tear gas"
15
+ },
16
+ {
17
+ "filename": "english_fleurs_1645.wav",
18
+ "bucket": "english",
19
+ "source": "fleurs_en_us",
20
+ "duration_seconds": 6.84,
21
+ "transcript": "its all-pervading power affected everyone from king to commoner"
22
+ },
23
+ {
24
+ "filename": "english_fleurs_1545.wav",
25
+ "bucket": "english",
26
+ "source": "fleurs_en_us",
27
+ "duration_seconds": 7.66,
28
+ "transcript": "a curry can be either dry or wet depending on the amount of liquid"
29
+ },
30
+ {
31
+ "filename": "english_fleurs_1578.wav",
32
+ "bucket": "english",
33
+ "source": "fleurs_en_us",
34
+ "duration_seconds": 8.3,
35
+ "transcript": "then lakkha singh took the lead in singing the bhajans"
36
+ },
37
+ {
38
+ "filename": "english_fleurs_1639.wav",
39
+ "bucket": "english",
40
+ "source": "fleurs_en_us",
41
+ "duration_seconds": 9.12,
42
+ "transcript": "since students are often the most critical audience the blog writer begins to strive to improve writing to avoid criticism"
43
+ },
44
+ {
45
+ "filename": "english_fleurs_1595.wav",
46
+ "bucket": "english",
47
+ "source": "fleurs_en_us",
48
+ "duration_seconds": 10.02,
49
+ "transcript": "the tibetan buddhism is based on the teachings of buddha but were extended by the mahayana path of love and by a lot of techniques from indian yoga"
50
+ },
51
+ {
52
+ "filename": "english_fleurs_1512.wav",
53
+ "bucket": "english",
54
+ "source": "fleurs_en_us",
55
+ "duration_seconds": 10.8,
56
+ "transcript": "this new environment has different resources and different competitors so the new population will need different features or adaptations to be a strong competitor than what they had needed before"
57
+ },
58
+ {
59
+ "filename": "english_fleurs_1546.wav",
60
+ "bucket": "english",
61
+ "source": "fleurs_en_us",
62
+ "duration_seconds": 12.24,
63
+ "transcript": "the feathers' structure suggests that they were not used in flight but rather for temperature regulation or display the researchers suggested that even though this is the tail of a young dinosaur the sample shows adult plumage and not a chick's down"
64
+ },
65
+ {
66
+ "filename": "english_fleurs_1637.wav",
67
+ "bucket": "english",
68
+ "source": "fleurs_en_us",
69
+ "duration_seconds": 31.74,
70
+ "transcript": "the terrified king louis xvi queen marie antoinette their two young children 11 year old marie therese and four year old louis-charles and the king's sister madam elizabeth on the 6th october 1789 were forced back to paris from versailles by a mob of market women"
71
+ },
72
+ {
73
+ "filename": "hindi_fleurs_1645.wav",
74
+ "bucket": "hindi",
75
+ "source": "fleurs_hi_in",
76
+ "duration_seconds": 5.28,
77
+ "transcript": "\u0907\u0938\u0915\u0940 \u0938\u0930\u094d\u0935\u0935\u094d\u092f\u093e\u092a\u0940 \u0936\u0915\u094d\u0924\u093f \u0928\u0947 \u0930\u093e\u091c\u093e \u0938\u0947 \u0932\u0947\u0915\u0930 \u0906\u092e \u0924\u0915 \u0938\u092d\u0940 \u0915\u094b \u092a\u094d\u0930\u092d\u093e\u0935\u093f\u0924 \u0915\u093f\u092f\u093e"
78
+ },
79
+ {
80
+ "filename": "hindi_fleurs_1560.wav",
81
+ "bucket": "hindi",
82
+ "source": "fleurs_hi_in",
83
+ "duration_seconds": 6.0,
84
+ "transcript": "\u091c\u0948\u0938\u0947 \u0939\u0940 \u0906\u092a \u0927\u093e\u0930\u093e \u0938\u0947 \u0928\u093f\u0915\u0932 \u0906\u0924\u0947 \u0939\u0948\u0902 \u092a\u0940\u091b\u0947 \u0915\u0940 \u0913\u0930 \u0924\u0948\u0930\u0928\u0947 \u092e\u0947\u0902 \u0915\u094b\u0908 \u0916\u093e\u0938 \u092e\u0941\u0936\u094d\u0915\u093f\u0932 \u0928\u0939\u0940\u0902 \u0939\u094b\u0924\u0940 \u0939\u0948"
85
+ },
86
+ {
87
+ "filename": "hindi_fleurs_1581.wav",
88
+ "bucket": "hindi",
89
+ "source": "fleurs_hi_in",
90
+ "duration_seconds": 7.02,
91
+ "transcript": "\u0905\u092e\u0947\u091c\u093c\u0928 \u0927\u0930\u0924\u0940 \u0915\u0940 \u0938\u092c\u0938\u0947 \u091a\u094c\u0921\u093c\u0940 \u0928\u0926\u0940 \u092d\u0940 \u0939\u0948 \u091c\u094b \u0915\u092d\u0940-\u0915\u092d\u0940 \u091b\u0939 \u092e\u0940\u0932 \u091c\u093f\u0924\u0928\u0940 \u091a\u094c\u0921\u093c\u0940 \u0939\u094b \u091c\u093e\u0924\u0940 \u0939\u0948"
92
+ },
93
+ {
94
+ "filename": "hindi_fleurs_1540.wav",
95
+ "bucket": "hindi",
96
+ "source": "fleurs_hi_in",
97
+ "duration_seconds": 8.64,
98
+ "transcript": "\u0938\u0948\u0926\u094d\u0927\u093e\u0902\u0924\u093f\u0915 \u0930\u0942\u092a \u0938\u0947 \u0924\u093f\u092c\u094d\u092c\u0924\u0940 \u092c\u094c\u0926\u094d\u0927 \u0927\u0930\u094d\u092e \u092c\u0939\u0941\u0924 \u0938\u0930\u0932 \u0939\u0948 \u0907\u0938\u092e\u0947\u0902 \u0915\u0941\u0902\u0921\u0932\u093f\u0928\u0940 \u092f\u094b\u0917 \u0927\u094d\u092f\u093e\u0928 \u0914\u0930 \u0938\u092d\u0940 \u0915\u094b \u092a\u094d\u0930\u0947\u092e \u0938\u0947 \u0917\u0932\u0947 \u0932\u0917\u093e\u0928\u0947 \u0935\u093e\u0932\u093e \u092e\u093e\u0930\u094d\u0917 \u0936\u093e\u092e\u093f\u0932 \u0939\u0948"
99
+ },
100
+ {
101
+ "filename": "hindi_fleurs_1609.wav",
102
+ "bucket": "hindi",
103
+ "source": "fleurs_hi_in",
104
+ "duration_seconds": 9.48,
105
+ "transcript": "\u0930\u0949\u092c\u093f\u0928 \u0909\u0925\u092a\u094d\u092a\u093e \u0928\u0947 \u0915\u0947\u0935\u0932 41 \u0917\u0947\u0902\u0926\u094b\u0902 \u092e\u0947\u0902 11 \u091a\u094c\u0915\u094b\u0902 \u0914\u0930 2 \u091b\u0915\u094d\u0915\u094b\u0902 \u0915\u0940 \u092e\u0926\u0926 \u0938\u0947 70 \u0930\u0928 \u092c\u0928\u093e \u0915\u0930 \u092a\u093e\u0930\u0940 \u0915\u093e \u0938\u0930\u094d\u0935\u094b\u091a\u094d\u091a \u0938\u094d\u0915\u094b\u0930 \u092c\u0928\u093e\u092f\u093e"
106
+ },
107
+ {
108
+ "filename": "hindi_fleurs_1641.wav",
109
+ "bucket": "hindi",
110
+ "source": "fleurs_hi_in",
111
+ "duration_seconds": 11.28,
112
+ "transcript": "\u0939\u093e\u0932\u093e\u0902\u0915\u093f \u091c\u0902\u0917\u0932 \u0915\u093e \u0905\u0930\u094d\u0925 \u0915\u0947\u0935\u0932 \u092e\u0948\u0902\u0917\u094d\u0930\u094b\u0935 \u0926\u0932\u0926\u0932 \u0928\u0939\u0940\u0902 \u0939\u0948\u0902 \u0935\u0947 \u0915\u0941\u091b \u0905\u0902\u0924\u093f\u092e \u0936\u0915\u094d\u0924\u093f\u0936\u093e\u0932\u0940 \u091c\u0902\u0917\u0932\u094b\u0902 \u092e\u0947\u0902 \u0938\u0947 \u0939\u0948\u0902 \u091c\u094b \u0915\u092d\u0940 \u0917\u0902\u0917\u093e \u092a\u0920\u093e\u0930 \u0915\u094b \u0915\u0935\u0930 \u0915\u0930\u0924\u0947 \u0925\u0947"
113
+ },
114
+ {
115
+ "filename": "hindi_fleurs_1524.wav",
116
+ "bucket": "hindi",
117
+ "source": "fleurs_hi_in",
118
+ "duration_seconds": 11.94,
119
+ "transcript": "\u092e\u0940\u091f\u094d\u0930\u093f\u0915 \u092a\u094d\u0930\u0923\u093e\u0932\u0940 \u0915\u0947 \u0907\u0938\u094d\u0924\u0947\u092e\u093e\u0932 \u0928\u093f\u0930\u092a\u0947\u0915\u094d\u0937\u0935\u093e\u0926 \u0938\u0947 \u0917\u0923\u0924\u0902\u0924\u094d\u0930\u0935\u093e\u0926 \u0930\u093e\u0937\u094d\u091f\u094d\u0930\u0935\u093e\u0926 \u092e\u0947\u0902 \u092c\u0926\u0932\u093e\u0935 \u0914\u0930 \u0926\u0947\u0936 \u0915\u093e \u092d\u0930\u094b\u0938\u093e \u0932\u094b\u0917\u094b\u0902 \u0938\u0947 \u0939\u0948 \u0915\u093f\u0938\u0940 \u090f\u0915 \u0936\u093e\u0938\u0915 \u0938\u0947 \u0928\u0939\u0940\u0902 \u0910\u0938\u0940 \u092c\u093e\u0924\u094b\u0902 \u0915\u093e \u092c\u0939\u0941\u0924 \u091c\u093c\u094d\u092f\u093e\u0926\u093e \u0938\u093e\u092e\u093e\u091c\u093f\u0915 \u0914\u0930 \u0930\u093e\u091c\u0928\u0948\u0924\u093f\u0915 \u0905\u0938\u0930 \u0939\u094b\u0924\u093e \u0939\u0948"
120
+ },
121
+ {
122
+ "filename": "hindi_fleurs_1526.wav",
123
+ "bucket": "hindi",
124
+ "source": "fleurs_hi_in",
125
+ "duration_seconds": 14.16,
126
+ "transcript": "\u092f\u0926\u093f \u0906\u092a \u0938\u0930\u094d\u0926\u093f\u092f\u094b\u0902 \u092e\u0947\u0902 \u0906\u0930\u094d\u0915\u091f\u093f\u0915 \u092f\u093e \u0905\u0902\u091f\u093e\u0930\u094d\u0915\u091f\u093f\u0915 \u0915\u094d\u0937\u0947\u0924\u094d\u0930\u094b\u0902 \u092e\u0947\u0902 \u091c\u093e\u0924\u0947 \u0939\u0948\u0902 \u0924\u094b \u0906\u092a \u0927\u094d\u0930\u0941\u0935\u0940\u092f \u0930\u093e\u0924 \u0915\u093e \u0905\u0928\u0941\u092d\u0935 \u0915\u0930\u0947\u0902\u0917\u0947 \u091c\u093f\u0938\u0915\u093e \u0905\u0930\u094d\u0925 \u0939\u0948 \u0915\u093f \u0938\u0942\u0930\u091c \u0915\u094d\u0937\u093f\u0924\u093f\u091c \u0938\u0947 \u090a\u092a\u0930 \u0928\u0939\u0940\u0902 \u0909\u0917\u0924\u093e \u0939\u0948"
127
+ },
128
+ {
129
+ "filename": "hindi_fleurs_1549.wav",
130
+ "bucket": "hindi",
131
+ "source": "fleurs_hi_in",
132
+ "duration_seconds": 15.84,
133
+ "transcript": "\u0907\u0928 \u0938\u093f\u0926\u094d\u0927\u093e\u0902\u0924\u094b\u0902 \u0938\u0947 \u0938\u0902\u0915\u0947\u0924 \u092e\u093f\u0932\u0924\u093e \u0939\u0948 \u0915\u093f \u0932\u094b\u0917\u094b\u0902 \u0915\u0940 \u0915\u0941\u091b \u0910\u0938\u0940 \u091c\u093c\u0930\u0942\u0930\u0924\u0947\u0902 \u0914\u0930/\u092f\u093e \u0907\u091a\u094d\u091b\u093e\u090f\u0901 \u0925\u0940\u0902 \u091c\u094b \u0909\u0928\u0915\u0947 \u0935\u092f\u0938\u094d\u0915 \u0939\u094b\u0924\u0947-\u0939\u094b\u0924\u0947 \u0909\u0928\u0915\u0947 \u092d\u0940\u0924\u0930 \u0928\u093f\u0939\u093f\u0924 \u0939\u094b \u0917\u092f\u0940"
134
+ },
135
+ {
136
+ "filename": "hindi_fleurs_1620.wav",
137
+ "bucket": "hindi",
138
+ "source": "fleurs_hi_in",
139
+ "duration_seconds": 19.08,
140
+ "transcript": "\u091c\u093e\u0935\u093e \u0915\u0947 \u0935\u094d\u092f\u0902\u091c\u0928 \u0905\u092c \u092a\u0942\u0930\u0947 \u0926\u094d\u0935\u0940\u092a\u0938\u092e\u0942\u0939 \u092e\u0947\u0902 \u0935\u094d\u092f\u093e\u092a\u0915 \u0930\u0942\u092a \u0938\u0947 \u0909\u092a\u0932\u092c\u094d\u0927 \u0939\u0948\u0902 \u091c\u093f\u0938\u092e\u0947\u0902 \u092a\u093e\u0930\u0902\u092a\u0930\u093f\u0915 \u0935\u094d\u092f\u0902\u091c\u0928\u094b\u0902 \u0915\u0940 \u090f\u0915 \u0935\u093f\u0936\u0947\u0937\u0924\u093e \u0939\u0948 \u091c\u093e\u0935\u093e \u0915\u0947 \u0935\u094d\u092f\u0902\u091c\u0928\u094b\u0902 \u092e\u0947\u0902 \u092a\u094d\u0930\u092e\u0941\u0916 \u0938\u094d\u0935\u093e\u0926 \u092e\u0942\u0902\u0917\u092b\u0932\u0940 \u092e\u093f\u0930\u094d\u091a \u091a\u0940\u0928\u0940 \u0935\u093f\u0936\u0947\u0937 \u0930\u0942\u092a \u0938\u0947 \u091c\u093e\u0935\u093e \u0915\u0940 \u0928\u093e\u0930\u093f\u092f\u0932 \u091a\u0940\u0928\u0940 \u0914\u0930 \u0935\u093f\u092d\u093f\u0928\u094d\u0928 \u0938\u0941\u0917\u0902\u0927\u093f\u0924 \u092e\u0938\u093e\u0932\u094b\u0902 \u0915\u093e \u0939\u094b\u0924\u093e \u0939\u0948\u0964"
141
+ },
142
+ {
143
+ "filename": "hinglish_hiacc_AD13003.wav",
144
+ "bucket": "hinglish",
145
+ "source": "hiacc_adult_test",
146
+ "duration_seconds": 3.02,
147
+ "transcript": "So \u092e\u0947\u0930\u093e favourite festival \u0939\u0948 Diwali",
148
+ "code_mixing_index": 33.33,
149
+ "code_switch_count": 4
150
+ },
151
+ {
152
+ "filename": "hinglish_hiacc_AD23025.wav",
153
+ "bucket": "hinglish",
154
+ "source": "hiacc_adult_test",
155
+ "duration_seconds": 3.27,
156
+ "transcript": "\u0914\u0930 \u0909\u0928\u0915\u0940 friendship \u092c\u093f\u0932\u094d\u0915\u0941\u0932 \u091a\u0941\u091f\u0915\u093f\u092f\u094b\u0902 \u0915\u0940 \u0924\u0930\u0939 \u0925\u0940",
157
+ "code_mixing_index": 12.5,
158
+ "code_switch_count": 2
159
+ },
160
+ {
161
+ "filename": "hinglish_hiacc_AD36064.wav",
162
+ "bucket": "hinglish",
163
+ "source": "hiacc_adult_test",
164
+ "duration_seconds": 3.78,
165
+ "transcript": "\u0935\u094b as a memorial \u092c\u0928\u093e \u0939\u0948 those people who lost",
166
+ "code_mixing_index": 30.0,
167
+ "code_switch_count": 3
168
+ },
169
+ {
170
+ "filename": "hinglish_hiacc_AD36007.wav",
171
+ "bucket": "hinglish",
172
+ "source": "hiacc_adult_test",
173
+ "duration_seconds": 4.31,
174
+ "transcript": "Major reason is because \u092e\u0941\u091d\u0947 lights \u092c\u0939\u0941\u0924 \u092a\u0938\u0902\u0926 \u0939\u0948 I love all these \u0926\u093f\u092f\u093e",
175
+ "code_mixing_index": 35.71,
176
+ "code_switch_count": 5
177
+ },
178
+ {
179
+ "filename": "hinglish_hiacc_AD60096.wav",
180
+ "bucket": "hinglish",
181
+ "source": "hiacc_adult_test",
182
+ "duration_seconds": 4.78,
183
+ "transcript": "\u0907\u0938\u0947 \u0938\u0941\u0928\u0915\u0947 \u0926\u094b\u0928\u094b\u0902 \u0928\u0947 \u0938\u094b\u091a\u093e \u0915\u093f \u092f\u0947 \u0909\u0928\u0915\u093e next adventure spot \u0939\u094b \u0938\u0915\u0924\u093e \u0939\u0948\u0964",
184
+ "code_mixing_index": 21.43,
185
+ "code_switch_count": 2
186
+ },
187
+ {
188
+ "filename": "hinglish_hiacc_AD40158.wav",
189
+ "bucket": "hinglish",
190
+ "source": "hiacc_adult_test",
191
+ "duration_seconds": 5.42,
192
+ "transcript": "\u0910\u0938\u093e \u0932\u0917 \u0930\u0939\u093e \u0939\u0948 \u091c\u0948\u0938\u0947 \u0915\u0940 superman \u092c\u0928\u0928\u0947 \u0915\u0940 \u0915\u094b\u0936\u093f\u0936 \u0915\u0930 \u0930\u0939\u093e \u0939\u0948 \u0924\u094b \u0907\u0938 image \u0938\u0947 \u092f\u0947 \u092d\u0940 \u0915\u0939\u093e \u091c\u093e \u0938\u0915\u0924\u093e \u0939\u0948 \u0915\u093f",
193
+ "code_mixing_index": 8.33,
194
+ "code_switch_count": 4
195
+ },
196
+ {
197
+ "filename": "hinglish_hiacc_AD40103.wav",
198
+ "bucket": "hinglish",
199
+ "source": "hiacc_adult_test",
200
+ "duration_seconds": 6.04,
201
+ "transcript": "\u091a\u093f\u0902\u091f\u0942 \u0914\u0930 \u0930\u093e\u091c\u0942 \u0928\u0947 guardian \u0915\u0947 \u0926\u093f\u090f \u0917\u090f task complete \u0915\u0930 \u0932\u093f\u090f \u0914\u0930 \u0939\u0930 \u090f\u0915 task \u0915\u0947 \u092c\u093e\u0926",
202
+ "code_mixing_index": 22.22,
203
+ "code_switch_count": 6
204
+ },
205
+ {
206
+ "filename": "hinglish_hiacc_AD22092.wav",
207
+ "bucket": "hinglish",
208
+ "source": "hiacc_adult_test",
209
+ "duration_seconds": 7.11,
210
+ "transcript": "\u0905\u092a\u0928\u093e \u0916\u093e\u0928\u093e \u0926\u0947\u0916 \u0915\u0947 \u0915\u094d\u092f\u0942\u0901\u0915\u093f \u090f\u0915 \u0915\u094b \u0915\u092e \u092e\u093f\u0932\u093e \u090f\u0915 \u0915\u094b \u091c\u093e\u0926\u093e \u092e\u093f\u0932\u093e \u0924\u092c \u092d\u0940 \u0935\u094b unsatisfied \u0939\u0948 \u0909\u0938\u0915\u0947 dressing sense \u0914\u0930 \u0935\u094b \u0938\u092c \u091a\u0940\u091c\u0947 \u092d\u0940 \u0905\u091a\u094d\u091b\u0940 \u0939\u0948\u0964",
211
+ "code_mixing_index": 10.71,
212
+ "code_switch_count": 4
213
+ },
214
+ {
215
+ "filename": "hinglish_hiacc_AD40049.wav",
216
+ "bucket": "hinglish",
217
+ "source": "hiacc_adult_test",
218
+ "duration_seconds": 9.05,
219
+ "transcript": "\u092a\u0939\u093e\u0921\u0940 \u092f\u093e \u092b\u093f\u0930 \u0915\u093f\u0938\u0940 outstation \u091c\u0948\u0938\u0947 \u0936\u0939\u0930 \u092e\u0947\u0902 \u0939\u094b \u0938\u0915\u0924\u093e \u0939\u0948 \u0915\u094d\u092f\u0942\u0915\u093f \u0935\u0939\u093e\u0901 \u092a\u0947 \u092c\u0939\u0941\u0924 different \u091c\u0917\u0939 \u0915\u0947 \u0932\u094b\u0917 \u0939\u094b\u0924\u0947 \u0939\u0948\u0902\u0964",
220
+ "code_mixing_index": 9.52,
221
+ "code_switch_count": 4
222
+ },
223
+ {
224
+ "filename": "hinglish_hiacc_AD09072.wav",
225
+ "bucket": "hinglish",
226
+ "source": "hiacc_adult_test",
227
+ "duration_seconds": 16.0,
228
+ "transcript": "that's a barren land Barren \u092a\u0921\u093c\u0940 \u0939\u0948 \u0910\u0938\u093e \u0932\u0917\u0924\u093e \u0939\u0948 \u0915\u093f \u092f\u0947 desert \u0939\u0948 \u090f\u0915 \u0914\u0930 there are small small Mountains are there \u091c\u094b mountains \u0939\u0948 and that are short heighted mountain \u091c\u093f\u0938\u0938\u0947 \u0915\u0940 \u0938\u093e\u092b \u0928\u091c\u0930 \u0906\u0924\u093e \u0939\u0948 \u0915\u093f \u092f\u0947 \u090f\u0915 desert \u0915\u093e part \u0939\u0948",
229
+ "code_mixing_index": 48.89,
230
+ "code_switch_count": 13
231
+ }
232
+ ]