-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathvoice-guesser.html
94 lines (81 loc) · 2.74 KB
/
voice-guesser.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<h1 id=e_text>[click to start]</h1>
<script>
let active = false;
function reset() {
document.body.style.backgroundColor = '#aaa';
}
reset();
function set_text(text) {
e_text.textContent = `[${text}]`;
console.log(e_text.textContent);
}
async function toggle() {
if (active) {
active = false;
set_text('stopping...');
return;
}
active = true;
set_text('starting...');
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const ac = new AudioContext();
const mss = ac.createMediaStreamSource(stream);
const an = new AnalyserNode(ac);
an.fftSize = 2048 * 4;
an.smoothingTimeConstant = 0.6;
mss.connect(an);
const freq_interval = (1 / (an.frequencyBinCount - 1)) * (ac.sampleRate / 2);
const MAX_FREQ = 1000;
const max_bins_needed = Math.ceil(MAX_FREQ / freq_interval);
const freqs = new Float32Array(max_bins_needed);
console.log({fftSize: an.fftSize, freq_interval, max_bins_needed});
while (active) {
await new Promise(go => setTimeout(go, 300));
an.getFloatFrequencyData(freqs);
let kv_list = Object.entries(freqs).map(([i, db]) => {
const freq = i * freq_interval;
return { freq, db };
});
//kv_list = kv_list.filter(kv => kv[1] != -Infinity);
kv_list = kv_list.sort((a, b) => b.db - a.db);
//console.log(...kv_list.slice(0, 3));
const max_db = kv_list[0].db;
const ABS_CUTOFF = -50;
const REL_CUTOFF = -7;
const cutoff = Math.max(max_db + REL_CUTOFF, ABS_CUTOFF);
let signals = kv_list.filter(x => x.db >= cutoff);
if (!signals.length || signals.length > 10) {
//console.log('likely noise:', kv_list[0]);
continue;
}
signals = signals.sort((a, b) => a.freq - b.freq);
//console.log(signals.length, ':', ...signals);
const root = signals[0];
const MALE_RANGE = [85, 155];
const FEMALE_RANGE = [165, 255];
const CROSSOVER = (MALE_RANGE[1] + FEMALE_RANGE[0]) / 2
const RANGE = Math.min(MALE_RANGE[1] - MALE_RANGE[0],
FEMALE_RANGE[1] - FEMALE_RANGE[0]);
const femme = (root.freq - CROSSOVER) / (RANGE / 2);
//console.log({femme}, root);
const femme_percent = Math.max(0, Math.min(50 + 50 * femme, 100));
const SOFT_BLUE = '#5bcffa';
const SOFT_PINK = '#f5abb9';
document.body.style.backgroundColor =
`color-mix(in srgb, ${SOFT_PINK} ${femme_percent}%, ${SOFT_BLUE})`;
set_text(`femme: ${femme.toFixed(2)} @ ${root.freq|0}Hz (${root.db|0}db)`);
}
ac.close();
set_text('stopped');
reset();
}
document.addEventListener('click', toggle)
</script>
</body>
</html>