Falls es dich noch interessiert:
Die PCMSoundStream-Klasse stellt einen Stream bereit, der beim Aufruf der Stream.Read-Funktion die Töne in einen Puffer einliest. Hierbei wird das Wave-Dateien zugrunde liegende Konzept aufgegriffen. In Wave-Dateien sind lediglich die Elongationen aneinandergereiht. Der Puffer wird bei mir über einen Delegaten gefüttert, damit wenigstens etwas Dynamik im Stream herrscht. Das Kernstück des Programms ist eigentlich genau dieser Stream.
Wave-Dateien bestehen aus einem Header und Chunks (Brocken). Der Header stellt nur Informationen über die Wave-Datei selber zur Verfügung: Dass es sich um eine RIFF-Datei handelt, wie lang sie ist und dass es speziell eine Wave-Datei ist. Die vom PCMSoundStream erzeugte Wave-Datei liest den Header aus einem Array ein, das im Konstruktor erzeugt wird. Die Informationen werden über die WaveFormat-Struktur bereitgestellt, die Dauer und der der Berechnung zugrunde liegende Delegat werden ebenfalls übergeben:
Der Delegat bei mir erzeugt Sinus-Wellen, also quasi "harmonische Wellen". Er wird in der ApplicationEssentials-Klasse definiert:
Wie man erkennt, wird die Berechnung auf Basis des Formates und der Position im Stream vorgenommen (vgl. blockOffset und PCMSoundStream.Read). Da mein Test nur mono ist, brauche ich auch nur eine Berechnung auszuführen. Die Berechnung hängt halt von der Frequenz ab, die bei mir 440 Hz ist (also 440.0#). Die Größe eines Samples ist bei mir 16, woraus auch eine Amplitude von Short.MaxValue hergeleitet wird (nach unten hin ist halt leider noch 1 Wert Platz, aber das war mir jetzt egal). Der Rest steht ja da: die vergangene Zeit t = blockOffset / format.SamplesPerSecond. Das ist jetzt aber genau auf diesen einen Fall zugeschneidert, da bei mir die Kanäle nicht berücksichtigt werden (sonst müsste man t = blockOffset / (format.SamplesPerSecond * format.BlockAlign) oder so nehmen). Der Rest ist wahrscheinlich selbsterklärend.
Jetzt noch zum Konstruktor von PCMSoundStream und PCMSoundStream.Read:
Da wir kein unsafe in VB haben und ich aus Faulheit nicht über Marshalling arbeiten wollte, habe ich einfach Array.Copy und BitConverter.GetBytes verwendet. BitConverter.GetBytes gibt die Bytes von primitiven Datentypen zurück. Diese werden dann in das Array kopiert, das den Header der vereinfachten Datei bereitstellt. PCM-Wave-Dateien enthalten ganzzahlige Werte, wie SByte oder Int16 (Short).
Jetzt noch die Read-Funktion mit Kommentaren:
Da war ich wohl nicht ganz fit, als ich den Code geschrieben hab... Die äußere If-Abfrage sollte weg und count -= hcount sollte innen stehen. Außerdem wird nicht um count erhöht, sondern um Read.. Entschuldigung für all die Fehler, das sollte nicht passieren...
Der restliche Inhalt des PCMSoundStreams ist dazu da, den Stream zu vollenden.
Gruß
~blaze~
Die PCMSoundStream-Klasse stellt einen Stream bereit, der beim Aufruf der Stream.Read-Funktion die Töne in einen Puffer einliest. Hierbei wird das Wave-Dateien zugrunde liegende Konzept aufgegriffen. In Wave-Dateien sind lediglich die Elongationen aneinandergereiht. Der Puffer wird bei mir über einen Delegaten gefüttert, damit wenigstens etwas Dynamik im Stream herrscht. Das Kernstück des Programms ist eigentlich genau dieser Stream.
Wave-Dateien bestehen aus einem Header und Chunks (Brocken). Der Header stellt nur Informationen über die Wave-Datei selber zur Verfügung: Dass es sich um eine RIFF-Datei handelt, wie lang sie ist und dass es speziell eine Wave-Datei ist. Die vom PCMSoundStream erzeugte Wave-Datei liest den Header aus einem Array ein, das im Konstruktor erzeugt wird. Die Informationen werden über die WaveFormat-Struktur bereitgestellt, die Dauer und der der Berechnung zugrunde liegende Delegat werden ebenfalls übergeben:
Der Delegat bei mir erzeugt Sinus-Wellen, also quasi "harmonische Wellen". Er wird in der ApplicationEssentials-Klasse definiert:
VB.NET-Quellcode
- Private Shared Sub SineHandler(ByVal buffer() As Byte, ByVal offset As Integer, ByVal format As WaveFormat, ByVal blockOffset As Integer)
- 'channels = 1 ==> t = blockOffset / format.SamplesPerSecond
- Array.Copy(BitConverter.GetBytes(CShort(Short.MaxValue * Math.Sin(blockOffset / format.SamplesPerSecond * Math.PI * 440.0#))), 0, buffer, offset, 2)
- End Sub
Wie man erkennt, wird die Berechnung auf Basis des Formates und der Position im Stream vorgenommen (vgl. blockOffset und PCMSoundStream.Read). Da mein Test nur mono ist, brauche ich auch nur eine Berechnung auszuführen. Die Berechnung hängt halt von der Frequenz ab, die bei mir 440 Hz ist (also 440.0#). Die Größe eines Samples ist bei mir 16, woraus auch eine Amplitude von Short.MaxValue hergeleitet wird (nach unten hin ist halt leider noch 1 Wert Platz, aber das war mir jetzt egal). Der Rest steht ja da: die vergangene Zeit t = blockOffset / format.SamplesPerSecond. Das ist jetzt aber genau auf diesen einen Fall zugeschneidert, da bei mir die Kanäle nicht berücksichtigt werden (sonst müsste man t = blockOffset / (format.SamplesPerSecond * format.BlockAlign) oder so nehmen). Der Rest ist wahrscheinlich selbsterklärend.
Jetzt noch zum Konstruktor von PCMSoundStream und PCMSoundStream.Read:
VB.NET-Quellcode
- Public Sub New(ByVal format As WaveFormat, ByVal duration As TimeSpan, ByVal computionHandler As ComputeValue)
- btHeader = New Byte(43) {}
- wfFormat = format
- intLength = CInt((format.AverageBytesPerSecond * duration.Ticks) \ TimeSpan.TicksPerSecond)
- Array.Copy(BitConverter.GetBytes(1179011410), btHeader, 4) 'RIFF
- Array.Copy(BitConverter.GetBytes(intLength), 0, btHeader, 4, 4)
- Array.Copy(BitConverter.GetBytes(1163280727), 0, btHeader, 8, 4) 'WAVE
- Array.Copy(BitConverter.GetBytes(544501094), 0, btHeader, 12, 4) 'fmt
- Array.Copy(BitConverter.GetBytes(16), 0, btHeader, 16, 4)
- Array.Copy(BitConverter.GetBytes(CShort(format.FormatTag)), 0, btHeader, 20, 2)
- Array.Copy(BitConverter.GetBytes(format.Channels), 0, btHeader, 22, 2)
- Array.Copy(BitConverter.GetBytes(format.SamplesPerSecond), 0, btHeader, 24, 4)
- Array.Copy(BitConverter.GetBytes(format.AverageBytesPerSecond), 0, btHeader, 28, 4)
- Array.Copy(BitConverter.GetBytes(format.BlockAlign), 0, btHeader, 32, 2)
- Array.Copy(BitConverter.GetBytes(format.BitsPerSample), 0, btHeader, 34, 2)
- Array.Copy(BitConverter.GetBytes(1635017060), 0, btHeader, 36, 4) 'data
- Array.Copy(BitConverter.GetBytes(intLength - 36), 0, btHeader, 40, 4)
- cvCompution = computionHandler
- End Sub
Da wir kein unsafe in VB haben und ich aus Faulheit nicht über Marshalling arbeiten wollte, habe ich einfach Array.Copy und BitConverter.GetBytes verwendet. BitConverter.GetBytes gibt die Bytes von primitiven Datentypen zurück. Diese werden dann in das Array kopiert, das den Header der vereinfachten Datei bereitstellt. PCM-Wave-Dateien enthalten ganzzahlige Werte, wie SByte oder Int16 (Short).
Jetzt noch die Read-Funktion mit Kommentaren:
VB.NET-Quellcode
- Public Overrides Function Read(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) As Integer
- Read = Math.Min(count, intLength - intOffset)'... ermitteln wir die Anzahl der insgesamt ausgelesenen Bytes
- If intOffset < 44 Then 'werden Informationen aus dem Header benoetigt
- Dim hcount As Integer = Math.Min(44 - intOffset, count) 'enthaelt hcount die Anzahl der vom Header gelesenen Bytes
- Array.Copy(btHeader, intOffset, buffer, offset, hcount) 'der Headerinhalt wird kopiert
- If count > hcount Then 'ist die Anzahl der zu lesenden Bytes groesser, als der gelesene Header-Inhalt...
- count -= hcount '...wird die verbleibende Anzahl der Bytes wird berechnet
- For i As Integer = hcount To Read - 1 Step wfFormat.BlockAlign 'und die Werte werden eingelesen
- 'BlockAlign gibt uebrigens die Groesse eines Blocks ab (also Anz. der Kanaele * Sample-Groesse)
- cvCompution(buffer, offset + i, wfFormat, intOffset + i)
- Next
- End If
- Else
- 'sind keine Header-Informationen notwendig, werden die Werte einfach berechnet:
- For i As Integer = 0 To Read - 1 Step wfFormat.BlockAlign
- 'BlockAlign gibt uebrigens die Groesse eines Blocks ab (also Anz. der Kanaele * Sample-Groesse)
- cvCompution(buffer, offset + i, wfFormat, intOffset + i)
- Next
- End If
- intOffset += Read
- End Function
Da war ich wohl nicht ganz fit, als ich den Code geschrieben hab... Die äußere If-Abfrage sollte weg und count -= hcount sollte innen stehen. Außerdem wird nicht um count erhöht, sondern um Read.. Entschuldigung für all die Fehler, das sollte nicht passieren...
Der restliche Inhalt des PCMSoundStreams ist dazu da, den Stream zu vollenden.
Gruß
~blaze~