# Einen Sound mit veränderter Tonhöhe und Geschwindigkeit abspielen - Hilfe erbeten



## Förster44 (11. Aug 2012)

Hallo,
ich möchte in meinem aktuellen Programm die Funktionalität, Sounds mit veränderter Tonhöhe und Geschwindigkeit* abzuspielen anhand von Faktoren wie 0.4 oder 1.7 . Die Sounds habe ich im .wav-Format (16bit) vorliegen. Da ich nun aber seit 2 Tagen rumprobiere, teste und versuche und die Ergebnisse recht kläglich bleiben, möchte ich hier Hilfe/Unterstützung erbitten (und würde mich natürlich sehr freuen wenn ich sie auch bekomme  )
Hier nun also meine bisherigen Versuche:
Zunächst die Konvertierung: Da ich ja 2 byte pro Frame "geliefert" bekam, hab ich diese mit folgendem in ein Int-Array (das dann bearbeitet wurde) gepackt und später wieder zurückkonvertiert:

```
static int[] toUnsignedByte(byte[] b)
	{
		int[] ret = new int[b.length/2];
		for(int i=0; i<ret.length; i++)
		{
			ret[i] = (int)(( ((int)b[i*2]) << 8) | ((int)b[i*2+1]));
		}
		return ret;
	}
	
	static byte[] toByte(int[] i)
	{
		byte[] ret = new byte[i.length*2];
		for(int j=0; j<i.length; j++)
		{
			ret[j*2] = (byte) (i[j] >> 8);
			ret[j*2+1] = (byte) (i[j] & 255);
		}
		return ret;
	}
```
Hier mein 1. Algorithmus (als erster Anlauf ein meinem Empfinden nach eher grobes Gehacke, das aber interessanterweise noch eher in manchen Fällen nach dem Original klingt als mein zweiter Versuch):

```
static byte[] pitch(int[] toPlay/*das "Originalarray" wurde bereits in konvertierter Form geliefert*/, float var)
	{
                float stretchFact = (1/var);
		
		int[] neu = new int[Math.round(toPlay.length*(1/var))];
		for(int i=0; i<neu.length; i++)
		{
			neu[i] = toPlay[(int)(i/stretchFact)];
		}
		
		int[] geglättet = new int[neu.length];
		geglättet[0] = neu[0];
		for( int i=1; i<neu.length-1; i++)
		{
			if(i % stretchFact == 0)
			{
				geglättet[i] = neu[i];
				continue;
			}
			geglättet[i] = (int)((neu[i-1]*2+neu[i]+neu[i+1]*2) / 5.0 + 0.5);
		}
		geglättet[neu.length-1] = neu[neu.length-1];
		
		for(int i = 0; i < geglättet.length; i++)
			if(Math.round(stretchFact*i) < geglättet.length)  
			  geglättet[Math.round(stretchFact*i)] = toPlay[i];
		return toByte(geglättet);
	}
```

Hier mein zweiter Versuch, diesmal unter dem Leitmotiv, den "Streckfaktor" zu verzehnfachen, die Lücken mit "linearen Übergängen" zu füllen ({60, 0, 0, 30} -> {60, 50, 40, 30}) und dann aus diesem Array jeden zehnten Wert in das "finale" Array einzulesen.

```
static byte[] pitch(int[] toPlay/*das "Originalarray" wurde bereits in konvertierter Form geliefert*/, float var)
	{
                int stretchFact = (int)((1/var)*10 + 0.5F);
		
		int[] temp = new int[toPlay.length*stretchFact];
		
		for(int i=0; i<toPlay.length; i++)
		{
			temp[i*stretchFact] = toPlay[i];
		}
		
		int step;
		for(int i=0; i<temp.length; i++)
		{
			step = i % stretchFact;
			if(step == 0)
				continue;
			try { temp[i] = ( toPlay[i/stretchFact] + toPlay[(i+stretchFact)/stretchFact] )*step/stretchFact; } catch(ArrayIndexOutOfBoundsException e) {}
		}
		
		int[] ret = new int[(int) (toPlay.length*(1/var)+0.5)];
		
		for(int i=0; i<ret.length; i++)
			try { ret[i] = temp[i*10]; } catch(ArrayIndexOutOfBoundsException e) {}
			
		return toByte(ret);
```

Wie gesagt, für Ratschläge/Hinweise/Tipps/Hilfe wäre ich sehr dankbar 


*sowohl aus Gründen der "Einfachheit" als auch aus Designgründen


----------



## Noisefever (12. Aug 2012)

willst du tonhöhe und geschwindigkeit trennen? dann bist du auf dem falschen weg mit lücken linear füllen. willst du nur ganz normal die geschwindigkeit ändern (wodurch die tonhöhe natürlich entsprechend angepaßt wird), dann frage ich mich: warum der umstand? ist es wichtig daß du das rendern selber übernimmst?


----------



## Förster44 (12. Aug 2012)

Noisefever hat gesagt.:


> willst du tonhöhe und geschwindigkeit trennen?



Nein, ich möchte sie nicht trennen, sondern beide gemeinsam gleichermaßen ändern. Sorry falls ich mich da unklar ausgedrückt habe 



Noisefever hat gesagt.:


> willst du nur ganz normal die geschwindigkeit ändern (wodurch die tonhöhe natürlich entsprechend angepaßt wird), dann frage ich mich: warum der umstand? ist es wichtig daß du das rendern selber übernimmst?



Umstand? Da gibts auch eine einfachere Variante die Geschwindigkeit (+Tonhöhe natürlich^^) zu ändern? Würde mich ja sehr freuen wenns so ist


----------



## Förster44 (12. Aug 2012)

Entschuldigung für den Doppelpost, aber ich bin scheinbar malwieder zu blind um den Bearbeiten-Button zu finden :bahnhof:

Jedenfalls wollte ich nur erwähnen (weil ich eben bemerkt habe, dass ich das nicht grade gut erkennbar gemacht habe), dass das eigentliche Problem hier die Tatsache ist, dass beide meiner Tonhöhe- und-Geschwindigkeits-Veränderungs-Algorithmen nicht das erwünschte Ergebnis liefern und sich die Sounds im besten Falle zumindest ansatzweise nach dem Ursprunssound anhören (was aber Ausnahmefälle sind, im Normalfall werden meine Lautsprecherboxen von einem unerkennbaren rauschigen Kreischen verge******t) und ich da wirklich nicht mehr weiter weiß


----------



## Marco13 (12. Aug 2012)

Da ich vor einigen Tagen auch mal was "in der Art" gemacht habe (also ein bißchen mit sounds rumgespielt... Frequenzanalyse mit FFT) : Was ist denn (in Worten) dein bisheriger Ansatz? (Ich würde ja den Code lesen, aber als ich ihn überflogen habe, sind mir bei [c]catch(ArrayIndexOutOfBoundsException e) {}[/c] die Augen geplatzt... )


----------



## Förster44 (12. Aug 2012)

Okay, ich versuchs mal schriftlich weiter zu erläutern (ich werd dabei nicht allzu in die Details gehen^^)...

1. Codeabschnitt: Da das Audioformat in 16bit (->2 Bytes/Frame) vorliegt, muss ich ja erstmal, um die einzelnen Frames bearbeiten zu können, 2 Bytes zu jeweils einem Integer zusammenfügen (dann bearbeiten), später dann wieder zu 2 Bytes auseinanderfitzeln und dass dann ausgeben lassen.

2. Codeabschnitt: var ist der Faktor zum Ändern, wobei 0.5 das zu bearbeitende Array um den Faktor 2 streckt (und der Ton somit tiefer klingt) und 1.5 das Array um den Faktor 2/3 staucht (und der Ton somit höher klingt), weswegen ich den Streckfaktor 1/var berechne und dadurch auch die Größe des Outputarrays festlege. Dieses wird dann mit den zugehörigen Werten aus dem Ursprungsarray befüllt und später dann anhand des Durschnitts der jeweiligen Nachbarswerte etwas geglättet, wobei ich den Nachbarschaftswerten eine doppelte Wertigkeit gab, weil ... naja, ähm, Intuition :rtfm:
Ab da geht das Array auch schon direkt weiter an die Lautsprecher (oder zumindest an das, was dafür zuständig ist, dass es dort landet).

3. Codeabschnitt: Hier wollte ich ursprünglich etwas "sauberer" arbeiten *hust hat ja auch super geklappt hust* .... wieder mal mit dem Streckfaktor, nur wollte ich diesmal das Kommazeugs loswerden (ich hab dem irgendwie misstraut, vor allem wenn % zum Einsatz kam) und hab den Steckfaktor verzehnfacht, was ich damit ausgleichen wollte, dass ich aus dem dann entstehenden Array einfach nur jeden 10ten Wert wieder in das Outputarray einlese. Sobald also das "Zwischen"-Array anhand des größeren Streckfaktors erstellt ist, werden die Werte aus dem Ursprungsarray der Skalierung entsprechend darin eingefügt. In der folgenden Schleife werden "leere" Stellen im "Zwischen"-Array mit "linearen Überbrückungswerten"  gefüllt, wobei bereits aus dem Ursprungsarray übernommene Werte unverändert bleiben (abstraktes Beispiel: {80, _, _, _, 40} wird zu {80, 70, 60, 50 , 40} (oder soll zumindest dazu werden)).
Im Anschluss wird dann noch, wie weiter oben "angekündigt", jeder zehnte Wert aus dem "Zwischen"-Array in das Output-Array übernommen und in die Weiten des restlichen Programms entlassen.

Ich hoffe das war jetzt verständlicher als mein Code^^


----------



## Marco13 (13. Aug 2012)

Uhrzeitbedingt hab' ich's jetzt nicht ganz nachvollzogen, aber ... ist das irgendwie Performancekritisch? Ich hatte bei meinen Experimenten dann irgendwann genug von Linker Kanal, Rechter Kanal, 1 oder 2 byte little oder big endian.... und hab' die Sounddaten dann einfach in einen FloatBuffer reingerechnet (der natürlich bei Bedarf auch wieder in eine abspielbare Form gebracht werden kann). Ganz naiv würde ich ja vermuten, dass man durch die Welle auch eine interpolierende Funktion durchlegen kann, und damit in bezug auf schnelleres oder langsameres Abspielen recht flexibel sein dürfte.


----------



## Spacerat (13. Aug 2012)

@Marco13: Das wollte ich auch grad' tippen. Liegst da gar nicht so verkehrt bzw. vollkommen richtig, zumindest, wenn er interpolieren will. Der FloatBuffer sollte dann aber nur Werte zwischen 0 und 1 speichern, nicht -1 bis +1, sonst wird das am Ende erfordeliche Biasing von Unsigned nach Singed ungenau.

Viel wichtiger aber ist, das er sich diese ganzen Array-Operationen (vergrössern, kopieren usw.) sparen kann, wenn er als Positionszeiger auf die Samples ein float verwendet, dann kann er mit der Formel:

```
(MAXFREQ / Periodendauer) / SAMPLERATE
```
diese Position inkremental verändern und die gecasteten ints * FRAMESIZE aus dieser Position zeigen immer auf das richtige Byte im original Array.
Die einzelnen Werte bedeuten:
MAXFREQ: maximale Frequenz der Tonhöhe.
Periodendauer: Dauer der Periode um eine gewisse Tonhöhe zu erreichen.
SAMPLERATE: Die Abtastrate des Widergabe-Audioformats.

Wie man auf die Werte MAXFREQ und Periodendauer kommt ist mir beim PC nicht geläufig. Ich hab' das Ganze aus 'ner Doku über ein uraltes Amiga Protracker2-Format. MAXFREQ ist dort 3546894,6Hz (Mittelwert zwischen PAL und NTSC) und die Periodenzeiten stehen in einer Tabelle.

```
907, 900, 894, 887, 881, 875, 868, 862,
		856, 850, 844, 838, 832, 826, 820, 814, //C_1
		808, 802, 796, 791, 785, 779, 774, 768, //Db1
		762, 757, 752, 746, 741, 736, 730, 725, //D_1
		720, 715, 709, 704, 699, 694, 689, 684, //Eb1
		678, 674, 670, 665, 660, 655, 651, 646, //E_1
		640, 637, 632, 628, 623, 619, 614, 610, //F_1
		604, 601, 597, 592, 588, 584, 580, 575, //Gb1
		570, 567, 563, 559, 555, 551, 547, 543, //G_1
		538, 535, 532, 528, 524, 520, 516, 513, //Ab1
		508, 505, 502, 498, 495, 491, 487, 484, //A_1
		480, 477, 474, 470, 467, 463, 460, 457, //Bb1
		453, 450, 447, 444, 441, 437, 434, 431, //B_1
		428, 425, 422, 419, 416, 413, 410, 407, //C_2
		404, 401, 398, 395, 392, 390, 387, 384, //Db2
		381, 379, 376, 373, 370, 368, 365, 363, //D_2
		360, 357, 355, 352, 350, 347, 345, 342, //Eb2
		339, 337, 335, 332, 330, 328, 325, 323, //E_2
		320, 318, 316, 314, 312, 309, 307, 305, //F_2
		302, 300, 298, 296, 294, 292, 290, 288, //Gb2
		285, 284, 282, 280, 278, 276, 274, 272, //G_2
		269, 268, 266, 264, 262, 260, 258, 256, //Ab2
		254, 253, 251, 249, 247, 245, 244, 242, //A_2
		240, 239, 237, 235, 233, 232, 230, 228, //Bb2
		226, 225, 224, 222, 220, 219, 217, 216, //B_2
		214, 213, 211, 209, 208, 206, 205, 204, //C_3
		202, 201, 199, 198, 196, 195, 193, 192, //Db3
		190, 198, 188, 187, 185, 184, 183, 181, //D_3
		180, 179, 177, 176, 175, 174, 172, 171, //Eb3
		170, 169, 167, 166, 165, 164, 163, 161, //E_3
		160, 159, 158, 157, 156, 155, 154, 152, //F_3
		151, 150, 149, 148, 147, 146, 145, 144, //Gb3
		143, 142, 141, 140, 139, 138, 137, 136, //G_3
		135, 134, 133, 132, 131, 130, 129, 128, //Ab3
		127, 126, 125, 125, 124, 123, 122, 121, //A_3
		120, 119, 118, 118, 117, 116, 115, 114, //Bb3
		113, 113, 112, 111, 110, 109, 109, 108, //B_3
		0, //___
		-1 //UNKNOWN;
```
Ich hab' keine Ahnung, was passiert, wenn man die Werte ändert (bis auf die Tatsache, dass es sich schrecklich anhört) aber evtl. findest du ja jemanden, der darüber genaueres weiss.


----------



## Förster44 (13. Aug 2012)

Bitte entschuldigt, wenn ich jetzt viele Anfängerfragen stelle, was meine Kenntnisse des (recht großen) Java-Klassen-Angebots betrifft, bin ich ein blutiger Anfänger :S



Marco13 hat gesagt.:


> Uhrzeitbedingt hab' ich's jetzt nicht ganz nachvollzogen, aber ... ist das irgendwie Performancekritisch?


Um die Performance hab ich mir vorerst weniger Gedanken gemacht, erstmal wollte ich den Algorithmus in einen funktionierenden Zustand bringen^^



Marco13 hat gesagt.:


> Ich hatte bei meinen Experimenten dann irgendwann genug von Linker Kanal, Rechter Kanal, 1 oder 2 byte little oder big endian.... und hab' die Sounddaten dann einfach in einen FloatBuffer reingerechnet (der natürlich bei Bedarf auch wieder in eine abspielbare Form gebracht werden kann).


Nach kurzem Googlen: Warum FloatBuffer (und warum float statt int und Konsorten)? Was ist da anders als bei einem "gewöhnlichen" Array? Welche Vorteile (außer evt. der Performance) bietet das? Hilft der FloatBuffer beim Verändern/Skalieren der Werte?



Marco13 hat gesagt.:


> Ganz naiv würde ich ja vermuten, dass man durch die Welle auch eine interpolierende Funktion durchlegen kann, und damit in bezug auf schnelleres oder langsameres Abspielen recht flexibel sein dürfte.


 Ist "durch die Welle" hier eine mir unbekannte Redewendung oder eine mir unbekannte Programmfunktionalität?



Spacerat hat gesagt.:


> Viel wichtiger aber ist, das er sich diese ganzen Array-Operationen (vergrössern, kopieren usw.) sparen kann, wenn er als Positionszeiger auf die Samples ein float verwendet, dann kann er mit der Formel:
> 
> ```
> (MAXFREQ / Periodendauer) / SAMPLERATE
> ...



Das verstehe ich grade nicht so recht ... ein float als Positionszeiger (geht das nicht nur mit Ganzzahlen)? Welche Komponente der Formel wird inkrementiert? Und wo kommt die Modifizierung zum Einsatz?


----------



## Marco13 (13. Aug 2012)

Ein float[] Array würde natürlich auch gehen. Ein FloatBuffer kann manchmal ganz praktisch sein, und fügt sich schön ins Bild, dass man die _Umwandlung_ bzw. Interpretation der Eingabedaten auch gut mit ByteBuffer bzw. ShortBuffer abhandeln kann. Wenn man eine WAV liest, dann hat die ja (abhängig vom genauen AudioFormat) z.B. 2 bytes pro Frame. Diese 2 bytes liest man aus dem AudioInputStream. Die kann man z.B. in einen ByteArrayOutputStream reinlesen, vom dem man später einen byte[]-Array abholen kann, der alle Daten enthält. Diesen byte[] array kann man mit ByteBuffer#wrap in einen ByteBuffer packen, und dann mit byteBuffer.asShortBuffer() als ShortBuffer ansehen. DER enthält dann wiederum genau die Daten, die du jetzt (etwas unpassenderweise) als int[] array gespeichert hast. Man spart sich damit die manuelle Umwandlung der einzlenen bytes mit den shift>>>operatoren und so.

"Durch die Welle" bezog sich darauf, dass ich (immernoch) glaube, dass durch diese Daten (sei es nun byte, short oder float) eine Kurve beschrieben wird. 

Achtung, was folgt ist Halbwissen und naives Wunschdenken, ich fräse mich da gerade erst rein. Wenn irgendwas grob falsch ist, möge man (Spacerat z.B.) mich darauf hinweisen:

Wenn diese Werte also z.B. diese sind:
2, 8, 15, 43, 21, 12, 3
dann entspricht einer Kurve die kurz ansteigt und wieder abfällt - ähnlich wie der erste Teil einer Sinuskurve. Die Zeitpunkte, für die diese Werte vorliegen, hängen von der Abtastrate ab, also bei 22050 liegen die Werte "1/22050" Sekunden auseinander.  Wenn man dort nun eine Funktion f durchlegt, so dass
f(0*dt) = 2
f(1*dt) = 8
f(2*dt) = 15
...
(wobei dt='1/22055')
dann hindert einen niemand daran, das ganze schneller abzuspielen, indem man zum eigentlichen Abspielen nicht die Daten f(t) verwendet, sondern die von f(t*speed). Dann bekommt man z.B.
f(0*dt*1.5) = 2
f(1*dt*1.5) = 13
f(2*dt*1.5) = 36
also eine Kurve, die schneller ansteigt. 

So, bei Gelegenheit werde ich mal drüber nachdenken (und vielleicht ausprobieren!) ob das stimmt und Sinn macht, was ich hier gerade geschrieben habe


----------



## Förster44 (13. Aug 2012)

Marco13 hat gesagt.:


> Ein float[] Array würde natürlich auch gehen. Ein FloatBuffer kann manchmal ganz praktisch sein, und fügt sich schön ins Bild, dass man die _Umwandlung_ bzw. Interpretation der Eingabedaten auch gut mit ByteBuffer bzw. ShortBuffer abhandeln kann. Wenn man eine WAV liest, dann hat die ja (abhängig vom genauen AudioFormat) z.B. 2 bytes pro Frame. Diese 2 bytes liest man aus dem AudioInputStream. Die kann man z.B. in einen ByteArrayOutputStream reinlesen, vom dem man später einen byte[]-Array abholen kann, der alle Daten enthält. Diesen byte[] array kann man mit ByteBuffer#wrap in einen ByteBuffer packen, und dann mit byteBuffer.asShortBuffer() als ShortBuffer ansehen. DER enthält dann wiederum genau die Daten, die du jetzt (etwas unpassenderweise) als int[] array gespeichert hast. Man spart sich damit die manuelle Umwandlung der einzlenen bytes mit den shift>>>operatoren und so.




Was mein bisheriges Ausprobieren mit den Buffern betrifft, hab ich in ShortBuffer keine Möglichkeit gefunden, das byte[] "wiederzubekommen" (mit dem Modifizieren warte ich normalerweise, bis die Konvertierung und das Drumherum stimmen)... außerdem waren auch im ShortBuffer negative Werte drin, ich meine gelesen zu haben dass negative Werte nicht so erwünscht sind?

(Danke übrigens auch für deine Tipps zum Modifizieren, wie gesagt melde ich mich dazu zurück wenn das Drumherum passt  )


----------



## Spacerat (13. Aug 2012)

Wie du das ByteArray widerbekommst? Gar nicht, du musst es nur als Instanz behalten. Selbiges gilt für den Byte- und den ShortBuffer. Letztere beiden teilen sich nämlich durch ihre Instanzierung per wrap, das ByteArray. Änderungen im Array bedeuten Änderungen in den Buffern und umgedreht. Das einzige um was du dir noch gedanken machen musst ist die ByteOrder (order = Reihenfolge) der Buffer, damit die Bytes auch in der richtigen Reihenfolge das Short ergeben.
Im übrigen ist Marcos Formel ([c]f(sample * sampleTime * speed)[/c] zum pitchen völlig ausreichend bzw. sogar besser, weil stufenlos. Bei der Protracker-Formel hingegen hat man die Frequenzabstufungen der jeweiligen Noten. Bei Marcos Formel muss man aber aufpassen, dass speed nicht 0 wird.


----------



## Förster44 (14. Aug 2012)

> Wie du das ByteArray widerbekommst? Gar nicht, du musst es nur als Instanz behalten.


 Ah, gut zu wissen!



> Das einzige um was du dir noch gedanken machen musst ist die ByteOrder (order = Reihenfolge) der Buffer, damit die Bytes auch in der richtigen Reihenfolge das Short ergeben.


Was ist denn die richtige Reihenfolge? (Beim ersten Anschauen der Buffer nahm ich diese Reihenfolge:

```
static byte[] pitch(byte[] toPlay, float var)
{
    ByteBuffer byteBuffer = ByteBuffer.wrap(toPlay);
    ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
}
```
Ich hoffe dass das so nicht richtig war, weil mich sonst die negativen Werte im ausgegebenen ShortBuffer sehr wundern würden...

Was Marcos Formel angeht, muss ich leider gestehen, dass ich nicht so recht verstehe was sie macht (bzw. wie sie das machen soll) (und wo man dt herbekommt, aber das ist Nebensache), ich hoffe ihr könnt mir das noch etwas genauer erklären ... ?


----------



## Spacerat (14. Aug 2012)

Okay... Grundlagen:
1. In deinem AudioStream (dem ByteArray) sind die Daten ja in einer bestimmten Reihenfolge abgelegt: (r = rechts, l = linkks)

```
{r0, l0, r1, l1, ... rN, lN}
```
2. Bei 16Bit-Samples benötigt man pro Sample also schon mal 2 Bytes: (l = low, h = high)

```
{rl0, rh0, ll0, lh0, rl1, rh1, ll1, lh1, ... rlN, rhN, llN, lhN}
```
3. Low- und Highbytes können pro Kanal nun auch vertauscht sein, das nennt sich ByteOrder Little.Endian oder BigEndian:

```
{rh0, rl0, lh0, ll0, rh1, rl1, lh1, ll1, ... rhN, rlN, lhN, llN}
```
4. Zuletzt ist noch interessant, ob die Daten im Stream Vorzeichenbehaftet sind oder nicht bzw. ob sie gar ein ganz anderes Encoding ausser PCM, nämlich ALAW oder ULAW haben.
5. Zum Abspielen des Streams benötigt man nun noch die Abspielgeschwindigkeit in Samples pro Sekunde (gängig sind 11025Hz, 22050Hz, 44100Hz und 48000Hz; Java untestützt bis zu V1.5 8000Hz bis 48000Hz), das ist die Samplerate. Daraus folgt, dass ein Sample genau 1/SampleRate Sekunden dauert, so kam Marco auch auf dt.

Wie dein AudioStream nun aufgebaut ist, bestimmt dessen AudioFormat ([c]javax.sound.sampled.AudioFormat[/c]). Diese Klasse ist an und für sich selbstredend. Damit lassen sich die AudioStreams individuell interpretieren. Eine Stream entsprechende Instanz davon bekommst du, indem du [c]<Stream>.getAudioFormat()[/c] aufrufst. Man kann also erst mal gar nicht sagen, ob dein Codeschnipsel gerade zufällig deinen Stream korrekt liest. Aber der Ansatz ist schon mal korrekt.


----------



## Förster44 (14. Aug 2012)

Danke für diese ausführlichen Informationen  Ich hab mir mal das Audioformat ausgeben lassen, sämtliche Kandidaten für die Tonhöhen-Änderungen liegen in folgendem Format vor:
Encoding PCM_SIGNED, Samplerate 16000.0 Hz, 16 bit, Mono, 2 bytes/frame, little-endian

Wie muss ich da dann also das byte[] (oder die Buffer) bearbeiten, um die Daten in weiterverarbeitbare Form zu bringen? (Ich frage hauptsächlich deshalb, weil Marco anfänglich meinte, mit den Shortbuffern könnte ich mir Bitverschiebungen etc. sparen und ich da jetzt etwas verunsichert bin.)


----------



## Marco13 (14. Aug 2012)

Spacerat hat gesagt.:


> Der FloatBuffer sollte dann aber nur Werte zwischen 0 und 1 speichern, nicht -1 bis +1, sonst wird das am Ende erfordeliche Biasing von Unsigned nach Singed ungenau.



Was meintest du damit?

Die Schwierigkeit bei diesen ganzen Sound-Sachen ist, dass man meistens (!) relativ (!) leicht eine Sache für ein bestimmtes Dateiformat schreiben kann. Wenn du mal so eine WAV irgendwo hochlädst, könnte man da mal was testen. Wenn man aber einigermaßen generisch bleiben will, wird es sehr schnell sehr aufwändig. Wenn du also jetzt eine funktionierende Version (mit einer Klasse mit 500 Zeilen) hast, und dann später auch nur eine einzige Datei mit Stereo oder unsigned oder big endian verarbeiten willst, musst du u.U. SEHR große Teile dafür schlicht neu schreiben.
Ich hatte mal angefangen, dafür ein paar Utilties zu schreiben, aber die sind noch ziemlich halbgar...


----------



## Spacerat (15. Aug 2012)

@Marco13: Das N-Flag vorzeichenbehafteter Samples (Zweierkomplement) macht den eigentlich geringeren Anteil der Wellenform zum grösseren. Ferner ist der negative Anteil im Zweierkomplement stets einen Wert grösser. Bei Floats zwischen -1 und +1 würdest du nun zwei Wertebereiche gleicher Länge bekommen, müsstest dich also entscheiden ob du z.B. +128 (ungültiger Wert) hinzunimmst oder -128 weglässt, beides ist sehr ungenau. Deswegen ist's ratsam, den gesamten Wertebereich in den positiven Bereich zu verschieben (+ 128) und ihn dann in ebenso positive Floats zu wandeln (/ 255). Bei der Rückwandlung in ein vorzeichenbehaftetes Format muss das natürlich wieder rückgängig gemacht werden. In der Elektrotechnik nennt sich das Biasing, zu deutsch Arbeitspunkt- bzw. 0-Linienverschiebung.
Eine generische Klasse zum Wandeln beliebiger AudioStreams in FloatBuffer kann ich auch vorweisen.

```
import java.io.File;
import java.nio.FloatBuffer;

import static javax.sound.sampled.AudioFormat.Encoding.*;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;


public class AudioTests {
	public static void main(String[] args) throws Exception {
		AudioInputStream ais = AudioSystem.getAudioInputStream(new File(args[0]).getAbsoluteFile());
		AudioFormat af = ais.getFormat();

		if(ALAW.equals(af.getEncoding()) || ULAW.equals(af.getEncoding())) {
			// Encoding ALAW und ULAW in PCM_Signed wandeln
			af = new AudioFormat(PCM_SIGNED,
					af.getSampleRate(),
					af.getSampleSizeInBits() * 2,
					af.getChannels(),
					af.getFrameSize() * 2,
					af.getFrameRate(),
					af.isBigEndian());
			// AudioInputStream durch die A- bzw. ULAW-Decoder lesen.
			ais = AudioSystem.getAudioInputStream(af, ais);
		}
		byte[] sample = new byte[af.getSampleSizeInBits() / 8];
		int sampleBytes = sample.length - 1;
		int mask = (int) Math.pow(2.0, af.getSampleSizeInBits()) - 1;
		int channels = af.getChannels();
		int bias = 0;
		long length = ais.getFrameLength();
		if(length > Integer.MAX_VALUE) {
			length = Integer.MAX_VALUE;
		}
		if(PCM_SIGNED.equals(af.getEncoding())) {
			bias = (int) Math.pow(2.0, af.getSampleSizeInBits() - 1.0);
		}
		int value;
		FloatBuffer[] channelBuffer = new FloatBuffer[af.getChannels()];
		for(int l = 0; l < length; l++) {
			for(int c = 0; c < channels; c++) {
				if(channelBuffer[c] == null) {
					channelBuffer[c] = FloatBuffer.allocate((int) length);
				}
				value = 0;
				ais.read(sample);
				for(int s = 0; s <= sampleBytes; s++) {
					value <<= 8;
					value |= (((af.isBigEndian())? sample[sampleBytes - s] : sample[s]) & 0xFF);
				}
				value = (value + bias) & mask;
				channelBuffer[c].put(value / (float) mask);
			}
		}
	}
}
```
Hoffe ich hab' da nichts vergessen oder verwechselt.


----------



## Förster44 (15. Aug 2012)

Vielen Dank! Es würde mich nur interessieren, wo man in deinem Codebeispiel dann letztendlich (nach dem Modifizieren*)  das abzuspielende byte[] herbekommt?


* dazu hätte ich auch noch eine Bitte ... Marcos Funktion f weiter oben scheint dazu ja gut geeignet zu sein, nur habe ich deren Funktionsweise (also wie die Funktion das machen soll) nicht so recht nachvollziehen können, wenn ihr mir das vielleicht noch erklären könntet ... würde mich sehr freuen


----------



## Spacerat (15. Aug 2012)

Zunächst erstmal, den FloatBuffer benötigst du nur, wenn du zwischen zwei Amplitudenabgriffen (oder bleiben wir schlicht bei Samples ) interpolieren willst, was nebenbei gesagt so ab 20kHz stetig zunehmend weniger Sinn macht (je höher die SampleRate wird, desto weniger Sinn macht's).
Ansonten würde deine Lösung mit Byte- und ShortBuffer schon hinhauen. Nur, warum du immer ein neues ByteArray mit der gestreckten bzw. gestauchten Grösse erstellen willst erschliesst sich mir nicht. Normalerweise arbeitet man in der Praxis mit zwei Puffern fester Grösse. Einer davon am Dateneingang, welcher mit dem Audiostream verknüpft ist und bei Bedarf vom Sampler aktualisiert (maw: weitergelesen) wird, der andere am Datenausgang, welcher mit einer DataLine verknüpft ist, die sofort blockiert, wenn die Hardwarepuffer voll sind. Als Eingangspuffer einer Line darf auch der Ausgangspuffer der Quelle verwendet werden, so reduziert man die Anzahl der in der Line zu definierenden Puffer auf nun mehr einen. Der Sampler bekommt einen Zeiger auf die jeweilige Sampleposition im Float-Format, welcher mittels Marcos oder meiner Formel inkrementiert wird, wobei er logischerweise auch zwischen zwei Abgriffen der Original-Wellenform landen kann. Hier hat man nun die wahl: Schreibe ich erneut den letzten Amplitudenwert in den Ausgangspuffer oder interpoliere ich anteilig zwischen der letzten und folgenden Amplitude und wenn ja, linear oder polynomial (verd. schreibt man das so? :autsch.
In der Hoffnung, dass du dich schon mal mit Lines (SDLs, TDLs, Mixer und Ports), AudioStreams und Controls auseinander gesetzt hast biete ich dir hier vorläufig erst mal die Klasse LinePitcher an, welche man mit der SDL instanzieren kann, in die man sonst den AudioStream direkt spoolen würde. Interpoliert wird darin aber noch nicht. 

```
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.Control;
import javax.sound.sampled.Control.Type;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;


public class LinePitcher implements SourceDataLine {
	private static final class PitchControl extends FloatControl {
		protected PitchControl() {
			super(Type.SAMPLE_RATE, 0.0F, 2.0F, Float.MIN_VALUE, 10, 1.0F, "", "-", "+", "0");
		}
		
	}
	private final SourceDataLine source;
	private final List<Control> controls;
	private final PitchControl ctrl;
	private ByteBuffer bOut;
	private float samplePos, stepRate;
	private int frameSize;

	public LinePitcher(SourceDataLine source) {
		this.source = source;
		List<Control> controls = new ArrayList<Control>(Arrays.asList(source.getControls()));
		ctrl = new PitchControl();
		controls.add(ctrl);
		this.controls = Collections.unmodifiableList(controls);
	}

	@Override
	public void drain() {
		synchronized(source) {
			source.drain();
		}
	}

	@Override
	public void flush() {
		synchronized (source) {
			source.flush();
		}
	}

	@Override
	public void start() {
		synchronized (source) {
			source.start();
		}
	}

	@Override
	public void stop() {
		synchronized (source) {
			source.stop();
		}
	}

	@Override
	public boolean isRunning() {
		return source.isRunning();
	}

	@Override
	public boolean isActive() {
		return source.isActive();
	}

	@Override
	public AudioFormat getFormat() {
		return source.getFormat();
	}

	@Override
	public int getBufferSize() {
		return source.getBufferSize();
	}

	@Override
	public int available() {
		return source.available();
	}

	@Override
	public int getFramePosition() {
		return source.getFramePosition();
	}

	@Override
	public long getLongFramePosition() {
		return source.getLongFramePosition();
	}

	@Override
	public long getMicrosecondPosition() {
		return source.getMicrosecondPosition();
	}

	@Override
	public float getLevel() {
		return source.getLevel();
	}

	@Override
	public javax.sound.sampled.Line.Info getLineInfo() {
		return source.getLineInfo();
	}

	@Override
	public void open() throws LineUnavailableException {
		source.open();
	}

	@Override
	public void close() {
		source.close();
	}

	@Override
	public boolean isOpen() {
		return source.isOpen();
	}

	@Override
	public Control[] getControls() {
		return controls.toArray(new Control[controls.size()]);
	}

	@Override
	public boolean isControlSupported(Type control) {
		for(Control c : controls) {
			if(c.getType() == control) {
				return true;
			}
		}
		return false;
	}

	@Override
	public Control getControl(Type control) {
		for(Control c : controls) {
			if(c.getType() == control) {
				return c;
			}
		}
		return null;
	}

	@Override
	public void addLineListener(LineListener listener) {
		source.addLineListener(listener);
	}

	@Override
	public void removeLineListener(LineListener listener) {
		source.removeLineListener(listener);
	}

	@Override
	public void open(AudioFormat format, int bufferSize) throws LineUnavailableException {
		synchronized (source) {
			source.open(format, bufferSize);
			bOut = ByteBuffer.wrap(new byte[bufferSize]);
			frameSize = format.getChannels() * ((format.getSampleSizeInBits() + 7) / 8);
			stepRate = 1.0f / format.getSampleRate(); // Das ist Marcos "dt"
			samplePos = 0.0f;
		}
	}

	@Override
	public void open(AudioFormat format) throws LineUnavailableException {
		int bufferSize = format.getChannels() * ((format.getSampleSizeInBits() + 7) / 8);
		bufferSize *= (int) (format.getSampleRate() / 25.0f) * format.getFrameSize();
		open(format, bufferSize);
	}

	@Override
	public int write(byte[] b, int off, int len) {
		synchronized(source) {
			ByteBuffer bIn = ByteBuffer.wrap(b, off, len);
			samplePos %= 1.0f;
			while(samplePos < len + 1) {
				for(int c = 0; c < frameSize; c++) {
					bOut.put(bIn.get((int) samplePos + c + off));
				}
				if(bOut.position() == bOut.capacity()) {
					source.write(bOut.array(), 0, bOut.capacity());
					bOut.clear();
				}
				samplePos += (stepRate * ctrl.getValue()); // und das ist der Rest von Marcos Formel
				if(samplePos % frameSize >= 1.0f) {
					samplePos += frameSize - 1;
				}
			}
			source.write(bOut.array(), 0, bOut.capacity());
			bOut.clear();
			return len;
		}
	}
}
```
Dieser LinePitcher ist leider noch nicht getestet, sollte aber funktionieren, wenn sich kein Flüchtigkeitsfehler eingeschlichen hat. Ich werde dazu auf jeden Fall noch ein kleines Demo veröffentlichen.


----------



## Spacerat (15. Aug 2012)

Eigentlich EDIT:
Okay... ein Mal Flüchtigkeit wurde schon mal entlarvt... die "stepRate" ist iwie viel zu kurz.  Mit Marcos Formel komm' ich da nicht hin... hmm...


----------



## Spacerat (15. Aug 2012)

Okay... got it

```
import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static javax.sound.sampled.AudioFormat.Encoding.*;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Control;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.AudioFormat.Encoding;
import javax.sound.sampled.Control.Type;

public class AudioTests {
	public static void main(String[] args) throws Throwable {
		if(args == null || args.length == 0) {
			args = new String[] {"test.mp3"}; // javazoom MP3 required!
		}
		AudioInputStream ais = ensurePCM(AudioSystem.getAudioInputStream(new File(args[0]).getAbsoluteFile()));
		AudioFormat af = ais.getFormat();
		LinePitcher lp = new LinePitcher(AudioSystem.getSourceDataLine(af));
		lp.open();
		lp.start();
		FloatControl pitch = (FloatControl) lp.getControl(LinePitcher.PITCH_CONTROL);
		FloatControl volume = (FloatControl) lp.getControl(FloatControl.Type.MASTER_GAIN);
		pitch.setValue(1.2f); // play @120% speed
		volume.setValue(volume.getMaximum()); // max volume
		int r = 0;
		byte[] buf = new byte[8192];
		while((r = ais.read(buf)) != -1) {
			lp.write(buf, 0, r);
		}
	}

	public static final AudioInputStream ensurePCM(AudioInputStream source)
	{
		AudioFormat af = source.getFormat();
		Encoding enc = af.getEncoding();
		if(!PCM_SIGNED.equals(enc) && !PCM_UNSIGNED.equals(enc)) {
			int ssb = af.getSampleSizeInBits();
			int c = af.getChannels();
			if(ssb < 8) {
				ssb = 8;
			}
			int minFs = c * ssb / 8;
			int fs = af.getFrameSize();
			if(fs < minFs) {
				fs = minFs;
			}
			af = new AudioFormat(
					AudioFormat.Encoding.PCM_SIGNED,
					af.getSampleRate(),
					ssb * 2,
					af.getChannels(),
					fs * 2,
					af.getSampleRate(),
					af.isBigEndian()
				);
			source = AudioSystem.getAudioInputStream(af, source);
		}
		return source;
	}
}

class LinePitcher implements SourceDataLine {
	public static final PitchControl.Type PITCH_CONTROL = new PitchControl.Type();
	private static final class PitchControl extends FloatControl {
		protected PitchControl() { // a control to pitch an audio stream from 0 (stop) to 2 * norm speed.
			super(PITCH_CONTROL, 0.0F, 2.0F, Float.MIN_VALUE, 10, 1.0F, "", "-", "+", "0");
		}

		private static final class Type extends FloatControl.Type {
			private Type() {
				super("Pitch Control");
			}
		}
	}
	private final Object lock = new Object();
	private final SourceDataLine source;
	private final List<Control> controls;
	private final PitchControl ctrl;
	private ByteBuffer bOut;
	private float samplePos;
	private int frameSize;

	public LinePitcher(SourceDataLine source) {
		this.source = source;
		List<Control> controls = new ArrayList<Control>(Arrays.asList(source.getControls()));
		ctrl = new PitchControl();
		controls.add(ctrl);
		this.controls = Collections.unmodifiableList(controls);
	}

	@Override
	public void drain() {
		synchronized(lock) {
			source.drain();
		}
	}

	@Override
	public void flush() {
		synchronized(lock) {
			source.flush();
		}
	}

	@Override
	public void start() {
		synchronized(lock) {
			source.start();
		}
	}

	@Override
	public void stop() {
		synchronized(lock) {
			source.stop();
		}
	}

	@Override
	public boolean isRunning() {
		return source.isRunning();
	}

	@Override
	public boolean isActive() {
		return source.isActive();
	}

	@Override
	public AudioFormat getFormat() {
		return source.getFormat();
	}

	@Override
	public int getBufferSize() {
		return source.getBufferSize();
	}

	@Override
	public int available() {
		return source.available();
	}

	@Override
	public int getFramePosition() {
		return source.getFramePosition();
	}

	@Override
	public long getLongFramePosition() {
		return source.getLongFramePosition();
	}

	@Override
	public long getMicrosecondPosition() {
		return source.getMicrosecondPosition();
	}

	@Override
	public float getLevel() {
		return source.getLevel();
	}

	@Override
	public javax.sound.sampled.Line.Info getLineInfo() {
		return source.getLineInfo();
	}

	@Override
	public void open() throws LineUnavailableException {
		open(source.getFormat());
	}

	@Override
	public void close() {
		source.close();
	}

	@Override
	public boolean isOpen() {
		return source.isOpen();
	}

	@Override
	public Control[] getControls() {
		return controls.toArray(new Control[controls.size()]);
	}

	@Override
	public boolean isControlSupported(Type control) {
		for(Control c : controls) {
			if(c.getType() == control) {
				return true;
			}
		}
		return false;
	}

	@Override
	public Control getControl(Type control) {
		for(Control c : controls) {
			if(c.getType() == control) {
				return c;
			}
		}
		return null;
	}

	@Override
	public void addLineListener(LineListener listener) {
		source.addLineListener(listener);
	}

	@Override
	public void removeLineListener(LineListener listener) {
		source.removeLineListener(listener);
	}

	

	@Override
	public void open(AudioFormat format, int bufferSize) throws LineUnavailableException {
		synchronized(lock) {
			source.open(format, bufferSize);
			bOut = ByteBuffer.wrap(new byte[bufferSize]);
			frameSize = format.getChannels() * ((format.getSampleSizeInBits() + 7) / 8);
			samplePos = 0.0f;
		}
	}

	@Override
	public void open(AudioFormat format) throws LineUnavailableException {
		int bufferSize = format.getChannels() * ((format.getSampleSizeInBits() + 7) / 8);
		bufferSize *= (int) (format.getSampleRate() / 25.0f) * format.getFrameSize();
		open(format, bufferSize);
	}

	@Override
	public int write(byte[] b, int off, int len) {
		synchronized(lock) {
			ByteBuffer bIn = ByteBuffer.wrap(b, off, len);
			samplePos %= 1.0F;
			int p = 0;
			while(p < len) {
				for(int c = 0; c < frameSize; c++) {
					bOut.put(bIn.get(p + c + off));
				}
				if(bOut.position() == bOut.capacity()) {
					byte[] bb = bOut.array().clone();
					source.write(bb, 0, bOut.capacity());
					bOut.clear();
				}
				samplePos += ctrl.getValue();
				p = (int) samplePos * frameSize;
			}
			b = bOut.array().clone();
			source.write(b, 0, bOut.position());
			bOut.clear();
			return len;
		}
	}
}
```
Die Werte der Controls der gesamten SoundAPI lassen sich im übrigen auch bequem über GUIs steuern... und jetzt bau' ich mir DJ-Software in Java :lol:


----------



## Förster44 (16. Aug 2012)

So, jetzt mach ich mich auch mal ans Ausprobieren deines Codes (danke nochmals dafür übrigens  ) ...
Warum ist nun eigentlich "plötzlich" .mp3 das AudioFormat der Wahl? Ich dachte .mp3 wird von der Java Sound API gar nicht unterstützt?


----------



## Spacerat (16. Aug 2012)

Ertappt! Du liest keine Kommentare. :lol: javazoom MP3 required heisst
MP3 library for the Java Platform benötigt.


----------



## Förster44 (16. Aug 2012)

Spacerat hat gesagt.:


> Ertappt! Du liest keine Kommentare. :lol: javazoom MP3 required heisst
> MP3 library for the Java Platform benötigt.



Doch, den Kommentar hab ich gelesen, aber das war gestern Abend und da kam ich nicht auf den Gedanken dass das eine Zusatz-Library sein könnte 

Also zum Thema Zusatz-Library ... da es sich bei jenem Projekt, in dem ich dieses Pitching auch einbauen möchte, um ein Applet handelt, stellt sich mir nun die Frage, wie ich diese Library darin einbinden kann (ich kann in deinem Code auch keine Importe dieser Library erkennen) oder ob jeder, der mein Applet nutzen möchte, sich vorher diese Library installieren muss?


----------



## Spacerat (16. Aug 2012)

Also ich bekomm's zumindest nicht hin, den jLayer in Applets zu nutzen, weder installiert noch in der Website gebundled.


----------



## Förster44 (16. Aug 2012)

Argh ... das darf doch nicht wahr sein ;(

Was genau müsste denn gemacht werden, um den Code auch mit .wav lauffähig zu bekommen?


----------



## Spacerat (16. Aug 2012)

'ne wav-datei laden? z.B. XD
Nein, ernsthaft... du musst nur statt "test.mp3" "test.wav" da rein sechreiben, dann lädt er die, sofern es eine "test.wav" gibt.


----------



## Förster44 (16. Aug 2012)

Spacerat hat gesagt.:


> 'ne wav-datei laden? z.B. XD
> Nein, ernsthaft... du musst nur statt "test.mp3" "test.wav" da rein sechreiben, dann lädt er die, sofern es eine "test.wav" gibt.



Achso, okay xD
Ich hatte jetzt gedacht dass dein Code nur mit .mp3 wegen kodierungstechnischer Umstände kompatibel sei, aber umso besser wenns nicht so ist 
Dann melde ich mich gleich mit den Ergebnissen der Implementierung (in meinem Applet) zurück 

Edit: Es funktioniert, wunderbar! Vielen vielen Dank nochmals :toll:


----------



## Spacerat (17. Aug 2012)

Juhuuu... Java7 macht alles anders. Zumindest bekomm' ich von einer SourceDataLine keine Volume- bzw. Master-Gain- oder überhaupt irgend eine andere Control mehr. Deswegen funktioniert der Code oben auf Java7 schon mal nicht mehr. Abwärtskompatibilität geht anders. Hat das Oracle etwa aufgegeben? Mal sehen, was nach dem Update noch alles für Überraschungen kommen.


----------



## Spacerat (17. Aug 2012)

Ein Glück... Entwarnung... Die Controls gibt es in Java 7 in der gleichen Form wie eh' und je doch noch, wäre ja verwunderlich, wenn nicht. Allerdings scheinen sie erst verfügbar zu sein, wenn die Line geöffnet wurde.


----------

