Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(new tool) Microphone tester #1340

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
5 changes: 1 addition & 4 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,18 @@ declare module '@vue/runtime-core' {
MenuIconItem: typeof import('./src/components/MenuIconItem.vue')['default']
MenuLayout: typeof import('./src/components/MenuLayout.vue')['default']
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MicTester: typeof import('./src/tools/mic-tester/mic-tester.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDivider: typeof import('naive-ui')['NDivider']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NH1: typeof import('naive-ui')['NH1']
NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon']
NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu']
NSpace: typeof import('naive-ui')['NSpace']
NTable: typeof import('naive-ui')['NTable']
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
Expand Down
5 changes: 5 additions & 0 deletions locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ tools:
mime-types:
title: MIME-Typen
description: Konvertiere MIME-Typen in Erweiterungen und umgekehrt.
mic-tester:
title: Mikrofonprufung
description: Wiedergabe und Visualisierung des Tons von Ihrem Mikrofon, mit einer Sekunde Verzögerung hinzugefügt
start-button-text: Mikrofon-Wiedergabe Starten
stop-button-text: Mikrofon-Wiedergabe Stoppen
toml-to-json:
title: TOML zu JSON
description: Parse und konvertiere TOML zu JSON.
Expand Down
6 changes: 6 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@ tools:
title: MIME types
description: Convert MIME types to file extensions and vice-versa.

mic-tester:
title: Microphone Tester
description: Replay and Visualize sound from Your microphone, with added one second of delay
start-button-text: Start replaying microphone
stop-button-text: Stop replaying microphone

toml-to-json:
title: TOML to JSON
description: Parse and convert TOML to JSON.
Expand Down
87 changes: 87 additions & 0 deletions locales/pl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
home:
categories:
newestTools: 'Najnowsze narzędzia'
favoriteTools: 'Twoje ulubione narzędzia'
allTools: 'Wszystkie narzędzia'
subtitle: 'Narzędzia dla programistów'
toggleMenu: 'Menu'
home: Strona główna
uiLib: 'UI Lib'
buyMeACoffee: 'Wesprzyj IT-Tools'
follow:
title: 'Podoba Ci się it-tools?'
p1: 'Wesprzyj nas gwiazdką na'
githubRepository: "repozytorium GitHub IT-Tools"
p2: 'lub śledź nas na'
twitterAccount: "koncie Twitter IT-Tools"
thankYou: 'Dziękujemy!'
nav:
github: 'Repozytorium GitHub'
githubRepository: "Repozytorium GitHub IT-Tools"
twitter: 'Konto Twitter'
twitterAccount: "Konto Twitter IT-Tools"
about: "O IT-Tools"
aboutLabel: 'O nas'
darkMode: 'Tryb ciemny'
lightMode: 'Tryb jasny'
mode: 'Przełącz tryb ciemny/jasny'
about:
content: >
# O IT-Tools

Ta wspaniała strona, stworzona z ❤ przez [Corentina Thomasseta](https://corentin.tech?utm_source=it-tools&utm_medium=about), zbiera przydatne narzędzia dla programistów i osób pracujących w IT. Jeśli uznasz ją za pomocną, nie zapomnij się nią podzielić i dodać do ulubionych!

IT Tools jest open-source (na licencji MIT) i darmowe, i takie pozostanie, ale koszty związane z jego hostingiem i odnawianiem domeny spoczywają na mnie. Jeśli chcesz wesprzeć moją pracę i zmotywować mnie do dodania kolejnych narzędzi, zachęcam do [wsparcia](https://www.buymeacoffee.com/cthmsst).

## Technologie

IT Tools zostało stworzone w Vue.js (Vue 3) z wykorzystaniem biblioteki komponentów Naive UI i jest hostowane oraz ciągle wdrażane przez Vercel. Niektóre narzędzia korzystają z zewnętrznych bibliotek open-source, pełną listę znajdziesz w pliku [package.json](https://github.com/CorentinTh/it-tools/blob/main/package.json) repozytorium.

## Znalazłeś błąd? Brakuje jakiegoś narzędzia?

Jeśli potrzebujesz narzędzia, które nie jest jeszcze dostępne, a uważasz, że mogłoby być przydatne, zapraszam do zgłoszenia propozycji funkcji w [sekcji issue](https://github.com/CorentinTh/it-tools/issues/new/choose) repozytorium GitHub.

404:
notFound: '404 Nie znaleziono'
sorry: "Przepraszamy, ta strona nie istnieje"
maybe: 'Może to problem z cachem, spróbuj wymusić odświeżenie?'
backHome: "Powrót na stronę główną"
toolCard:
new: Nowe
search:
label: Szukaj
tools:
categories:
favorite-tools: 'Twoje ulubione narzędzia'
crypto: Kryptografia
converter: Konwerter
web: Web
images and videos: 'Obrazy i wideo'
development: Programowanie
network: Sieć
math: Matematyka
measurement: Pomiary
text: Tekst
data: Dane

token-generator:
title: Generator tokenów
description: >-
Generuje losowy ciąg znaków z wybranych przez Ciebie znaków: wielkie
lub małe litery, cyfry i/lub symbole.
uppercase: Wielkie litery (ABC...)
lowercase: Małe litery (abc...)
numbers: Cyfry (123...)
symbols: Symbole (!-;...)
button:
copy: Kopiuj
refresh: Odśwież
copied: Token został skopiowany
length: Długość
tokenPlaceholder: Token...

mic-tester:
title: Tester mikrofonu
description: Odtwórz i wizualizuj dźwięk z Twojego mikrofonu, z dodaną jedną sekundą opóźnienia
start-button-text: Odtwarzaj mikrofonu
stop-button-text: Zatrzymaj odtwarzanie mikrofonu
1 change: 1 addition & 0 deletions src/modules/i18n/components/locale-selector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const localesLong: Record<string, string> = {
de: 'Deutsch',
es: 'Español',
fr: 'Français',
pl: 'Polski',
pt: 'Português',
ru: 'Русский',
uk: 'Українська',
Expand Down
2 changes: 2 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import { tool as uuidGenerator } from './uuid-generator';
import { tool as macAddressLookup } from './mac-address-lookup';
import { tool as xmlFormatter } from './xml-formatter';
import { tool as yamlViewer } from './yaml-viewer';
import { tool as micTester } from './mic-tester';

export const toolsByCategory: ToolCategory[] = [
{
Expand Down Expand Up @@ -137,6 +138,7 @@ export const toolsByCategory: ToolCategory[] = [
httpStatusCodes,
jsonDiff,
safelinkDecoder,
micTester,
],
},
{
Expand Down
12 changes: 12 additions & 0 deletions src/tools/mic-tester/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Microphone } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';

export const tool = defineTool({
name: translate('tools.mic-tester.title'),
path: '/mic-tester',
description: translate('tools.mic-tester.description'),
keywords: ['mic', 'microphone', 'test', 'check', 'troubleshoot', 'sound'],
component: () => import('./mic-tester.vue'),
icon: Microphone,
});
91 changes: 91 additions & 0 deletions src/tools/mic-tester/mic-tester.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { onBeforeUnmount, ref } from 'vue';

interface IMessageSender {
error: (...messages: any[]) => void
}

export function useMicrophoneService(messageSender: IMessageSender) {
let audioContext: AudioContext | null = null;
let delayNode: DelayNode | null = null;
let sourceNode: MediaStreamAudioSourceNode | null = null;
let analyserNode: AnalyserNode | null = null;
let stream: MediaStream | null = null;

const isPlaying = ref(false);
const loudnessLevel = ref(0); // Observable for loudness

// Measure loudness and update loudness bar
function measureLoudness() {
const dataArray = new Uint8Array(analyserNode!.frequencyBinCount);

const updateLoudness = () => {
analyserNode!.getByteFrequencyData(dataArray);

// Calculate average loudness
let sum = 0;
dataArray.forEach(value => sum += value);
const average = sum / dataArray.length;

// Update the observable loudness level
loudnessLevel.value = average;

if (isPlaying.value) {
requestAnimationFrame(updateLoudness);
}
};
updateLoudness();
};

const startMicReplay = async () => {
if (!audioContext) {
audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
}

try {
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
}
catch (err) {
console.error('Microphone access denied:', err);
messageSender.error('Microphone access denied (the error is also in the console):', err);
return;
}

sourceNode = audioContext.createMediaStreamSource(stream);
delayNode = audioContext.createDelay(1.0);
delayNode.delayTime.value = 1.0;

analyserNode = audioContext.createAnalyser();
analyserNode.fftSize = 256;

// Connect nodes: mic -> delay -> speakers
sourceNode.connect(delayNode);
delayNode.connect(audioContext.destination);
sourceNode.connect(analyserNode);

isPlaying.value = true;
measureLoudness();
};

function stopMicReplay() {
if (audioContext && stream) {
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
audioContext.close();
audioContext = null;
isPlaying.value = false;
loudnessLevel.value = 0;
}
};

// Cleanup on service destruction
onBeforeUnmount(() => {
stopMicReplay();
});

return {
startMicReplay,
stopMicReplay,
loudnessLevel,
isPlaying,
};
}
50 changes: 50 additions & 0 deletions src/tools/mic-tester/mic-tester.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { useMessage } from 'naive-ui';
import { useMicrophoneService } from './mic-tester.service';

const message = useMessage();

const { t } = useI18n();
const { startMicReplay, stopMicReplay, loudnessLevel, isPlaying } = useMicrophoneService(message);
</script>

<template>
<div>
<c-card>
<div class="control-buttons">
<c-button :disabled="isPlaying" @click="startMicReplay">
{{ t('tools.mic-tester.start-button-text') }}
</c-button>
<c-button :disabled="!isPlaying" @click="stopMicReplay">
{{ t('tools.mic-tester.stop-button-text') }}
</c-button>
</div>

<!-- Loudness Meter -->
<div id="loudnessMeter">
<div id="loudnessBar" :style="{ width: `${loudnessLevel}%` }" />
</div>
</c-card>
</div>
</template>

<style scoped>
#loudnessMeter {
width: 100%;
height: 30px;
background-color: rgba(46, 51, 56, 0.05);
margin-top: 20px;
position: relative;
}
#loudnessBar {
height: 100%;
background: linear-gradient(48deg, rgba(37, 99, 108, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(20, 160, 88, 1) 100%);
}
.control-buttons {
display: flex;
gap: 10px;
margin-bottom: 20px;
justify-content: space-between;
}
</style>
Loading