• Das Erstellen neuer Accounts wurde ausgesetzt. Bei berechtigtem Interesse bitte Kontaktaufnahme über die üblichen Wege. Beste Grüße der Admin

Gleiche Töne parallel ausgeben

Yogilein

Member
Guten Tach,

ich bin ganz neu hier, beschäftige mich aber seit bereits über 5 Jahren mit JS. Neben dem normalen HP-Stuff schreibe ich vor allem kleine Spiele, die ich auf meiner HP anbiete. Seit neuestem auch mit einer Tonausgabe über HTML5.

Und jetzt meine Frage: Wie kann ich einen gleichen Ton mehrfach parallel ausgeben. Wenn ich z,B. Schritte akustisch darstellen möchte und ein Ton dazu 1/2s (mit Ausklang) dauert, so muss ich immer diese 1/2s warten, bis ich den gleichen Ton erneut ausgeben kann. Rufe ich ihn vor dem vollständigen Abspielen erneut auf, passiert nämlich gar nichts. Drückt nun ein Spieler permanent die Cursortaste und die Tastatur hat eine Wiederholrate von 30, so müsste dieser Ton aber 30 Mal in einer Sekunde ausgegeben werden.

Mein aktueller Ansatz ist, den gleichen Ton mehrmals zu laden und dann so ausgeben: Ton1, Ton2, Ton3 ... Ton15, Ton1, Ton2 ... Das funktioniert zwar, erscheint mir aber sehr umständlich. Ein weiterer Ansatz ist, immer vor einer neuen Tonausgabe, den alten Ton zu löschen. Auch das geht, hört sich aber absolut abgehackt an.

Kennt jemand eine elegantere Lösung?

LG Yogilein
 
Zeig' doch mal deinen Code, mit dem du die Tonausgabe realisiert hast. Oder noch besser einen Testlink. Dann fällt uns vielleicht was sinnvolles ein.
 
Momentan habe ich das mit den Schritten nicht umgesetzt, da es mir zu holprig ist.

Die anderen Klänge erzeuge ich so (auszugsweise):

1. HTML

</audio>
<audio id="sfx2" preload oncanplay="gel2=1">
<source src="../Spiele/FIDIFU3D/Bewegung.mp3" type="audio/mpeg" />
<source src="../Spiele/FIDIFU3D/Bewegung.ogg" type="audio/ogg" />
</audio>

2. Javascript

if(so==1){
document.getElementById("sfx2").currentTime=0;
document.getElementById("sfx2").play();}

Ich setze den Ton vor dem Aufruf immer zurück, damit er erneut aufgerufen werden kann, auch wenn er noch am Abspielen wäre. Hier funktioniert es auch ganz gut, da diese Töne immer nur in einem gewissen zeitlichen Abstand aufgerufen werden können, also nicht 30 Mal in der Sekunde. Und dass diese Vorgehensweise ganz gut funktioniert, sieht man z.B. hier: YogiSpiele - FIDIFU 3D (zum Starten der Tonausgabe auf FX klicken).

Das genaue Problem ist, dass ohne "document.getElementById("sfx2").currentTime=0;" der Befehl "document.getElementById("sfx2").play();" nur ausgeführt wird, wenn zuvor "sfx2" komplett abgespielt wurde. Aber vielleicht gibt es für so etwas einen anderen Befehl oder irgend einen Flag.

LG Yogilein
 
OK. Ich würde ja einfach die <audio>-Node clonen und dann das Klon abspielen lassen - hab's jetzt nur im FF getestet, aber da funktioniert's einwandfrei:
Code:
<!DOCTYPE html>

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Fenstertitel</title>
</head>
<body>
<audio id="sfx2" preload oncanplay="gel2=1">
	<source src="../Spiele/FIDIFU3D/Bewegung.mp3" type="audio/mpeg" />
	<source src="../Spiele/FIDIFU3D/Bewegung.ogg" type="audio/ogg" />
</audio>
<script type="text/javascript">
document.onclick = function(){
	var audio = document.getElementById("sfx2").cloneNode(true);
	audio.play();
};
</script>
</body>
</html>
 
Hallo Yogilein, ich bin zwar auch noch Grünschnabel in diesem Forum, aber ich kann dir sagen da wirst du wohl um die Web Audio Api nicht drum rum kommen.
Der Sound sollte via xhr2 hochladen werden und in eine globalen float32 array abspeichern werden.

Um eine mp3 in eine float32 zu verwandeln gibt es in der Web Audio api die Methode decodeAudiodata()

Kleines Beispiel:
Code:
//globale 
var Context = new AudioContext();
var audio = new Float32Array(data);//data ist der decodierte audio buffer
var AudioStart =0; 

//function die bei onklick ausgelöst wird
function play(data){
    var source = context.createBufferSource();
    var audioBuffer = context.createBuffer(1, audio.length , 44100);// neuer Audio buffer (nuber of channels, größe des buffers, sample rate)

    audioBuffer.getChannelData(0).set(audio);// Schreibe die daten in den audio buffer via set
	
    source.buffer = audioBuffer;
     source.connect(context.destination);
    source.start(AudioStart);
    AudioStart += audioBuffer.duration;
}

//Sound laden
function loadSchritte(url, variableToBufferSound) {
  var request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.responseType = 'arraybuffer';

  // Decode asynchronously
  request.onload = function() {
    context.decodeAudioData(request.response, function(buffer) {
      audio = buffer;
    }, onError);
  }
  request.send();
}

Das Beispiel ist sehr rudimentär, ich denke kkapsner weiß worauf ich hinaus will.


Ob man den dekodierten Puffer jetzt so einfach in ein Typed Array schreiben kann weiß ich auch nicht, man könnte eine Variable mit „null“ erstellen die V8 macht das dann automatisch.
Bevor ich mich jetzt zu weit aus dem Fenster lehne lasse ich lieber den Großmeister kkapsner sprechen.

Das mit dem abgehackt kommt daher, dass du den Ausgabepuffer überschreibst bevor er überhaut komplett ausgegeben wurde.
Das hat was mit der Samplerate zu tun und der größe des Puffers mit dem du deinen Soundkarte befeuerst. Um das zu umgehen benötig man den ScriptProcessor(daruf gehen wir jetzt erst mal nicht weiter ein).

Noch eine Frage warum willst du 30 Schritte in der Sekunde ausgeben so schnell ist doch kein Mensch. Du musst die schritte des Menschen mit der Soundausgabe timen und nicht umgekehrt.

Einen einstig in die Web Audio api ist zu finden hier: Getting Started with Web Audio API - HTML5 Rocks
und Game Audio hier: Developing Game Audio with the Web Audio API - HTML5 Rocks
So habe ich den Umgang mit der Audio api gelernt.

Ps: Ich bin die Tage erst mal auf Geschäftsreise kann also nicht weiter helfen... wie schon erwähnt kkapsner weiß worauf ich hinaus will, mein Vertrauen liegt in ihn.

Mfg xorg1990
 
var audio = document.getElementById("sfx2").cloneNode(true);
audio.play();
Oh, danke, klasser Ansatz. Genau so etwas hatte ich gesucht.

Ich habe es gleich getestet und es funktioniert, wenn ich "var" weglasse, also so:

if(so==1){
audio=document.getElementById("sfx2").cloneNode(true);
audio.play();}

Ich werde jetzt wohl meine Spiele (auch ohne Schrittgeräusche) umbauen, denn diese Lösung erscheint mir sauberer.

Noch eine Frage warum willst du 30 Schritte in der Sekunde ausgeben so schnell ist doch kein Mensch. Du musst die schritte des Menschen mit der Soundausgabe timen und nicht umgekehrt.

Das war ein Versuch bei einem meiner Käferli-Spielen: YogiSpiele - Käferli3

Hier muss man sehr schnell die Doppelbeere erhaschen. Wenn die weit weg ist und der Weg frei ist, kann man mit dem Finger einfach auf der Curtsor-Taste bleiben, dann flitzt das Käferli über das Spielfeld. Und da ich (kurze) Schrittgeräusche einbauen wollte, hatte ich auf meine Weise ein Problem. Ohne Löschen fehlten einfach viele Geräusche und mit Löschen hörte es sich abgehackt an.
 
Ich habe jetzt einmal die Klone-Variante in meine Spiele eingebaut.

Dabei habe ich allerdings noch etwas geändert. Irgendwie kam mir vor, dass das Klonen etwas Zeit kostet. Nach dem Klick schien es mir, dass der Ton etwas zeitversetzt kam. Vielleicht war es auch nur Einbildung.

Und das habe ich gemacht.

1. In der Routine, in der ich ermittle ob die Tonausgabe überhaupt möglich ist, habe ich dies hinzugefügt:

audio2=document.getElementById("sfx2").cloneNode(true);

2. Die eigentliche Tonausgabe sieht jetzt so aus:

if(so==1){
audio2.play();
audio2=document.getElementById("sfx2").cloneNode(true);}

Ich klone jetzt also immer im Voraus und wenn das erste Mal der Ton aufgerufen wird, kann ich gleich mit der Ausgabe anfangen.

Ganz schnelle Schritte funktionieren noch nicht optimal. Die Töne kommen zwar parallel, aber irgendwie zeitversetzt. D.h., dass der Anfang des letzten Tons stattfindet, wenn die Schritte bereits aufgehört haben. Falls meine obige Theorie stimmt, würde das daran liegen, dass zwischen jedem Ton geklont werden muss, was entsprechend Zeit kostet.

Aber auf jeden Fall hilft mir diese Methode schon enorm weiter. Vielen Dank! Ich werde mir dazu auch noch xorg1990s Lösung etwas näher betrachten.


LG Yogilein
 
Ich habe es gleich getestet und es funktioniert, wenn ich "var" weglasse
Das muss auch funktionieren, wenn du das nicht als globale Variable deklarierst (was nie eine gute Idee ist, da es zu schnell irgendwelche verwirrenden Fehler erzeugen kann).

Irgendwie kam mir vor, dass das Klonen etwas Zeit kostet.
Ja, das Klonen ist nicht das Schnellste. Hab' gerade selbst ein bisschen rumgespielt und es ist wirklich nicht ideal, wenn du das extrem oft wiederholst.

Hab' mich jetzt mal mit der Audio API beschäftigt und das ist dabei rausgekommen - funktioniert eigentlich nicht schlecht:
Code:
<!DOCTYPE html>

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Soundeffekt</title>
</head>
<body>
<script type="text/javascript">
function Sound(URL){
	var context = new (window.AudioContext || window.webkitAudioContext)();
	var audioNode = document.createElement("audio");
	audioNode.src = URL;
	audioNode.onloadeddata = function(){

		// Create a ScriptProcessorNode with a bufferSize of 4096 and a single input channel
		var readOut = context.createScriptProcessor(4096, 1, 1);

		var data = [];

		// Give the node a function to process audio events
		readOut.onaudioprocess = function(audioProcessingEvent){
			var inputBuffer = audioProcessingEvent.inputBuffer;
			for (var channel = 0; channel < inputBuffer.numberOfChannels; channel++){
				if (data.length <= channel){
					data.push([]);
				}
				var inData = inputBuffer.getChannelData(channel);

				// Loop through the 4096 samples
				for (var sample = 0; sample < inputBuffer.length; sample++) {
					data[channel].push(inData[sample]);
				}
			}
			var outputBuffer = audioProcessingEvent.outputBuffer;
			for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++){
				var outData = outputBuffer.getChannelData(channel);

				// Loop through the 4096 samples
				for (var sample = 0; sample < outputBuffer.length; sample++) {
					outData[sample] = 0;
				}
			}
		}
		var mediaSourceNode = context.createMediaElementSource(audioNode);
		mediaSourceNode.connect(readOut);
		readOut.connect(context.destination);

		audioNode.onended = function(){
			buffer = [];
			data.forEach(function(cData, i){
				buffer[i] = new Float32Array(cData);
			});
			readOut.onaudioprocess = null;
		};
	}
	audioNode.play();
	
	var buffer = null;
	this.play = function(){
		if (buffer){
			var audioBuffer = context.createBuffer(buffer.length, buffer[0].length, 44100);
			buffer.forEach(function(buffer, i){
				audioBuffer.getChannelData(i).set(buffer);
			});
			var source = context.createBufferSource();
			source.buffer = audioBuffer;
			source.connect(context.destination);
			source.start(0);
		}
	};
}



var klick = new Sound("URL ZUM MP3");
document.onkeypress = function(){
	klick.play();
}
</script>
</body>
</html>
- funktioniert im Chrome und FF. Die Anderen unterstützen das ja noch nicht.

PS: Bei dem Käferspiel höre ich gar nichts...
 
Das muss auch funktionieren, wenn du das nicht als globale Variable deklarierst (was nie eine gute Idee ist, da es zu schnell irgendwelche verwirrenden Fehler erzeugen kann).
Ich müsste es noch einmal testen. Aber beim ersten Test kam mit "var" kein einziger Ton - in meinem Code eingebaut - heraus. Vielleicht hatte ich es auf die Schnelle auch falsch eingebaut.

funktioniert im Chrome und FF. Die Anderen unterstützen das ja noch nicht.
Dann merke ich mir mal den Code vor. Aktuell haben die meisten meiner Seitenbesucher den IE. Also werde ich es vorerst nicht einsetzen können.

PS: Bei dem Käferspiel höre ich gar nichts...
Da habe ich die Schrittgeräusche ja auch noch nicht drin, da sie bei konstant gedrückter Cursortaste zum Schluss leicht verzögert kommen. Aber die anderen Töne hörst Du aber (vorher FX anklicken)?

Ja, das Klonen ist nicht das Schnellste.
Ich habe jetzt einmal einen Test gemacht, dessen Ergebnis mich ein wenig überrascht hat: Ich habe eine 100.000er-Schleife einmal mit dem Klonen-Befehl und einmal ohne laufen lassen und habe dann die Differenz pro Klonversuch errechnet. Und es waren lediglich 2ms. Das kommt mir jetzt doch etwas kurz vor, denn dies stellt eigentlich keine tragische Verzögerung dar. Vielleicht ist der play-Befehl der Übeltäter, wenn dieser mehrmals hintereinander aufgerufen wird?

LG Yogilein
 
Wobei sich 2ms bei 1000 Schritten zu 2 Sekunden summieren und das Gehör ist da ziemlih empfindlich - ich denke, dass man eine Verzögerung von 0.2 Sekunden schon merkt.

Für das play() hab' ich kein Gefühl, aber ich denke nicht, dass das auf möglichst schnelles Starten getrimmt wurde...
 
Für das play() hab' ich kein Gefühl, aber ich denke nicht, dass das auf möglichst schnelles Starten getrimmt wurde...
Leider kann ich's nicht richtig testen, da 1. bei einer 100.000-Schleife mein Rechner gar nichts ausgibt (JS überfordert?) und 2. die Tonausgabe asynchron erfolgt. D.h. ich bekomme in einer 100er-Schleife schon die Zeit angezeigt, während die Tonausgabe noch aktiv ist.

Würde alles ohne nennenswerte Verzögerung ablaufen, dürfte eigentlich nur ein Ton zu hören sein, evtl. 0,2s länger, da das Klonen diese Zeit beansprucht. Der letzte Ton kommt aber deutlich später, so dass ich davon ausgehe, dass tatsächlich der play-Befehl einiges an Zeit verbrät. Für einen einmaligen Aufruf völlig unkritisch, aber in einer Schleife ...

Apropos Zeit verbraten: Was macht eigentlich Safari, denn da kommt bereits der erste Ton leicht zeitverzögert.
 
Zum Safari kann ich nichts sagen, da der alte Safari für Windows kein <audio> kennt.

Aber mein Firefox meckert, wenn ich viele gleichzeitig starten will:
Medien-Ressource ... mp3 konnte nicht dekodiert werden.
Alle Kandidaten-Ressourcen konnten nicht geladen werden. Medien-Laden pausiert.
 
Ah... jetzt fällt's mir wieder ein: der Safari unterstützt <audio> und <video> nur, wenn man auch Quicktime installiert hat...
 
Zurück
Oben