@jjlmoya/utils-hardware 1.28.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +4 -1
  4. package/src/index.ts +1 -0
  5. package/src/tests/locale_completeness.test.ts +1 -1
  6. package/src/tests/tool_validation.test.ts +1 -1
  7. package/src/tool/subwooferCrossoverTest/bibliography.astro +14 -0
  8. package/src/tool/subwooferCrossoverTest/bibliography.ts +16 -0
  9. package/src/tool/subwooferCrossoverTest/component.astro +253 -0
  10. package/src/tool/subwooferCrossoverTest/entry.ts +29 -0
  11. package/src/tool/subwooferCrossoverTest/i18n/de.ts +188 -0
  12. package/src/tool/subwooferCrossoverTest/i18n/en.ts +188 -0
  13. package/src/tool/subwooferCrossoverTest/i18n/es.ts +188 -0
  14. package/src/tool/subwooferCrossoverTest/i18n/fr.ts +188 -0
  15. package/src/tool/subwooferCrossoverTest/i18n/id.ts +188 -0
  16. package/src/tool/subwooferCrossoverTest/i18n/it.ts +188 -0
  17. package/src/tool/subwooferCrossoverTest/i18n/ja.ts +188 -0
  18. package/src/tool/subwooferCrossoverTest/i18n/ko.ts +188 -0
  19. package/src/tool/subwooferCrossoverTest/i18n/nl.ts +188 -0
  20. package/src/tool/subwooferCrossoverTest/i18n/pl.ts +188 -0
  21. package/src/tool/subwooferCrossoverTest/i18n/pt.ts +188 -0
  22. package/src/tool/subwooferCrossoverTest/i18n/ru.ts +188 -0
  23. package/src/tool/subwooferCrossoverTest/i18n/sv.ts +188 -0
  24. package/src/tool/subwooferCrossoverTest/i18n/tr.ts +188 -0
  25. package/src/tool/subwooferCrossoverTest/i18n/zh.ts +188 -0
  26. package/src/tool/subwooferCrossoverTest/index.ts +11 -0
  27. package/src/tool/subwooferCrossoverTest/logic.ts +30 -0
  28. package/src/tool/subwooferCrossoverTest/seo.astro +15 -0
  29. package/src/tool/subwooferCrossoverTest/subwoofer-crossover-test.css +282 -0
  30. package/src/tool/subwooferCrossoverTest/ui.ts +20 -0
  31. package/src/tools.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-hardware",
3
- "version": "1.28.0",
3
+ "version": "1.29.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -8,6 +8,7 @@ import { mouseDoubleClickTest } from '../tool/mouseDoubleClickTest/index';
8
8
  import { mouseScrollTest } from '../tool/mouseScrollTest/index';
9
9
  import { estimadorSaludBateria } from '../tool/batteryHealthEstimator/index';
10
10
  import { toneGenerator } from '../tool/toneGenerator/index';
11
+ import { subwooferCrossoverTest } from '../tool/subwooferCrossoverTest/index';
11
12
  import { refreshRateDetector } from '../tool/refreshRateDetector/index';
12
13
  import { monitorGhostingTest } from '../tool/monitorGhostingTest/index';
13
14
  import { spectrumCanvas } from '../tool/colorAccuracyTest/index';
@@ -21,7 +22,7 @@ import { mobileSensorTest } from '../tool/mobileSensorTest/index';
21
22
 
22
23
  export const hardwareCategory: HardwareCategoryEntry = {
23
24
  icon: 'mdi:memory',
24
- tools: [pixelesPantalla, testTeclado, keyboardChatterTest, testMando, probadorVibracionMando, testRaton, mouseDoubleClickTest, mouseScrollTest, estimadorSaludBateria, toneGenerator, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator, usbPowerBudgetCalculator, mobileSensorTest, stereoAudioTest, webBluetoothBleScanner, webUsbSerialMonitor],
25
+ tools: [pixelesPantalla, testTeclado, keyboardChatterTest, testMando, probadorVibracionMando, testRaton, mouseDoubleClickTest, mouseScrollTest, estimadorSaludBateria, toneGenerator, subwooferCrossoverTest, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator, usbPowerBudgetCalculator, mobileSensorTest, stereoAudioTest, webBluetoothBleScanner, webUsbSerialMonitor],
25
26
  i18n: {
26
27
  en: () => import('./i18n/en').then((m) => m.content),
27
28
  es: () => import('./i18n/es').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -16,6 +16,8 @@ export { mouseScrollTest } from './tool/mouseScrollTest/entry';
16
16
  export type { MouseScrollTestLocaleContent } from './tool/mouseScrollTest/entry';
17
17
  export { toneGenerator } from './tool/toneGenerator/entry';
18
18
  export type { ToneGeneratorLocaleContent } from './tool/toneGenerator/entry';
19
+ export { subwooferCrossoverTest } from './tool/subwooferCrossoverTest/entry';
20
+ export type { SubwooferCrossoverTestLocaleContent } from './tool/subwooferCrossoverTest/entry';
19
21
  export { refreshRateDetector } from './tool/refreshRateDetector/entry';
20
22
  export type { RefreshRateDetectorLocaleContent } from './tool/refreshRateDetector/entry';
21
23
  export { monitorGhostingTest } from './tool/monitorGhostingTest/entry';
@@ -46,6 +48,7 @@ import { testRaton } from './tool/mousePollingTest/entry';
46
48
  import { mouseDoubleClickTest } from './tool/mouseDoubleClickTest/entry';
47
49
  import { mouseScrollTest } from './tool/mouseScrollTest/entry';
48
50
  import { toneGenerator } from './tool/toneGenerator/entry';
51
+ import { subwooferCrossoverTest } from './tool/subwooferCrossoverTest/entry';
49
52
  import { refreshRateDetector } from './tool/refreshRateDetector/entry';
50
53
  import { monitorGhostingTest } from './tool/monitorGhostingTest/entry';
51
54
  import { spectrumCanvas } from './tool/colorAccuracyTest/entry';
@@ -56,4 +59,4 @@ import { keyboardChatterTest } from './tool/keyboardChatterTest/entry';
56
59
  import { webUsbSerialMonitor } from './tool/webUsbSerialMonitor/entry';
57
60
  import { usbPowerBudgetCalculator } from './tool/usbPowerBudgetCalculator/entry';
58
61
  import { mobileSensorTest } from './tool/mobileSensorTest/entry';
59
- export const ALL_ENTRIES = [estimadorSaludBateria, pixelesPantalla, testMando, probadorVibracionMando, testTeclado, keyboardChatterTest, testRaton, mouseDoubleClickTest, mouseScrollTest, toneGenerator, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator, usbPowerBudgetCalculator, mobileSensorTest, stereoAudioTest, webBluetoothBleScanner, webUsbSerialMonitor];
62
+ export const ALL_ENTRIES = [estimadorSaludBateria, pixelesPantalla, testMando, probadorVibracionMando, testTeclado, keyboardChatterTest, testRaton, mouseDoubleClickTest, mouseScrollTest, toneGenerator, subwooferCrossoverTest, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator, usbPowerBudgetCalculator, mobileSensorTest, stereoAudioTest, webBluetoothBleScanner, webUsbSerialMonitor];
package/src/index.ts CHANGED
@@ -26,6 +26,7 @@ export { MOUSE_DOUBLE_CLICK_TEST_TOOL } from './tool/mouseDoubleClickTest/index'
26
26
  export { MOUSE_SCROLL_TEST_TOOL } from './tool/mouseScrollTest/index';
27
27
  export { ESTIMADOR_SALUD_BATERIA_TOOL } from './tool/batteryHealthEstimator/index';
28
28
  export { TONE_GENERATOR_TOOL } from './tool/toneGenerator/index';
29
+ export { SUBWOOFER_CROSSOVER_TEST_TOOL } from './tool/subwooferCrossoverTest/index';
29
30
  export { REFRESH_RATE_DETECTOR_TOOL } from './tool/refreshRateDetector/index';
30
31
  export { MONITOR_GHOSTING_TEST_TOOL } from './tool/monitorGhostingTest/index';
31
32
  export { SPECTRUM_CANVAS_TOOL } from './tool/colorAccuracyTest/index';
@@ -22,7 +22,7 @@ describe('Locale Completeness Validation', () => {
22
22
  });
23
23
 
24
24
  it('all tools registered', () => {
25
- expect(ALL_TOOLS.length).toBe(19);
25
+ expect(ALL_TOOLS.length).toBe(20);
26
26
  });
27
27
  });
28
28
 
@@ -5,7 +5,7 @@ import { hardwareCategory } from '../data';
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
7
  it('should have all tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(19);
8
+ expect(ALL_TOOLS.length).toBe(20);
9
9
  });
10
10
 
11
11
  it('hardwareCategory should be defined', () => {
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import type { KnownLocale } from '../../types';
4
+ import { subwooferCrossoverTest } from './index';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await subwooferCrossoverTest.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && content.bibliography.length > 0 && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,16 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Dolby - Speaker setup guides for home theater',
6
+ url: 'https://www.dolby.com/about/support/guide/speaker-setup-guides/',
7
+ },
8
+ {
9
+ name: 'THX - Speaker and subwoofer placement',
10
+ url: 'https://www.thx.com/questions/where-should-i-position-my-speakers-and-subwoofer/',
11
+ },
12
+ {
13
+ name: 'SVS - Subwoofer room placement and tuning',
14
+ url: 'https://www.svsound.com/blogs/subwoofer-setup-and-tuning',
15
+ },
16
+ ];
@@ -0,0 +1,253 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+ import type { SubwooferCrossoverTestUI } from './ui';
4
+ import { Icon } from 'astro-icon/components';
5
+ import './subwoofer-crossover-test.css';
6
+
7
+ interface Props {
8
+ locale?: KnownLocale;
9
+ ui?: Record<string, unknown>;
10
+ }
11
+
12
+ const { ui } = Astro.props;
13
+ const t = (ui ?? {}) as SubwooferCrossoverTestUI;
14
+
15
+ const S = JSON.stringify({
16
+ ready: t.statusReady,
17
+ running: t.statusRunning,
18
+ stopped: t.statusStopped,
19
+ start: t.start,
20
+ stop: t.stop,
21
+ safeStart: t.safeStart,
22
+ dropoutEmpty: t.dropoutEmpty,
23
+ crossoverEstimate: t.crossoverEstimate,
24
+ });
25
+ ---
26
+
27
+ <div id="sct-root" class="sct-shell" data-s={S}>
28
+ <section class="sct-console" aria-label={t.sweepLabel}>
29
+ <div class="sct-stage">
30
+ <div class="sct-orbit" aria-hidden="true">
31
+ <div class="sct-ring sct-ring-a"></div>
32
+ <div class="sct-ring sct-ring-b"></div>
33
+ <div class="sct-driver">
34
+ <Icon name="mdi:speaker" class="sct-speaker-icon" />
35
+ </div>
36
+ </div>
37
+
38
+ <div class="sct-frequency-panel">
39
+ <span id="sct-status" class="sct-status">{t.statusReady}</span>
40
+ <output id="sct-frequency" class="sct-frequency" aria-live="polite">200 Hz</output>
41
+ <div class="sct-meter" aria-hidden="true">
42
+ <span id="sct-meter-fill" class="sct-meter-fill"></span>
43
+ </div>
44
+ <div class="sct-readouts">
45
+ <span>{t.targetFrequency}: <strong>10 Hz</strong></span>
46
+ <span>{t.elapsed}: <strong id="sct-elapsed">0.0s</strong></span>
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
+ <div class="sct-controls">
52
+ <button id="sct-toggle" class="sct-primary" type="button">
53
+ <Icon name="mdi:play" class="sct-button-icon" />
54
+ <span>{t.start}</span>
55
+ </button>
56
+ <button id="sct-mark" class="sct-secondary" type="button" disabled>
57
+ <Icon name="mdi:map-marker-radius" class="sct-button-icon" />
58
+ <span>{t.markDropout}</span>
59
+ </button>
60
+ <button id="sct-reset" class="sct-ghost" type="button">
61
+ <Icon name="mdi:restart" class="sct-button-icon" />
62
+ <span>{t.reset}</span>
63
+ </button>
64
+ </div>
65
+
66
+ <div class="sct-tuning">
67
+ <label class="sct-slider-wrap" for="sct-volume">
68
+ <span>{t.volume}</span>
69
+ <input id="sct-volume" class="sct-slider" type="range" min="0" max="0.6" value="0.22" step="0.01" />
70
+ </label>
71
+ <label class="sct-slider-wrap" for="sct-duration">
72
+ <span>{t.duration}</span>
73
+ <input id="sct-duration" class="sct-slider" type="range" min="20" max="90" value="45" step="5" />
74
+ </label>
75
+ </div>
76
+
77
+ <div class="sct-result">
78
+ <div>
79
+ <span class="sct-result-label">{t.dropoutLabel}</span>
80
+ <strong id="sct-dropout">{t.dropoutEmpty}</strong>
81
+ </div>
82
+ <p id="sct-note">{t.safeStart}</p>
83
+ </div>
84
+
85
+ </section>
86
+
87
+ <script>
88
+ const root = document.getElementById('sct-root');
89
+ const strings = JSON.parse(root?.dataset?.s ?? '{}') as Record<string, string>;
90
+ const frequencyDisplay = document.getElementById('sct-frequency');
91
+ const elapsedDisplay = document.getElementById('sct-elapsed');
92
+ const meterFill = document.getElementById('sct-meter-fill') as HTMLSpanElement | null;
93
+ const statusDisplay = document.getElementById('sct-status');
94
+ const toggleButton = document.getElementById('sct-toggle') as HTMLButtonElement | null;
95
+ const markButton = document.getElementById('sct-mark') as HTMLButtonElement | null;
96
+ const resetButton = document.getElementById('sct-reset') as HTMLButtonElement | null;
97
+ const volumeInput = document.getElementById('sct-volume') as HTMLInputElement | null;
98
+ const durationInput = document.getElementById('sct-duration') as HTMLInputElement | null;
99
+ const dropoutDisplay = document.getElementById('sct-dropout');
100
+ const noteDisplay = document.getElementById('sct-note');
101
+
102
+ const startHz = 200;
103
+ const endHz = 10;
104
+ let audioContext: AudioContext | null = null;
105
+ let oscillator: OscillatorNode | null = null;
106
+ let gainNode: GainNode | null = null;
107
+ let startTime = 0;
108
+ let animationId = 0;
109
+ let running = false;
110
+ let currentHz = startHz;
111
+
112
+ function formatHz(value: number): string {
113
+ return value >= 100 ? `${Math.round(value)} Hz` : `${value.toFixed(1)} Hz`;
114
+ }
115
+
116
+ function sweepFrequency(elapsed: number, duration: number): number {
117
+ const progress = Math.min(1, Math.max(0, elapsed / duration));
118
+ return startHz * Math.pow(endHz / startHz, progress);
119
+ }
120
+
121
+ function setButtonPlaying(isPlaying: boolean): void {
122
+ if (!toggleButton) return;
123
+ const label = toggleButton.querySelector('span');
124
+ toggleButton.dataset.playing = isPlaying ? 'true' : 'false';
125
+ if (label) label.textContent = isPlaying ? strings.stop! : strings.start!;
126
+ }
127
+
128
+ function getAudioContext(): AudioContext | null {
129
+ if (audioContext) return audioContext;
130
+ const win = window as typeof window & { webkitAudioContext?: typeof AudioContext };
131
+ const Context = win.AudioContext ?? win.webkitAudioContext;
132
+ if (!Context) return null;
133
+ audioContext = new Context();
134
+ return audioContext;
135
+ }
136
+
137
+ function getElapsed(): { elapsed: number; progress: number } {
138
+ const duration = Number(durationInput?.value ?? 45);
139
+ const elapsed = running && audioContext ? audioContext.currentTime - startTime : 0;
140
+ currentHz = running ? sweepFrequency(elapsed, duration) : currentHz;
141
+ const progress = Math.min(1, Math.max(0, elapsed / duration));
142
+ return { elapsed, progress };
143
+ }
144
+
145
+ function refreshFrequencyDisplay(elapsed: number, progress: number): void {
146
+ if (frequencyDisplay) frequencyDisplay.textContent = formatHz(currentHz);
147
+ if (elapsedDisplay) elapsedDisplay.textContent = `${elapsed.toFixed(1)}s`;
148
+ if (meterFill) meterFill.style.transform = `scaleX(${1 - progress})`;
149
+ }
150
+
151
+ function updateVisuals(): void {
152
+ const { elapsed, progress } = getElapsed();
153
+ refreshFrequencyDisplay(elapsed, progress);
154
+ if (running && progress < 1) {
155
+ animationId = requestAnimationFrame(updateVisuals);
156
+ } else if (running) {
157
+ stopSweep();
158
+ }
159
+ }
160
+
161
+ function setupOscillator(context: AudioContext, duration: number, volume: number): void {
162
+ oscillator = context.createOscillator();
163
+ gainNode = context.createGain();
164
+ oscillator.type = 'sine';
165
+ oscillator.frequency.setValueAtTime(startHz, context.currentTime);
166
+ oscillator.frequency.exponentialRampToValueAtTime(endHz, context.currentTime + duration);
167
+ gainNode.gain.setValueAtTime(0.0001, context.currentTime);
168
+ gainNode.gain.exponentialRampToValueAtTime(Math.max(0.0001, volume), context.currentTime + 0.08);
169
+ gainNode.gain.setValueAtTime(Math.max(0.0001, volume), context.currentTime + duration - 0.08);
170
+ gainNode.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + duration);
171
+ oscillator.connect(gainNode);
172
+ gainNode.connect(context.destination);
173
+ oscillator.start();
174
+ oscillator.stop(context.currentTime + duration);
175
+ }
176
+
177
+ function applyRunningState(): void {
178
+ root?.classList.add('is-running');
179
+ if (markButton) markButton.disabled = false;
180
+ if (statusDisplay) statusDisplay.textContent = strings.running!;
181
+ }
182
+
183
+ function startSweep(): void {
184
+ const context = getAudioContext();
185
+ if (!context) return;
186
+ if (context.state === 'suspended') void context.resume();
187
+ stopSweep(false);
188
+ setupOscillator(context, Number(durationInput?.value ?? 45), Number(volumeInput?.value ?? 0.22));
189
+ startTime = context.currentTime;
190
+ currentHz = startHz;
191
+ running = true;
192
+ applyRunningState();
193
+ setButtonPlaying(true);
194
+ updateVisuals();
195
+ }
196
+
197
+ function cleanupAudio(): void {
198
+ if (gainNode && audioContext) {
199
+ gainNode.gain.cancelScheduledValues(audioContext.currentTime);
200
+ gainNode.gain.setTargetAtTime(0.0001, audioContext.currentTime, 0.025);
201
+ }
202
+ if (oscillator) {
203
+ try { oscillator.stop(); } catch { }
204
+ oscillator.disconnect();
205
+ }
206
+ oscillator = null;
207
+ gainNode = null;
208
+ }
209
+
210
+ function applyStoppedState(): void {
211
+ root?.classList.remove('is-running');
212
+ if (markButton) markButton.disabled = true;
213
+ }
214
+
215
+ function stopSweep(resetStatus = true): void {
216
+ cancelAnimationFrame(animationId);
217
+ cleanupAudio();
218
+ running = false;
219
+ applyStoppedState();
220
+ if (resetStatus && statusDisplay) statusDisplay.textContent = strings.stopped!;
221
+ setButtonPlaying(false);
222
+ }
223
+
224
+ toggleButton?.addEventListener('click', () => {
225
+ if (running) stopSweep();
226
+ else startSweep();
227
+ });
228
+
229
+ markButton?.addEventListener('click', () => {
230
+ if (!dropoutDisplay) return;
231
+ dropoutDisplay.textContent = formatHz(currentHz);
232
+ if (noteDisplay) {
233
+ noteDisplay.textContent = `${strings.crossoverEstimate!}: ${formatHz(currentHz)}.`;
234
+ }
235
+ stopSweep();
236
+ });
237
+
238
+ resetButton?.addEventListener('click', () => {
239
+ stopSweep(false);
240
+ currentHz = startHz;
241
+ if (dropoutDisplay) dropoutDisplay.textContent = strings.dropoutEmpty!;
242
+ if (noteDisplay) noteDisplay.textContent = strings.safeStart!;
243
+ if (statusDisplay) statusDisplay.textContent = strings.ready!;
244
+ updateVisuals();
245
+ });
246
+
247
+ volumeInput?.addEventListener('input', () => {
248
+ if (gainNode && audioContext) {
249
+ gainNode.gain.setTargetAtTime(Number(volumeInput.value), audioContext.currentTime, 0.03);
250
+ }
251
+ });
252
+ </script>
253
+ </div>
@@ -0,0 +1,29 @@
1
+ import type { HardwareToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { SubwooferCrossoverTestUI } from './ui';
3
+
4
+ export type SubwooferCrossoverTestLocaleContent = ToolLocaleContent<SubwooferCrossoverTestUI>;
5
+
6
+ export const subwooferCrossoverTest: HardwareToolEntry<SubwooferCrossoverTestUI> = {
7
+ id: 'subwoofer-crossover-test',
8
+ icons: {
9
+ bg: 'mdi:speaker-wireless',
10
+ fg: 'mdi:waves-arrow-left',
11
+ },
12
+ i18n: {
13
+ de: () => import('./i18n/de').then((m) => m.content),
14
+ en: () => import('./i18n/en').then((m) => m.content),
15
+ es: () => import('./i18n/es').then((m) => m.content),
16
+ fr: () => import('./i18n/fr').then((m) => m.content),
17
+ id: () => import('./i18n/id').then((m) => m.content),
18
+ it: () => import('./i18n/it').then((m) => m.content),
19
+ ja: () => import('./i18n/ja').then((m) => m.content),
20
+ ko: () => import('./i18n/ko').then((m) => m.content),
21
+ nl: () => import('./i18n/nl').then((m) => m.content),
22
+ pl: () => import('./i18n/pl').then((m) => m.content),
23
+ pt: () => import('./i18n/pt').then((m) => m.content),
24
+ ru: () => import('./i18n/ru').then((m) => m.content),
25
+ sv: () => import('./i18n/sv').then((m) => m.content),
26
+ tr: () => import('./i18n/tr').then((m) => m.content),
27
+ zh: () => import('./i18n/zh').then((m) => m.content),
28
+ },
29
+ };
@@ -0,0 +1,188 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { SubwooferCrossoverTestUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'subwoofer-frequenzweichen-test-online';
7
+ const title = 'Subwoofer Frequenzweichentest';
8
+ const description =
9
+ 'Führe einen Sinus-Sweep von 200 Hz bis 10 Hz in deinem Browser durch, um zu finden, wo dein Subwoofer abfällt, aussetzt oder an die Hauptlautsprecher übergibt.';
10
+
11
+ const faqData = [
12
+ {
13
+ question: 'Was misst ein Subwoofer-Frequenzweichen-Test?',
14
+ answer:
15
+ 'Er hilft dir, den Punkt zu hören, an dem der Bass zwischen deinen Hauptlautsprechern und dem Subwoofer nicht mehr durchgängig klingt. Eine Lücke, ein plötzlicher Pegelwechsel oder ein fehlender Bereich kann auf eine falsche Übergangsfrequenz, ein Phasenproblem, Raumauslöschung oder die Grenze des Subwoofers hinweisen.',
16
+ },
17
+ {
18
+ question: 'Warum sweeped dieser Test von 200 Hz auf 10 Hz herunter?',
19
+ answer:
20
+ 'Die meisten Heimkino-Übergangsfrequenzen liegen zwischen 60 Hz und 120 Hz, während viele Kompaktlautsprecher oberhalb oder unterhalb dieses Bereichs an Ausgabe verlieren. Das Heruntersweepen von 200 Hz macht die Lautsprecher-Subwoofer-Übergabe leicht hörbar, bevor der Ton den tiefen Subbass erreicht.',
21
+ },
22
+ {
23
+ question: 'Kann dieser Online-Subwoofer-Basstest Lautsprecher beschädigen?',
24
+ answer:
25
+ 'Ja, sehr tiefe Frequenzen bei hoher Lautstärke können kleine Lautsprecher überlasten oder einen Subwoofer überfordern. Starte leise, vermeide verstärkte Bassmoden und stoppe sofort, wenn du Klappern, Klopfen oder mechanische Überlastung hörst.',
26
+ },
27
+ {
28
+ question: 'Ist die markierte Ausfallfrequenz die exakte Übergangsfrequenz, die ich einstellen sollte?',
29
+ answer:
30
+ 'Nein. Betrachte sie als Hörtipp, nicht als Labormessung. Die beste Übergangsfrequenz hängt auch von den Lautsprecherspezifikationen, der Raumaufstellung, der Phase, der Entfernungskorrektur und dem Kalibrierungsziel ab.',
31
+ },
32
+ ];
33
+
34
+ const howToData = [
35
+ {
36
+ name: 'Stelle eine sichere Hörlautstärke ein',
37
+ text: 'Reduziere zuerst die Systemlautstärke. Der Sweep verwendet eine erzeugte Sinuswelle, sodass der Bass auch dann intensiv werden kann, wenn die Browser-Lautstärke moderat erscheint.',
38
+ },
39
+ {
40
+ name: 'Starte den 200 Hz bis 10 Hz Sweep',
41
+ text: 'Drücke Sweep starten und höre von deinem normalen Sitzplatz aus. Der Ton bewegt sich gleichmäßig durch den Bassbereich, in dem sich Subwoofer, Hauptlautsprecher und Raumakustik überlappen.',
42
+ },
43
+ {
44
+ name: 'Höre auf den Ausfall oder die Übergabe',
45
+ text: 'Achte auf den Moment, in dem der Bass schwächer wird, verschwindet, den Ort wechselt oder zwischen Subwoofer und Hauptlautsprechern ungleichmäßig klingt.',
46
+ },
47
+ {
48
+ name: 'Markiere die Frequenz',
49
+ text: 'Drücke Ausfall markieren am ersten deutlichen Problempegel. Verwende diese Frequenz als Hinweis zur Anpassung von Übergangsfrequenz, Phase, Aufstellung oder Raumkorrektur.',
50
+ },
51
+ ];
52
+
53
+ const faqSchema: WithContext<FAQPage> = {
54
+ '@context': 'https://schema.org',
55
+ '@type': 'FAQPage',
56
+ mainEntity: faqData.map((item) => ({
57
+ '@type': 'Question',
58
+ name: item.question,
59
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
60
+ })),
61
+ };
62
+
63
+ const howToSchema: WithContext<HowTo> = {
64
+ '@context': 'https://schema.org',
65
+ '@type': 'HowTo',
66
+ name: title,
67
+ description,
68
+ step: howToData.map((step, index) => ({
69
+ '@type': 'HowToStep',
70
+ position: index + 1,
71
+ name: step.name,
72
+ text: step.text,
73
+ })),
74
+ };
75
+
76
+ const appSchema: WithContext<SoftwareApplication> = {
77
+ '@context': 'https://schema.org',
78
+ '@type': 'SoftwareApplication',
79
+ name: title,
80
+ description,
81
+ applicationCategory: 'UtilityApplication',
82
+ operatingSystem: 'All',
83
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
84
+ inLanguage: 'de',
85
+ };
86
+
87
+ export const content: ToolLocaleContent<SubwooferCrossoverTestUI> = {
88
+ slug,
89
+ title,
90
+ description,
91
+ faq: faqData,
92
+ howTo: howToData,
93
+ schemas: [faqSchema, howToSchema, appSchema],
94
+ bibliography,
95
+ seo: [
96
+ { type: 'title', text: 'Finde Die Basslücke Zwischen Deinen Lautsprechern Und Dem Subwoofer', level: 2 },
97
+ {
98
+ type: 'paragraph',
99
+ html: 'Ein Subwoofer sollte nicht wie eine separate Box in der Ecke klingen. Der Bass sollte gleichmäßig bleiben, wenn die Töne von deinen Hauptlautsprechern in den Sub übergehen. Dieser Test sweeped von <strong>200 Hz bis 10 Hz</strong>, damit du genau die Zone hören kannst, in der die Übergabe schwach, dröhnend, ortbar oder stumm wird.',
100
+ },
101
+ {
102
+ type: 'table',
103
+ headers: ['Was du hörst', 'Was es normalerweise bedeutet', 'Was du zuerst versuchen solltest'],
104
+ rows: [
105
+ ['Bass verschwindet um 70-100 Hz', 'Subwoofer und Lautsprecher löschen sich gegenseitig nahe der Übergangsfrequenz aus.', 'Phase umkehren, Entfernung/Verzögerung anpassen, dann den Sweep wiederholen.'],
106
+ ['Bass dröhnt in einem schmalen Band', 'Raummode oder zu viel Überlappung zwischen Lautsprechern und Subwoofer.', 'Übergangsfrequenz leicht senken oder Subwoofer/Hörposition verschieben.'],
107
+ ['Bass scheint vom Subwoofer-Standort zu kommen', 'Übergangsfrequenz zu hoch oder Subwoofer-Pegel zu heiß.', '80 Hz oder niedriger versuchen und Subwoofer-Verstärkung reduzieren.'],
108
+ ['Tiefbass verblasst unter 30-40 Hz', 'Normale Ausdehnungsgrenze vieler Subs, besonders kompakter Modelle.', 'Subwoofer-Spezifikation prüfen, bevor du ein Problem verfolgst, das physikalisch sein könnte.'],
109
+ ],
110
+ },
111
+ { type: 'title', text: 'Was Dir Die Ausfallfrequenz Sagt', level: 3 },
112
+ {
113
+ type: 'diagnostic',
114
+ variant: 'info',
115
+ title: 'Verwende die markierte Frequenz als Abstimmungshinweis',
116
+ badge: 'Hörtipps',
117
+ html: '<p>Wenn der markierte Punkt nahe deiner AVR-Übergangsfrequenz liegt, ist das Problem wahrscheinlich Integration: Phase, Entfernung, Polarität, Pegel oder die Steilheit der Filter. Liegt der markierte Punkt viel tiefer, hörst du vielleicht, wie der Subwoofer an seine Leistungsgrenze stößt. Ändert sich das Problem stark, wenn du deinen Kopf bewegst, dominiert der Raum das Ergebnis.</p>',
118
+ },
119
+ {
120
+ type: 'title',
121
+ text: 'Übliche Übergangsfrequenzbereiche',
122
+ level: 3,
123
+ },
124
+ {
125
+ type: 'table',
126
+ headers: ['Lautsprechertyp', 'Typischer Übergangsfrequenz-Startpunkt', 'Warum'],
127
+ rows: [
128
+ ['Kleine Satelliten', '100-150 Hz', 'Winzige Gehäuse können normalerweise keinen sauberen oberen Bass bei Kino-Lautstärken wiedergeben.'],
129
+ ['Regallautsprecher', '70-100 Hz', 'Oft genug Bass für eine saubere Übergabe, ohne den Sub ortbar zu machen.'],
130
+ ['Standlautsprecher', '50-80 Hz', 'Größere Tieftöner können mehr Mittelbass bewältigen, aber der Raum kann trotzdem Subwoofer-Bassmanagement bevorzugen.'],
131
+ ['THX-Setup', '80 Hz', 'Ein praktischer Standardwert, der für viele Systeme gut funktioniert und Tiefbass zum Sub leitet.'],
132
+ ],
133
+ },
134
+ { type: 'title', text: 'Übergangsfrequenzproblem Oder Raumproblem?', level: 3 },
135
+ {
136
+ type: 'comparative',
137
+ columns: 2,
138
+ items: [
139
+ {
140
+ title: 'Übergangsfrequenz oder Phasenproblem',
141
+ description: 'Die Schwachstelle erscheint nahe der Übergangsfrequenz und verbessert sich nach Änderung von Phase, Polarität, Entfernung oder Übergangsfrequenz-Einstellung.',
142
+ points: ['Normalerweise vom gleichen Sitzplatz aus wiederholbar.', 'Oft um 60-120 Hz zentriert.'],
143
+ },
144
+ {
145
+ title: 'Raumauslöschung',
146
+ description: 'Die Schwachstelle ändert sich drastisch, wenn du deinen Kopf bewegst, den Sitzplatz wechselst oder den Subwoofer ein kurzes Stück verschiebst.',
147
+ points: ['Sehr positionsabhängig.', 'Oft mehr durch Aufstellung als durch Einstellungen zu lösen.'],
148
+ },
149
+ ],
150
+ },
151
+ {
152
+ type: 'tip',
153
+ title: 'Beste Art, den Test durchzuführen',
154
+ html: 'Setze dich dorthin, wo du tatsächlich Filme schaust oder Musik hörst. Stehe nicht neben dem Subwoofer. Eine Übergangsfrequenz, die neben dem Gehäuse gut klingt, kann auf dem Sofa immer noch eine Lücke hinterlassen.',
155
+ },
156
+ {
157
+ type: 'summary',
158
+ title: 'Was nach dem Sweep zu ändern ist',
159
+ items: [
160
+ 'Liegt die Lücke nahe der aktuellen Übergangsfrequenz, probiere 10-Hz-Änderungen und wiederhole den Sweep.',
161
+ 'Teste den Phasenschalter oder die Verzögerungseinstellung des Subwoofers, wenn das schwache Band nahe der Übergangsfrequenz liegt.',
162
+ 'Wird der Bass an einem Sitzplatz stärker und verschwindet an einem anderen, verschiebe den Subwoofer, bevor du alle AVR-Einstellungen änderst.',
163
+ 'Nach Aufstellungs- oder Übergangsfrequenzänderungen führe die Raumkorrektur erneut aus, damit der Receiver das neue Setup misst.',
164
+ 'Verwende die markierte Frequenz als Leitfaden für die Abstimmung und bestätige dann mit Musik, Filmen und vertrauten Basslinien.',
165
+ ],
166
+ },
167
+ ],
168
+ ui: {
169
+ sweepLabel: 'Subwoofer-Tieffrequenz-Sweep',
170
+ currentFrequency: 'Aktuelle Frequenz',
171
+ targetFrequency: 'Ziel',
172
+ elapsed: 'Verstrichen',
173
+ statusReady: 'Bereit für Tief-Sweep',
174
+ statusRunning: 'Sweepe nach unten',
175
+ statusStopped: 'Sweep gestoppt',
176
+ start: 'Sweep starten',
177
+ stop: 'Sweep stoppen',
178
+ markDropout: 'Ausfall markieren',
179
+ reset: 'Zurücksetzen',
180
+ volume: 'Ausgabepegel',
181
+ duration: 'Sweep-Dauer',
182
+ safeStart: 'Starte mit niedriger Lautstärke und markiere dann die erste Frequenz, bei der der Bass schwer hörbar wird.',
183
+ roomNote: 'Raumposition und Phase können das Ergebnis drastisch verändern.',
184
+ dropoutLabel: 'Markierter Punkt',
185
+ dropoutEmpty: 'Noch nicht markiert',
186
+ crossoverEstimate: 'Geschätzter Ausfallpunkt',
187
+ },
188
+ };