From 37174c7237a735c25032bc65b14594534efe733c Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 17 Sep 2025 10:00:00 +0200 Subject: [PATCH] =?UTF-8?q?=C3=84nderung:=20Hinzuf=C3=BCgung=20der=20Motor?= =?UTF-8?q?Sound-Klasse=20und=20Integration=20in=20das=20Taxi-Spiel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Änderungen: - Implementierung eines neuen AudioWorklet-Prozessors für realistische Motorgeräusche. - Erstellung der MotorSound-Klasse zur Verwaltung von motorgeräuschabhängigen Audioeffekten. - Integration des MotorSounds in die TaxiGame.vue, einschließlich der Initialisierung und Steuerung basierend auf der Geschwindigkeit des Taxis. - Anpassung der Audio-Parameter wie Lautstärke und Geschwindigkeit in Abhängigkeit von der Fahrzeuggeschwindigkeit. Diese Anpassungen verbessern die akustische Benutzererfahrung im Taxi-Minispiel erheblich und tragen zur Immersion bei. --- frontend/public/audio-worklet-processor.js | 43 +++++ frontend/src/assets/motorSound.js | 195 +++++++++++++++++++++ frontend/src/views/minigames/TaxiGame.vue | 65 ++++++- 3 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 frontend/public/audio-worklet-processor.js create mode 100644 frontend/src/assets/motorSound.js diff --git a/frontend/public/audio-worklet-processor.js b/frontend/public/audio-worklet-processor.js new file mode 100644 index 0000000..4312ce8 --- /dev/null +++ b/frontend/public/audio-worklet-processor.js @@ -0,0 +1,43 @@ +// AudioWorklet Processor für MotorSound +class MotorSoundProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.data = null; + this.speed = 0.6; + this.currentFrame = 0; + + this.port.onmessage = (e) => { + const { type, data, speed } = e.data; + switch (type) { + case 'init': + this.data = data; + this.speed = speed; + break; + case 'updateData': + this.data = data; + break; + case 'updateSpeed': + this.speed = speed; + break; + } + }; + } + + process(inputs, outputs, parameters) { + const output = outputs[0]; + if (!this.data || this.data.length === 0) return true; + + for (let channel = 0; channel < output.length; channel++) { + const outputChannel = output[channel]; + for (let i = 0; i < outputChannel.length; i++) { + this.currentFrame += this.speed; + const index = Math.floor(this.currentFrame) % this.data.length; + outputChannel[i] = this.data[index]; + } + } + this.currentFrame %= this.data.length; + return true; + } +} + +registerProcessor('motor-sound-processor', MotorSoundProcessor); diff --git a/frontend/src/assets/motorSound.js b/frontend/src/assets/motorSound.js new file mode 100644 index 0000000..cb37b0c --- /dev/null +++ b/frontend/src/assets/motorSound.js @@ -0,0 +1,195 @@ +// Moderne MotorSound-Lib mit Web Audio API (AudioWorklet statt ScriptProcessor) +// Geschwindigkeitsabhängiges Motorgeräusch für Taxi-Spiel + +class MotorSound { + constructor(context, generator) { + this.context = context; + this.generator = generator; + this.speed = 0.6; // incoming control value (0.3..1.5) + this.isPlaying = false; + this.currentFrame = 0; + this.workletNode = null; + this.gainNode = null; + this.data = null; + this.sampleRate = context.sampleRate || 44100; + this.step = 1; // table index increment per sample, derived from target frequency + this.regenerate(); + } + + async init() { + // Verwende ScriptProcessor für bessere Kompatibilität + console.log('Initialisiere MotorSound mit ScriptProcessor'); + this.workletNode = this.context.createScriptProcessor(1024); + this.workletNode.onaudioprocess = this.process.bind(this); + + // Filter-Kette für motorähnlicheren Klang + this.lowShelf = this.context.createBiquadFilter(); + this.lowShelf.type = 'lowshelf'; + this.lowShelf.frequency.value = 150; + this.lowShelf.gain.value = 6; + + this.lowPass = this.context.createBiquadFilter(); + this.lowPass.type = 'lowpass'; + this.lowPass.frequency.value = 1200; + this.lowPass.Q.value = 0.5; + + this.gainNode = this.context.createGain(); + this.gainNode.gain.value = 0.25; // Leiser starten + + // Verbindung: script -> lowshelf -> lowpass -> gain + this.workletNode.connect(this.lowShelf); + this.lowShelf.connect(this.lowPass); + this.lowPass.connect(this.gainNode); + + console.log('MotorSound ScriptProcessor initialisiert'); + } + + start() { + if (!this.gainNode) return; + if (this.context.state === 'suspended') { + this.context.resume().catch(() => {}); + } + this.gainNode.connect(this.context.destination); + this.isPlaying = true; + } + + stop() { + if (!this.gainNode) return; + this.gainNode.disconnect(this.context.destination); + this.isPlaying = false; + } + + regenerate() { + this.data = this.generator.generate(); + console.log('MotorSound Daten regeneriert, Länge:', this.data.length); + } + + setVolume(volume) { + if (this.gainNode) this.gainNode.gain.value = volume; + } + + setGenerator(generator) { + this.generator = generator; + this.regenerate(); + } + + setSpeed(speed) { + // speed erwartet ca. 0.3..1.5 (von Spiel geliefert). Normiere auf 0..1 + const normalized = Math.max(0, Math.min(1, (speed - 0.3) / 1.2)); + // Ziel-Frequenz für Motorgrundton (Hz) + const freq = 40 + normalized * 160; // 40..200 Hz + // Tabellen-Schritt für gewünschte Frequenz + this.step = (freq * this.data.length) / this.sampleRate; + } + + process(event) { + if (!this.data || this.data.length === 0) return; + + const output = event.outputBuffer; + const ch0 = output.getChannelData(0); + const ch1 = output.numberOfChannels > 1 ? output.getChannelData(1) : ch0; + + for (let i = 0; i < ch0.length; ++i) { + this.currentFrame += this.step; + const index = Math.floor(this.currentFrame) % this.data.length; + const sample = this.data[index] * 0.9; // etwas dämpfen + ch0[i] = sample; + ch1[i] = sample; + } + this.currentFrame %= this.data.length; + } +} + +class LinearGenerator { + constructor() { + this.dataLength = 1024; + } + + pushLinear(data, toValue, toPosition) { + const lastPosition = data.length - 1; + const lastValue = data.pop(); + const positionDiff = toPosition - lastPosition; + const step = (toValue - lastValue) / positionDiff; + for (let i = 0; i < positionDiff; i++) { + data.push(lastValue + step * i); + } + return data; + } + + generate() { + const data = []; + let lastValue = 1; + data.push(lastValue); + + for (let i = 0.05; i < 1; i += Math.random() / 8 + 0.01) { + const nextPosition = Math.floor(i * this.dataLength); + const nextValue = Math.random() * 2 - 1; + this.pushLinear(data, nextValue, nextPosition); + } + + this.pushLinear(data, 1, this.dataLength); + return data; + } +} + +class NoiseGenerator { + constructor() { + this.dataLength = 4096; + this.linearLength = 30; + this.smoothness = 3; + } + + generate() { + const data = []; + let lastValue = 0.5; + data.push(lastValue); + + for (let i = 1; i <= this.dataLength - this.linearLength; i++) { + lastValue += (Math.random() - 0.5) / this.smoothness; + lastValue = Math.min(1, lastValue); + lastValue = Math.max(-1, lastValue); + data.push(lastValue); + } + + const step = (0.5 - lastValue) / this.linearLength; + for (let j = 0; j < this.linearLength; j++) { + data.push(lastValue + step * j); + } + data.push(0.5); + return data; + } +} + +class CanvasGenerator { + constructor() { + this.canvas = document.createElement('canvas'); + this.canvas.width = 1024; + this.canvas.height = 1; + this.ctx = this.canvas.getContext('2d'); + } + + getRandomGradient() { + const gradient = this.ctx.createLinearGradient(0, 0, this.canvas.width, 0); + gradient.addColorStop(0, "rgba(0, 0, 0, 255)"); + for (let i = 0.05; i < 1; i += Math.random() / 8 + 0.01) { + gradient.addColorStop(i, "rgba(0, 0, 0," + Math.random() + ")"); + } + gradient.addColorStop(1, "rgba(0, 0, 0, 255)"); + return gradient; + } + + generate() { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.ctx.fillStyle = this.getRandomGradient(); + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + + const imageData = this.ctx.getImageData(0, 0, this.canvas.width, 1).data; + const data = []; + for (let i = 3, len = imageData.length; i < len; i += 4) { + data.push(imageData[i] / 128 - 1); + } + return data; + } +} + +export { MotorSound, LinearGenerator, NoiseGenerator, CanvasGenerator }; diff --git a/frontend/src/views/minigames/TaxiGame.vue b/frontend/src/views/minigames/TaxiGame.vue index ce6bd28..845b7bf 100644 --- a/frontend/src/views/minigames/TaxiGame.vue +++ b/frontend/src/views/minigames/TaxiGame.vue @@ -170,6 +170,7 @@