jptaylor commited on
Commit
aafe6b4
·
verified ·
1 Parent(s): 4b7bf16

Release rc1

Browse files
README.md ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: bsd-2-clause
3
+ language: en
4
+ library_name: onnx
5
+ pipeline_tag: audio-classification
6
+ base_model: laion/whisper-tiny-base-emotion-voice-burst
7
+ tags:
8
+ - pipecat
9
+ - speech-emotion-recognition
10
+ - sarcasm
11
+ - prosody
12
+ - onnx
13
+ ---
14
+
15
+ # Pipecat Voice Reader (rc1)
16
+
17
+ A small CPU model that hears how a user *sounds*: tone of voice, sarcasm in the delivery, energy, laughter and
18
+ speech rate. It reads the last 8 s of each user turn, once, when the user stops speaking, and Pipecat's
19
+ `VoiceReaderProcessor` writes the reading as RCS perception at the head of the user's message, beside the text
20
+ tone reader, which reads the words:
21
+
22
+ ```
23
+ ([emotion happy=0.62, neutral=0.30],[sarcasm value=0.08],[tone frustrated=0.71, neutral=0.22],[arousal value=0.34],[laughter value=0.02],[speech_rate value=3.1],[sarcasm_voice value=0.63]) Oh, great.
24
+ ```
25
+
26
+ The voice reader never writes `emotion` or `sarcasm`; it declares its own signals and the LLM weighs them with
27
+ the words. Its best use is the case the text reader cannot see: words that read sincere, said in a voice that
28
+ isn't.
29
+
30
+ ## Variants
31
+
32
+ This release carries two sizes of the same model, one folder each, read by the same runtime:
33
+
34
+ | Folder | `variant` | Params | int8 ONNX | Read time, p50 (M1 Max, 4 / 2 / 1 threads) | 1 thread, x86 server | Choose it for |
35
+ |---|---|---|---|---|---|---|
36
+ | `voice-reader-base/` | `"base"` (default) | 26M | 28.6 MB | 33 / 56 / 100 ms | ~100–150 ms | the best tone and sadness reading; adds the `sad` output |
37
+ | `voice-reader-tiny/` | `"tiny"` | 12.5M | 13.3 MB | 16 / 26 / 47 ms | ~45 ms | one-vCPU deployments: sarcasm about as well, half the CPU |
38
+
39
+ The read starts when the VAD says the user stopped and runs while the speech-to-text finalises, so it rarely
40
+ delays the transcription; the cost to watch is CPU at the end of each turn, beside the VAD, the turn detector and
41
+ the reply. Give `base` two threads where the machine allows.
42
+
43
+ Each folder has `model.int8.onnx` (the default), `model.onnx` (fp32), `config.json` (calibration, threshold,
44
+ output names) and `MANIFEST.json` (SHA-256 of every file, checked by the runtime on download).
45
+
46
+ ## Use
47
+
48
+ ```python
49
+ from pipecat_voice.reader import VoiceReaderParams, VoiceReaderProcessor
50
+
51
+ voice_reader = VoiceReaderProcessor(params=VoiceReaderParams(revision="rc1", variant="base", threads=2))
52
+
53
+ pipeline = Pipeline([transport.input(), stt, tone_reader, voice_reader, user_aggregator, llm, tts, transport.output()])
54
+ ```
55
+
56
+ It goes after the STT (it needs the transcriptions, and the STT passes the audio on) and after the text reader,
57
+ whose signal declarations it merges with its own. The runtime is numpy and onnxruntime; the model downloads from
58
+ this repository at the pinned revision the first time.
59
+
60
+ ## Outputs
61
+
62
+ | Signal | Published to the LLM | Meaning |
63
+ |---|---|---|
64
+ | `tone` | yes, top two | how the voice sounds: positive, neutral, sad, frustrated (temperature-calibrated) |
65
+ | `sarcasm_voice` | yes | the chance the delivery alone sounds sarcastic, Platt-calibrated on real conversation; below the release threshold (0.068) it is published under 0.5, as a guess; dropped under laughter |
66
+ | `arousal` | yes | vocal energy, 0–1 |
67
+ | `laughter` | yes | 0–1 |
68
+ | `speech_rate` | yes | syllables per second |
69
+ | `upset` | no, raw reading only | how clearly the speaker is upset, 0–1 |
70
+ | `sad` (`base` only) | no, raw reading only | how likely listeners would hear sadness, 0–1 |
71
+
72
+ Nothing is written for a turn with under 0.8 s of speech. The raw reading, including `upset` and `sad`, reaches
73
+ the application through the processor's `on_reading` event.
74
+
75
+ ## Evaluation
76
+
77
+ Labels on real speech come from two audio LLM judges (Gemini 3.1 Pro preview and OpenAI gpt-audio-1.5) hearing
78
+ each clip; a turn counts as sarcastic only when both say so (judge agreement on sarcasm: kappa 0.42). Test
79
+ speakers never appear in training.
80
+
81
+ | | `base` | `tiny` |
82
+ |---|---|---|
83
+ | **Sarcasm**, everyday conversation (4,366 turns, 203 sarcastic; Open Yap, otoSpeech, HarperValley), AUROC | 0.917 | 0.904 |
84
+ | — at the release threshold: F1 / recall / false alarms on sincere turns | 0.575 / 0.50 / 1.3 % | 0.560 / 0.48 / 1.3 % |
85
+ | — mean AUROC over four test sets (two conversational, Emolia, MUStARD++) | 0.791 | 0.788 |
86
+ | — MUStARD++ (TV, audio only, evaluation only), AUROC | 0.656 | 0.654 |
87
+ | **Tone** macro-F1: conversation / Emolia / CREMA-D (acted) / EmoNet-Voice (synthetic) | 0.706 / 0.661 / 0.887 / 0.563 | 0.712 / 0.627 / 0.872 / 0.457 |
88
+ | **Sadness**: tone recall for sad, mean of four sets | 0.53 | 0.38 |
89
+ | — `sad` output, AUROC: conversation (any judge heard sadness) / Emolia | 0.762 / 0.929 | – |
90
+ | **Upset**, mean AUROC (three sets) | 0.867 | 0.856 |
91
+ | **Arousal**, CCC against the training target | 0.89 | 0.88 |
92
+ | **Laughter**, AUROC / F1 | 0.994 / 0.72 | 0.991 / 0.70 |
93
+ | int8 vs fp32: tone agreement / sarcasm p95 \|Δp\| | 0.983 / 0.011 | 0.977 / 0.010 |
94
+
95
+ ## Limitations
96
+
97
+ - **Sarcasm from the voice alone is a weak cue.** At the threshold it catches about half of the sarcasm both
98
+ judges heard, at about 1 false alarm in 75 sincere turns. Weigh it with the words, never alone. On synthetic
99
+ (TTS) sarcasm it is near chance (AUROC 0.60 on our 20 demo pairs).
100
+ - **Sadness in conversation is rare** (2–6 % of turns), so even a good detector's per-turn flags are mostly
101
+ wrong (12–25 % precision at a 5 % false-alarm rate). Treat `sad` as a graded cue or average it over a
102
+ conversation. It is not a measure of depression or of anyone's mental health, and a single turn cannot show
103
+ either.
104
+ - **Acted, theatrical sadness** is under-read: 23 of 34 CREMA-D sad clips.
105
+ - English only; trained on conversational and podcast speech, 8 kHz telephone audio included.
106
+ - Tone labels are LLM judgments of the audio, not human ratings.
107
+
108
+ ## Training
109
+
110
+ - **Encoder**: Whisper encoder initialised from LAION's affect-tuned
111
+ [`laion/whisper-tiny-base-emotion-voice-burst`](https://huggingface.co/laion/whisper-tiny-base-emotion-voice-burst)
112
+ (CC BY 4.0), base and tiny encoders respectively, cut to an 8 s window, with masked statistics pooling and one
113
+ small head per output.
114
+ - **Data** (about 119k training windows): Open Yap 1K and otoSpeech conversations, HarperValleyBank calls
115
+ (CC BY 4.0), the English YODAS part of Emilia from `laion/Emolia` (CC BY 4.0), and CREMA-D (ODbL / DbCL).
116
+ Augmented with gain, noise, telephone band-limiting and codecs; never speed or pitch, since rate and pitch are
117
+ the signal.
118
+ - **Labels**: the two judges' verdicts on sarcasm, tone and upset; where they disagreed on tone, the mean of two
119
+ audio emotion models (emotion2vec+ large and the LAION model) at half weight; the `sad` head on the share of
120
+ judges who heard sadness.
121
+ - **Tiny**: distilled from the base model's predictions at half weight, with base-labelled Emolia clips added.
122
+ - **Calibration** on real conversational dev turns: temperature for tone, Platt for sarcasm and laughter; the
123
+ sarcasm threshold keeps false alarms at 2 % of sincere dev turns.
124
+
125
+ Evaluation-only data, never trained on: MUStARD++ (CC BY-NC) and EmoNet-Voice Bench's clearly rated clips.
voice-reader-base/MANIFEST.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "release": "rc1",
3
+ "folder": "voice-reader-base",
4
+ "run": "v3-base-sad",
5
+ "staged": "2026-11-02T20:40:58+00:00",
6
+ "files": {
7
+ "model.onnx": {
8
+ "sha256": "99975890aa7f1b0648af82a480233fdf641cee04bfee84370d0bed9c39d63313",
9
+ "bytes": 109728612
10
+ },
11
+ "model.int8.onnx": {
12
+ "sha256": "5c25605ea019521853c92347b48ced6f7706c14f21183f5de9da2a607c9fae95",
13
+ "bytes": 28567980
14
+ },
15
+ "config.json": {
16
+ "sha256": "72ed694b3ca24b368baa0d68e47fa90ccf271bf0d74895bff2a20aefb53cd165",
17
+ "bytes": 658
18
+ }
19
+ },
20
+ "parity_int8": {
21
+ "windows": 2000,
22
+ "tone_argmax_agreement": 0.983,
23
+ "tone_max_abs_dp": 0.14630481600761414,
24
+ "sarcasm_p95_abs_dp": 0.011146726086735725,
25
+ "sarcasm_max_abs_dp": 0.053686708211898804,
26
+ "laughter_p95_abs_dp": 0.0013878615573048592,
27
+ "arousal_p95_abs_d": 0.026246830821037292,
28
+ "speech_rate_p95_rel_d": 0.04056451842188835,
29
+ "auroc_sarcasm": {
30
+ "reference": 0.8036348918022429,
31
+ "other": 0.8078700047385879
32
+ },
33
+ "auroc_sarcasm_both": {
34
+ "reference": 0.8481948120698782,
35
+ "other": 0.8499735309687665
36
+ },
37
+ "auroc_sarcasm_voice_only": {
38
+ "reference": 0.9116690614837782,
39
+ "other": 0.9187022612115253
40
+ },
41
+ "tone_macro_f1": {
42
+ "reference": 0.5146829335401363,
43
+ "other": 0.49449348337582855,
44
+ "rows": 1546
45
+ }
46
+ }
47
+ }
voice-reader-base/config.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "window_secs": 8.0,
3
+ "tones": [
4
+ "positive",
5
+ "neutral",
6
+ "sad",
7
+ "frustrated"
8
+ ],
9
+ "outputs": [
10
+ "tone",
11
+ "sarcasm",
12
+ "laughter",
13
+ "arousal",
14
+ "speech_rate",
15
+ "upset",
16
+ "sad"
17
+ ],
18
+ "sarcasm_tau": 0.06803736669700389,
19
+ "calibration": {
20
+ "sarcasm": {
21
+ "a": 1.678951463802178,
22
+ "b": 0.12539543584903914
23
+ },
24
+ "laughter": {
25
+ "a": 0.8057154083813632,
26
+ "b": -0.09714794868282932
27
+ },
28
+ "tone_temperature": 0.8488899224103036
29
+ },
30
+ "run": "v3-base-sad",
31
+ "init": "laion-base",
32
+ "best_epoch": 7,
33
+ "note": "sarcasm_tau is the dev 2 % FPR threshold on the Platt-calibrated probability; tone temperature and laughter Platt curve fitted on dev"
34
+ }
voice-reader-base/model.int8.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5c25605ea019521853c92347b48ced6f7706c14f21183f5de9da2a607c9fae95
3
+ size 28567980
voice-reader-base/model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:99975890aa7f1b0648af82a480233fdf641cee04bfee84370d0bed9c39d63313
3
+ size 109728612
voice-reader-tiny/MANIFEST.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "release": "rc1",
3
+ "folder": "voice-reader-tiny",
4
+ "run": "student-tiny-v3",
5
+ "staged": "2026-11-02T20:40:59+00:00",
6
+ "files": {
7
+ "model.onnx": {
8
+ "sha256": "873b5e5991f607483331ca7078b175dadaee3a1ec2046507ae65f008db64cad0",
9
+ "bytes": 50202707
10
+ },
11
+ "model.int8.onnx": {
12
+ "sha256": "1823e7d7e861586f3981ffa470150a5c3653f5d017df97a49e606ccc4e6c6c42",
13
+ "bytes": 13332966
14
+ },
15
+ "config.json": {
16
+ "sha256": "79582d3816d963a092dd4d71da61d4444b3127659f846076223d20f240fc821d",
17
+ "bytes": 655
18
+ }
19
+ },
20
+ "parity_int8": {
21
+ "windows": 2000,
22
+ "tone_argmax_agreement": 0.977,
23
+ "tone_max_abs_dp": 0.2388080656528473,
24
+ "sarcasm_p95_abs_dp": 0.010020742192864418,
25
+ "sarcasm_max_abs_dp": 0.04528002440929413,
26
+ "laughter_p95_abs_dp": 0.0021592299453914165,
27
+ "arousal_p95_abs_d": 0.019707761704921722,
28
+ "speech_rate_p95_rel_d": 0.03602607175707817,
29
+ "auroc_sarcasm": {
30
+ "reference": 0.7930224293160638,
31
+ "other": 0.7871979150213236
32
+ },
33
+ "auroc_sarcasm_both": {
34
+ "reference": 0.8221916357861302,
35
+ "other": 0.819777660137639
36
+ },
37
+ "auroc_sarcasm_voice_only": {
38
+ "reference": 0.8642516826741284,
39
+ "other": 0.8534371927701732
40
+ },
41
+ "tone_macro_f1": {
42
+ "reference": 0.45427391583654375,
43
+ "other": 0.46146068669445456,
44
+ "rows": 1546
45
+ }
46
+ }
47
+ }
voice-reader-tiny/config.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "window_secs": 8.0,
3
+ "tones": [
4
+ "positive",
5
+ "neutral",
6
+ "sad",
7
+ "frustrated"
8
+ ],
9
+ "outputs": [
10
+ "tone",
11
+ "sarcasm",
12
+ "laughter",
13
+ "arousal",
14
+ "speech_rate",
15
+ "upset"
16
+ ],
17
+ "sarcasm_tau": 0.06590602517797733,
18
+ "calibration": {
19
+ "sarcasm": {
20
+ "a": 1.7731141168712765,
21
+ "b": 0.06321434642450725
22
+ },
23
+ "laughter": {
24
+ "a": 0.8303770152621825,
25
+ "b": -0.17392277961573782
26
+ },
27
+ "tone_temperature": 0.7673753909755566
28
+ },
29
+ "run": "student-tiny-v3",
30
+ "init": "laion-tiny",
31
+ "best_epoch": 10,
32
+ "note": "sarcasm_tau is the dev 2 % FPR threshold on the Platt-calibrated probability; tone temperature and laughter Platt curve fitted on dev"
33
+ }
voice-reader-tiny/model.int8.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1823e7d7e861586f3981ffa470150a5c3653f5d017df97a49e606ccc4e6c6c42
3
+ size 13332966
voice-reader-tiny/model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:873b5e5991f607483331ca7078b175dadaee3a1ec2046507ae65f008db64cad0
3
+ size 50202707