diff --git a/src/app/pages/algorithms/sorting/service/sorting-audio.service.ts b/src/app/pages/algorithms/sorting/service/sorting-audio.service.ts new file mode 100644 index 0000000..3ac8f3f --- /dev/null +++ b/src/app/pages/algorithms/sorting/service/sorting-audio.service.ts @@ -0,0 +1,80 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class SortingAudioService { + private audioContext: AudioContext | null = null; + + private ensureContext(): AudioContext { + this.audioContext ??= new AudioContext(); + if (this.audioContext.state === 'suspended') { + this.audioContext.resume(); + } + return this.audioContext; + } + + // Call this on the user gesture (button click) so the AudioContext can be created/resumed. + initOnUserGesture(): void { + this.ensureContext(); + } + + playTone(value: number, maxValue: number, animationSpeedMs: number): void { + const ctx = this.ensureContext(); + const frequency = this.valueToFrequency(value, maxValue); + + // Keep tone duration slightly shorter than the animation frame to avoid overlap + const duration = Math.min(0.1, (animationSpeedMs * 0.75) / 1000); + + const oscillator = ctx.createOscillator(); + const gainNode = ctx.createGain(); + + oscillator.connect(gainNode); + gainNode.connect(ctx.destination); + + oscillator.type = 'sawtooth'; + oscillator.frequency.setValueAtTime(frequency, ctx.currentTime); + + gainNode.gain.setValueAtTime(0.15, ctx.currentTime); + gainNode.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration); + + oscillator.start(ctx.currentTime); + oscillator.stop(ctx.currentTime + duration); + } + + playSortedSweep(sortedValues: number[], maxValue: number): void { + const ctx = this.ensureContext(); + // Play a quick ascending sweep through all sorted bar values + const step = Math.ceil(sortedValues.length / 40); + sortedValues.forEach((value, i) => { + if (i % step !== 0) { + return; + } + const delayMs = (i / step) * 18; + setTimeout(() => { + const frequency = this.valueToFrequency(value, maxValue); + const oscillator = ctx.createOscillator(); + const gainNode = ctx.createGain(); + + oscillator.connect(gainNode); + gainNode.connect(ctx.destination); + + oscillator.type = 'sine'; + oscillator.frequency.setValueAtTime(frequency, ctx.currentTime); + + gainNode.gain.setValueAtTime(0.15, ctx.currentTime); + gainNode.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.06); + + oscillator.start(ctx.currentTime); + oscillator.stop(ctx.currentTime + 0.06); + }, delayMs); + }); + } + + // Maps a bar value linearly to the frequency range 180–1100 Hz (roughly 3 octaves) + private valueToFrequency(value: number, maxValue: number): number { + const minFreq = 400; + const maxFreq = 800; + return minFreq + (value / maxValue) * (maxFreq - minFreq); + } +} diff --git a/src/app/pages/algorithms/sorting/sorting.component.html b/src/app/pages/algorithms/sorting/sorting.component.html index 7f97529..77ade5b 100644 --- a/src/app/pages/algorithms/sorting/sorting.component.html +++ b/src/app/pages/algorithms/sorting/sorting.component.html @@ -37,6 +37,10 @@ +
diff --git a/src/app/pages/algorithms/sorting/sorting.component.ts b/src/app/pages/algorithms/sorting/sorting.component.ts index f25bfd2..9cdc792 100644 --- a/src/app/pages/algorithms/sorting/sorting.component.ts +++ b/src/app/pages/algorithms/sorting/sorting.component.ts @@ -7,6 +7,7 @@ import {MatButtonModule} from "@angular/material/button"; import {MatIconModule} from "@angular/material/icon"; import {TranslateModule} from "@ngx-translate/core"; import { SortingService } from './service/sorting.service'; +import { SortingAudioService } from './service/sorting-audio.service'; import {SortData, SortSnapshot} from './sorting.models'; import { FormsModule } from '@angular/forms'; import {MatInput} from '@angular/material/input'; @@ -22,6 +23,7 @@ import {Information} from '../information/information'; export class SortingComponent implements OnInit { private readonly sortingService: SortingService = inject(SortingService); + private readonly audioService: SortingAudioService = inject(SortingAudioService); private readonly cdr: ChangeDetectorRef = inject(ChangeDetectorRef); readonly MAX_ARRAY_SIZE: number = 200; @@ -70,9 +72,10 @@ export class SortingComponent implements OnInit { unsortedArrayCopy: SortData[] = []; arraySize = 50; maxArrayValue = 100; - animationSpeed = 50; // Milliseconds per step + animationSpeed = 100; // Milliseconds per step selectedAlgorithm: string = this.algoInformation.entries[0].name; executionTime = 0; + isSoundEnabled = false; ngOnInit(): void { this.generateNewArray(); @@ -117,8 +120,14 @@ export class SortingComponent implements OnInit { } } + toggleSound(): void { + this.isSoundEnabled = !this.isSoundEnabled; + } + async startSorting(): Promise { this.resetSorting(); + // Init the AudioContext on this user gesture so the browser allows sound + this.audioService.initOnUserGesture(); const startTime = performance.now(); let snapshots: SortSnapshot[] = []; @@ -156,11 +165,22 @@ export class SortingComponent implements OnInit { } } + if (this.isSoundEnabled) { + // Play a tone for each comparing bar (max 2 at once to avoid noise) + snapshot.array + .filter(item => item.state === 'comparing') + .slice(0, 2) + .forEach(item => this.audioService.playTone(item.value, this.maxArrayValue, this.animationSpeed)); + } + this.cdr.detectChanges(); if (index === snapshots.length - 1) { this.sortArray.forEach(item => item.state = 'sorted'); this.cdr.detectChanges(); + if (this.isSoundEnabled) { + this.audioService.playSortedSweep(this.sortArray.map(item => item.value), this.maxArrayValue); + } } }, index * this.animationSpeed); diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index f93298f..e45e3cf 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -364,6 +364,8 @@ "START": "Sortierung starten", "RESET": "Zurücksetzen", "GENERATE_NEW_ARRAY": "Neues Array generieren", + "SOUND_ON": "Ton an", + "SOUND_OFF": "Ton aus", "EXECUTION_TIME": "Ausführungszeit", "ARRAY_SIZE": "Anzahl der Balken", "EXPLANATION": { diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index b2f0d97..effb9f9 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -364,6 +364,8 @@ "START": "Start Sorting", "RESET": "Reset", "GENERATE_NEW_ARRAY": "Generate New Array", + "SOUND_ON": "Sound On", + "SOUND_OFF": "Sound Off", "EXECUTION_TIME": "Execution Time", "ARRAY_SIZE": "Number of Bars", "EXPLANATION": {