Skip to content

Commit bd9afc7

Browse files
committed
Define and implement mic-tester tool
1 parent f962c41 commit bd9afc7

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

src/tools/mic-tester/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Microphone } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
import { translate } from '@/plugins/i18n.plugin';
4+
5+
export const tool = defineTool({
6+
name: translate('tools.mic-tester.title'),
7+
path: '/mic-tester',
8+
description: translate('tools.mic-tester.description'),
9+
keywords: ['mic', 'microphone', 'test', 'check', 'troubleshoot', 'sound'],
10+
component: () => import('./mic-tester.vue'),
11+
icon: Microphone,
12+
});

src/tools/mic-tester/mic-tester.vue

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<script setup lang="ts">
2+
import { ref, onBeforeUnmount } from 'vue';
3+
import { useI18n } from 'vue-i18n';
4+
import { Microphone } from '@vicons/tabler';
5+
const { t } = useI18n();
6+
7+
// Audio-related variables
8+
let audioContext: AudioContext | null = null;
9+
let delayNode: DelayNode | null = null;
10+
let sourceNode: MediaStreamAudioSourceNode | null = null;
11+
let analyserNode: AnalyserNode | null = null;
12+
let stream: MediaStream | null = null;
13+
14+
const isPlaying = ref(false);
15+
const loudnessBarWidth = ref(0);
16+
17+
const startMicReplayAndLoudMeter = async () => {
18+
if (!audioContext) {
19+
audioContext = new (window.AudioContext || window.webkitAudioContext)();
20+
}
21+
22+
try {
23+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
24+
} catch (err) {
25+
console.error('Microphone access denied:', err);
26+
alert('Microphone access denied (the error is also in the console):', err);
27+
return;
28+
}
29+
30+
sourceNode = audioContext.createMediaStreamSource(stream);
31+
delayNode = audioContext.createDelay(1.0);
32+
delayNode.delayTime.value = 1.0;
33+
34+
analyserNode = audioContext.createAnalyser();
35+
analyserNode.fftSize = 256;
36+
37+
// Connect nodes: mic -> delay -> speakers
38+
sourceNode.connect(delayNode);
39+
delayNode.connect(audioContext.destination);
40+
sourceNode.connect(analyserNode);
41+
42+
isPlaying.value = true;
43+
measureLoudness();
44+
};
45+
46+
const stopMicReplayAndLoudMeter = () => {
47+
if (audioContext && stream) {
48+
const tracks = stream.getTracks();
49+
tracks.forEach(track => track.stop());
50+
audioContext.close();
51+
audioContext = null;
52+
isPlaying.value = false;
53+
loudnessBarWidth.value = 0;
54+
}
55+
};
56+
57+
// Measure loudness and update loudness bar
58+
const measureLoudness = () => {
59+
const dataArray = new Uint8Array(analyserNode!.frequencyBinCount);
60+
61+
const updateLoudness = () => {
62+
analyserNode!.getByteFrequencyData(dataArray);
63+
64+
// Calculate average loudness
65+
let sum = 0;
66+
dataArray.forEach(value => sum += value);
67+
const average = sum / dataArray.length;
68+
69+
// Update loudness bar width based on loudness level
70+
loudnessBarWidth.value = average;
71+
72+
if (isPlaying.value) {
73+
requestAnimationFrame(updateLoudness);
74+
}
75+
};
76+
77+
updateLoudness();
78+
};
79+
80+
// Cleanup on component unmount
81+
onBeforeUnmount(() => {
82+
stopMicReplay();
83+
});
84+
</script>
85+
86+
<template>
87+
<div>
88+
<c-card>
89+
<div class="control-buttons">
90+
<c-button @click="startMicReplayAndLoudMeter" :disabled="isPlaying">{{ t('tools.mic-tester.start-button-text') }}</c-button>
91+
<c-button @click="stopMicReplayAndLoudMeter" :disabled="!isPlaying">{{ t('tools.mic-tester.stop-button-text') }}</c-button>
92+
</div>
93+
94+
<!-- Loudness Meter -->
95+
<div id="loudnessMeter">
96+
<div id="loudnessBar" :style="{ width: loudnessBarWidth + '%' }"></div>
97+
</div>
98+
</c-card>
99+
</div>
100+
</template>
101+
102+
<style scoped>
103+
#loudnessMeter {
104+
width: 100%;
105+
height: 30px;
106+
background-color: rgba(46, 51, 56, 0.05);
107+
margin-top: 20px;
108+
position: relative;
109+
}
110+
#loudnessBar {
111+
height: 100%;
112+
background: linear-gradient(48deg, rgba(37, 99, 108, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(20, 160, 88, 1) 100%);
113+
}
114+
.control-buttons {
115+
display: flex;
116+
gap: 10px;
117+
margin-bottom: 20px;
118+
justify-content: space-between;
119+
}
120+
</style>
121+

0 commit comments

Comments
 (0)