Sound-Sampling: Sinuston; kein Fehler, aber auch kein Ton :-(

Goldi

Aktives Mitglied
Hallo zusammen,

die Literatur und das Internet konnten mir bisher nicht weiterhelfen, weil es doch wohl ein sehr spezielles Thema ist. Ich brauche Unterstützung von jemandem, der sich im Sound-Sampling auskennt. Was mache ich falsch? Die Kiste gibt zwar keinen Fehler aus, aber hören tut man auch nix. Ich bin mir hinsichtlich der zahlreichen Parameter, die es beim Sound-Sampling gibt, aber auch noch nicht so 100-%-ig sicher - mangels Literatur ;-). Ich habe mal ein möglichst knappes Programm geschrieben, das natürlich auch nicht funktioniert, und möchte gerne wissen, was falsch ist. Bitte um Hilfe!

Java:
import java.io.*;
import javax.sound.sampled.*;

public class Sinuston {

	// Meine Parameter:
	float frequenz = 558.15f; 			// Frequenz des Sinustons in Hz (ungefähr ein "cis")
	float mySampleRate = 44100f; 		// Frequenz des Samplings in Hz
	int mySampleSizeInBits = 16;		// Auflösung der Abtastung; ein Sample umfasst 16 Bit = 2 Byte.
	int myChannels = 2;					// Ich mags Stereo
	int myFrameSizeInByte = (int)(mySampleSizeInBits / 2 * myChannels);
	int amplitude = 30000;				// Entspricht der Lautstärke; eine "Verstärkung" des y-Wertes der Sinus-
										// Funktion
	float myFrameRate = mySampleRate;   // Java: "The number of frames played or recorded per second"
	boolean myBigEndian = false;		// Die Bedeutung des "Big-Endian" oder "Little-Endian" in diesem Kontext
										// ist mir noch unklar!!
	
	
	// Liefert die Sampling-Daten eines Sinustons in Form eines Byte-Streams zurück:
	public ByteArrayOutputStream SinusDaten() {
		
		ByteArrayOutputStream oByteReihe = new ByteArrayOutputStream();
		
		/* Errechnet zu jedem Zeitpunkt eines Samples (Bruchteil einer Sekunde) den Sinuswert. Mathematisch 
		   betrachtet: Sinus = Winkelfunktion; Eine Sinuskurve ist vollständig abgelaufen, wenn quasi der Einheits-
		   kreis (r = 1) einmal umrundet ist, beginnend bei Bogenmaß = 0, endendend bei Bogenmaß = Kreißumfang = 
		   2 x r x Pi = 2 x 1 x Pi = 2 x Pi. Der Einheitskreis muss 558,15 Mal pro Sekunde umrundet werden (= Frequenz: 
		   558,15 Hz). 1 Umrundung dauert 1/558,15 sec. Frage: Welcher Sinuswert besteht bei z. B. einer 3/44100 sec.
		   (3. Abtastpunkt)?
		 */
		
		double BMBruchteil = (double)(frequenz / mySampleRate); // BMBruchteil = Bogenmaßbruchteil pro Sample (0,0127)
													 // Umkehrschluss: 1 Sinusschwingung hat 0,0127 x 558,15 Hz 
													 // = ca. 7 Abtastpunkte (Samples).
		double Kreisumfang = (float)(Math.PI * 2);	 // entspr. 360° = 1 abgeschlossene Sinuskurve; siehe Mathe-Buch ;-).
		double Bogenmass = 0;
		double Sinus;
		
		for (int i = 0; i <= mySampleRate; i = i++) {
			Sinus = (Math.sin(Bogenmass)) * amplitude;
			
			// Schreibe an den ByteStream:
			oByteReihe.write((byte)Sinus);
			// Schreibe den 2. Byte:
			oByteReihe.write((byte)Sinus >>> 8);
			
			// Für nächsten Abtastpunkt:
			Bogenmass = Bogenmass + Kreisumfang * BMBruchteil;
		}
		
		return oByteReihe;
	}
	
	
	public void SinusTonAbspielen() {
		
		// Definiere ein Audio-Format:
		AudioFormat aif = new AudioFormat(
				AudioFormat.Encoding.PCM_SIGNED, 	// empfohlen in einem Buch
				mySampleRate, 						// 44100
				mySampleSizeInBits, 				// 16 Bit
				myChannels, 						// 2 = Stereo
				myFrameSizeInByte, 					// 4, resultierend aus SampleSizeInBits und Channels
				myFrameRate,						// 44100
				myBigEndian);						// false; empfohlen in einem Buch
		
		// Für den Sinuston einen Audio-Inputstream:
		ByteArrayOutputStream bos = SinusDaten();
		InputStream is = new ByteArrayInputStream(bos.toByteArray());
		AudioInputStream ais = new AudioInputStream(is, aif, bos.size() / aif.getFrameSize());
		
		// Jetzt brauche ich eine Line, ...
		DataLine.Info myDataLineInfo = new DataLine.Info(Clip.class, aif);
		
		// ... die ich auf einen Clip caste:
		Clip myClip = null;
		try {
			myClip = (Clip)AudioSystem.getLine(myDataLineInfo);
		} catch (LineUnavailableException e) {
			System.out.println("Line mag nicht auf Clip!");
			System.exit(0);
		}
		
		// Jetzt wrid's ernst: Der Clip soll abgespielt werden:
		try {
			myClip.open(ais);
		} catch (LineUnavailableException e) {
			System.out.println("Der Clip mag nicht (LineUnavailableException)!");
		} catch (IOException e) {
			System.out.println("Der Clip mag nicht (IOException)");
		}
	}
	
	
	public static void main(String[] args) {
		Sinuston st = new Sinuston();
		st.SinusTonAbspielen();
	}
}
 
Ich habe mittlerweile selbst was entdeckt - aber leider das Problem nicht gelöst. Also, um Euch die Mühe zu sparen, darauf noch einzugehen, hier 3 kleine Änderungen des Codes:

Mit ner Endlosschleife vor dem Clip kanns natürlich keinen Ton geben - peinlich :applaus::
[JAVA=39](int i = 0; i <= mySampleRate; i++)[/code]

Die Formel für die Frame-Rate war natürlich falsch (zumindest für mein Verständnis, aber was weiß ich vom Sound-API :-():
[JAVA=11]int myFrameSizeInByte = (int)(mySampleSizeInBits / 8 * myChannels);[/code]

Und dann dachte ich mir, ich veersuchs noch mit einem Loop, der das Sampling ein paar mal abspielt:
[JAVA=85]try {
myClip.open(ais);
myClip.loop(2);
} catch (LineUnavailableException e)
usw.[/code]

Hat nur alles nix gebracht: Keine Fehlermeldung, aber auch kein Ton. Ich hoffe nach wie vor auf jemanden, der mir da ein bisschen Licht in das Dunkel meiner Verzweiflung bringt :rtfm:.

Besten Dank schon mal und schöne Grüße
Goldi
 
Zuletzt bearbeitet:
Hallo,

ein Problem ist, dass du nicht auf das Ende des Abspielen wartest. Der Ton wird nebenläufig abgespielt, d.h. dein Programm blockiert nicht während des Abspielens - stattdessen wird es beendet, weil main endet. Das lässt sich folgendermaßen beheben:

Java:
// Jetzt wrid's ernst: Der Clip soll abgespielt werden:
try {
    myClip.open(ais);
    myClip.start();
    Thread.sleep(2000);
    ...

Ein anderes Problem ist, dass du den Sinus-Wert auf byte castest - dadurch verlierst du die nötige Größe, denn deine Amplitude geht bis 30.000, byte-Werte werden aber ab 127 abgeschnitten. Die Lösung ist es, erst zu shiften und dann zu casten:

Java:
int data = (int) ((Math.sin(Bogenmass)) * amplitude);

// Schreibe an den ByteStream:
oByteReihe.write(data & 0xFF);
// Schreibe den 2. Byte:
oByteReihe.write((data >> 8) & 0xFF);

Damit wird bei mir ein schöner Ton gespielt, bei dir auch? 🙂
 
Hi rme!

:toll: Vielen Dank! Das war's!! Ich bin restlos begeistert!!!

Ich habe in der Zwischenzeit noch eine andere Lösung mit einem DataSourceStream und einem Streaming-Verfahren über einen Byte-Puffer gefunden. Das ist natürlich für so kleine Datenmengen umständlich. Jetzt geht beides - viel gelernt :applaus:

Das mit dem Shiften ... Ich sag's ehrlich: Da muss ich mich auch erst mal einlesen, nach dem Mittagessen 😛opcorn:. Ich habe dazu auch noch eine Lösung gefunden, und zwar mit der java.noi-Bibliothek. Da packe ich den Datenstrom erst in einen Short-Stream (Short = 2 Byte = 16 Bit), der dann in einen Byte-Stream übersetzt wird.

Jedenfalls herzlichen Dank für die Lösung und noch einen schönen Sonntag!

Gruß
Godli

PS: Eine weitere Entdeckung, die ich freilich allen zu Teil werden lassen möchte, die's interessiert: Dick Baldwin hat sehr erschöpfende und gute Tutorials u. a. was das Sound-API angeht - halt in Englisch, da kann man sprachlich auch noch zulegen :idea:. Hier ein Link:
Java and JavaScript Programming, by Richard G Baldwin
 

Zurück
Oben