IT-Academy Logo
Sign Up Login Help
Home - Programmieren - Visual Basic - Arbeiten mit DirectSound in VB



Arbeiten mit DirectSound in VB

Diese Tutorial-Reihe soll zeigen, wie man DirectSound initialisiert, Sound-Dateien lädt, abspielt und einige Effekte auf den Sound anwendet.


Autor: Tobias Surmann (incsoft)
Datum: 29-03-2003, 11:40:42
Referenzen: http://www.vbDirectX.de
Schwierigkeit: Fortgeschrittene
Ansichten: 12161x
Rating: 8.67 (3x bewertet)

Hinweis:

Für den hier dargestellte Inhalt ist nicht der Betreiber der Plattform, sondern der jeweilige Autor verantwortlich.
Falls Sie Missbrauch vermuten, bitten wir Sie, uns unter missbrauch@it-academy.cc zu kontaktieren.

[Druckansicht] [Als E-Mail senden] [Kommentar verfassen]



Einleitung

Als Vorraussetzung um diese Tutorials verstehen zu können solltest du schon mit der Programmiersprache Visual Basic umgehen können und zumindest die Grundlagen der Sprache verstehen. Wenn du schon einige Programme erstellt hast wäre das natürlich sehr von Vorteil.

Wir werden eine DirectSound Engine entwickeln, die in jedem beliebigen Spiel einsetzbar ist.

Abschnitt 1: DirectSound initialisieren

Genau wie bei DirectDraw muss man DirectSound auch erst initialisieren bevor man dessen Funktionen nutzen kann. Die Initialisierung von DirectSound ist das Thema dieses Tutorials.

Bevor wir damit beginnen Quelltext zu schreiben müssen wir erst mal ein neues Projekt anlegen. Dann fügen wir dem Projekt ein neues Klassenmodul hinzu. Wenn du schon an einem Spiel schreibst und nur noch eine Soundunterstützung brauchst kannst du das neue Klassenmodul natürlich auch sofort in diesem Projekt anlegen. Das Klassenmodul kann auch später noch zu einem anderen Projekt hinzugefügt werden, da es sehr allgemein geschrieben ist und mit jedem beliebigen Spiel funktionieren sollte. Das neue Klassenmodul nennen wir vbDX_SndEngine. Wir speichern das noch leere Modul unter dem Namen cls_SndEngine.cls ab.

Wir fügen einen Verweis auf die DirectX-API ein (s. DirectX in die Entwicklungsumgebung einfügen). Somit stehen uns alle Objekte, Methoden, Eigenschaften und Konstanten zur Verfügung.

Nun wechseln wir in den allgemeinen Deklarationsabschnitt des neuen Klassenmoduls. Wir brauchen nämlich ein allgemeines DirectX- und DirectSound-Objekt. Letzteres wird später vom DirectX-Objekt abgeleitet. Da diese beiden Objekte nur innerhalb der Klasse sichtbar sein sollen deklarieren wir sie als private. Somit sind sie von Zugriffen außerhalb der Klasse geschützt.

Private DX As New DirectX7
Private DS As DirectSound 

Private Type T_DS_Buffers


SndBufferDesc As DSBUFFERDESC
SndBuffer As DirectSoundBuffer
SndFormat As WAVEFORMATEX
BufferName As String
End Type 
Private DS_Buffers() As T_DS_Buffers 
Wir deklarieren auch noch ein dynamisches Array vom benutzerdefinierten Typ T_DS_Buffers, welcher die Haupteigenschaften eines SoundBuffers aufnimmt. Weitere Erklärungen dazu aber erst in den nächsten Abschnitten.

Wie schon gesagt, wird beim Initialisieren das DirectSound-Objekt vom DirectX-Objekt abgeleitet und einige Flags gesetzt. Daraus ergibt sich folgender Funktionskopf:

Public Function InitSound(hWnd As Long, PriorityLevel As CONST_DSSCLFLAGS, Optional SoundCardGUID) As Boolean

End Function


Als hWnd wird der Handle eines Fensters übergeben. Er steht im Zusammenhang mit dem Parameter PriorityLevel, der bestimmt, welchen Zugriff DirectSound auf die Soundkarte hat. Es gibt folgende PriorityLevel:

PriorityLevel Wert Beschreibung
DSSCL_NORMAL 1 Normaler Zugriff auf das Audiogerät (=Soundkarte), andere Anwendungen haben auch Zugriff darauf.
DSSCL_PRIORITY 2 Wir haben erweiterte Zugriffsrechte und Priorität beim Zugriff auf Hardwareressourcen, die wir uns aber trotzdem noch mit anderen Anwendungen, die die gleiche Soundkarte nutzen, teilen müssen.
DSSCL_EXCLUSIVE 3 Wir haben exklusives Zugriffrecht auf das Audiogerät, andere Anwendungen haben keinen Zugriff mehr darauf.
DSSCL_WRITEPRIMARY 4 Höchstes Zugriffsrecht. Wir können direkt in den Speicher schreiben.


Der optionale Parameter SoundCardGUID sollte einen String enthalten, der wiederum die GUID der zu verwendenden Soundkarte beinhaltet. Wird der Parameter ausgelassen, wird der primäre Soundtreiber benutzt. Die Funktion gibt bei erfolgreicher Initialisierung den Wert True zurück, ansonsten False.

Public Function InitSound(hWnd As Long, PriorityLevel As CONST_DSSCLFLAGS, Optional SoundCardGUID) As Boolean

On Error GoTo ErrOut


InitSound = True
Exit Function

ErrOut:
InitSound = False

End Function


Zuerst mal wieder die Fehlerbehandlung. Hinter On Error Goto ErrOut können wir jetzt den Quelltext der Funktion schreiben.

Public Function InitSound(hWnd As Long, PriorityLevel As CONST_DSSCLFLAGS, Optional SoundCardGUID) As Boolean

On Error GoTo ErrOut

If IsMissing(SoundCardGUID) Then SoundCardGUID = ""

Set DS = DX.DirectSoundCreate(SoundCardGUID)

DS.SetCooperativeLevel hWnd, PriorityLevel

ReDim Preserve DS_Buffers(0)

InitSound = True
Exit Function

ErrOut:
InitSound = False

End Function


Zuerst überprüfen wir mit

If IsMissing(SoundCardGUID) Then SoundCardGUID = ""

ob der optionale Parameter SoundCardGUID angegeben wurde. Falls nicht wird SoundCardGUID mit einem leeren String initialisiert. So werden später Fehler bei der Erzeugung des DirectSound-Objekts vermieden, da SoundCardGUID ansonsten den Wert Nicht vorhanden enthalten würde.

Kommen wir also zur Erzeugung des DirectSound-Objekts. Dieses wird, wie schon öfter erwähnt, vom DirectX-Objekt abgeleitet.

Set DS = DX.DirectSoundCreate(SoundCardGUID)

Als Argument wird bei der Erstellung die GUID eines Soundkartentreibers übergeben. Standardmäßig ist dies der primäre Soundtreiber.

Nun ist das DirectSound-Objekt DS initialisiert. Wir müssen nur noch den Kooperationslevel mitteilen, damit DirectSound weiß, welche Zugriffsrechte es auf die Hardware hat.

DS.SetCooperativeLevel hWnd, PriorityLevel

Mit

ReDim Preserve DS_Buffers(0)

initialisieren wir auch schon mal das dynamische Array, dessen Bedeutung ich in einem späteren Abschnitt noch genauer erklären werde.

Das war's auch schon für die Initialisierung von DirectSound. Wie man sieht ist es gar nicht so kompliziert ein DirectSound-Objekt sauber zu initialisieren.

Im nächsten Abschnitt werde ich darauf eingehen wie man herausbekommt, wieviele Soundkarten installiert sind, wie man dessen Beschreibungen, Namen und GUIDs ausliest und mit der eben entwickelten Funktion InitSound verwendet.

Abschnitt 2: Soundkarteneigenschaften mit DirectSound ermitteln

Im letzten Abschnitt haben wir gelernt wie wir DirectSound initialisieren. Dabei kann man z.B. angeben, welche Soundkarte von DirectSound benutzt werden soll. Um an die GUID des zu nutzenden Audiogeräts zu gelangen, stellt uns DirectSound eine Enumerations-Schnittstelle zur Verfügung.

Damit können wir z.B. auch auslesen, wie viele Soundkarten im System installiert sind, den Namen und eine Beschreibung, und, was für die Initialisierung von DirectSound so wichtig ist, die GUID der Soundkarte.

Fangen wir erst mal mit der Anzahl der installierten Soundkarten an.

Wir öffnen das Klassenmodul cls_SndEngine.cls und legen dort eine neue Methode an.

Public Function GetSoundCardCount() As Long 

End Function 
DirectSound stellt uns, wie bereits erwähnt, die Schnittstelle DirectSoundEnum zur Verfügung. Damit können wir verschiedene Eigenschaften der Hardware abfragen.

Public Function GetSoundCardCount() As Long 

Dim dsEnum As DirectSoundEnum 

Set dsEnum = DX.GetDSEnum
GetSoundCardCount = dsEnum.GetCount 

End Function 
Zuerst die Deklaration der DirectSound-Aufzählungsschnittstelle dsEnum As DirectSoundEnum. Diese initialisieren wir, indem wir die Methode GetDSEnum des DirectX-Objekts aufrufen. Damit befinden sich alle Soundkarteneigenschaften abfragebereit in der Variable dsEnum. Diese stellt uns die Methode GetCount zur Verfügung, die ermittelt, wie viele Soundkarten installiert sind (bzw. wie viele Treiber verfügbar sind).

Das ist aber noch längst nicht alles, was uns das Objekt DirectSoundEnum bieten kann! Wir können zum Beispiel den Namen einer beliebigen Soundkarte abfragen.

Hinweis: Der Name ist hierbei meist der Dateiname der Geräte-Datei (*.VXD).

Dazu muss einfach nur dessen Index angegeben werden.

Public Function GetSoundCardName(Index As Long) As String 

Dim dsEnum As DirectSoundEnum 

Set dsEnum = DX.GetDSEnum
GetSoundCardName = dsEnum.GetName(Index) 

End Function 
Die Initialisierung des DirectSoundEnum-Objekts bleibt immer gleich. Über die GetName-Methode wird anhand des angegebenen Index der dazugehörige Soundkartenname zurückgegeben.

Leider kennen wir damit immer noch nicht den richtigen Namen (= Produktbezeichnung) der Soundkarte. Diesen kann man über die Beschreibung auslesen.

Public Function GetSoundCardDescription(Index As Long) As String 

Dim dsEnum As DirectSoundEnum 

Set dsEnum = DX.GetDSEnum
GetSoundCardDescription = dsEnum.GetDescription(Index) 

End Function 
Dies funktioniert nach dem gleichen Prinzip wie bei der vorangegangenen Methode, nur das diesmal eben die Beschreibung anstatt der Name zurückgegeben wird.

Die wichtigste Funktion des DirectSoundEnum-Objekts ist wohl die zur Ermittlung der GUID einer Soundkarte. Wenn man diese der Methode InitSound unserer Klasse als Parameter übergibt, wird die dazugehörige Soundkarte von DirectSound verwendet.

Public Function GetSoundCardGUID(Index As Long) As String 

Dim dsEnum As DirectSoundEnum 

Set dsEnum = DX.GetDSEnum
GetSoundCardGUID = dsEnum.GetGuid(Index) 

End Function 
So kann man dann z.B. wie folgt vorgehen, um unsere Klasse mit der zweiten Soundkarte zu initialisieren.

Set vbDX_SE = New vbDX_SndEngine 

If vbDX_SE.GetSoundCardCount >= 1 Then 

For i = 1 To vbDX_SE.GetSoundCardCount 

msg = vbDX_SE.GetSoundCardGUID(i) & vbCrLf &_ 
vbDX_SE.GetSoundCardName(i) & vbCrLf & _ 
vbDX_SE.GetSoundCardDescription(i) 

MsgBox msg 

Next i 

End If 

vbDX_SE.InitSound Me.hWnd, DSSCL_NORMAL, vbDX_SE.GetSoundCardGUID(2) 
Zuerst erzeugen wir ein Objekt unserer Klasse (vbDX_SE). Danach überprüfen wir, ob überhaupt eine Soundkarte vorhanden ist. Wenn ja, werden die Eigenschaften aller Soundkarten nach einander in einer MessageBox dargestellt. Die letzte Zeile bewirkt die Initialisierung der zweiten Soundkarte, da wir dessen GUID übergeben.

Im nächsten Abschnitt zeige ich, wie man eine Sounddatei lädt und den belegten Speicher nach Benutzung wieder freigibt.

Abschnitt 3: Sounddateien mit DirectSound laden

In den letzten Tutorials haben wir gelernt wie wir DirectSound initialisieren und verschiedene Eigenschaften der Soundkarte abfragen.

Bis jetzt können wir aber immer noch nichts hören. Das liegt daran, dass wir weder eine Sounddatei geladen, noch diese abgespielt haben. Dieser Abschnitt beschäftigt sich erst mal mit dem Laden einer Sounddatei. Wie der vom Sound belegte Speicher wieder freigegeben wird, erkläre ich am Ende dieses Tutorials.

Public Function CreateSoundBuffer(FileName As String, BufferName As String, StaticOrStream As Boolean) As Boolean

End Function


So sieht die Deklaration der Hilfsfunktion, die wir zum Laden einer Sounddatei entwickeln, aus. Zuerst wird der Dateiname der zu ladenden Sounddatei übergeben. Danach können wir einen beliebigen Namen für unseren Sound festlegen. Über diesen Namen können wir es nachher ansprechen und z.B. abspielen oder die Lautstärke manipulieren. StaticOrStream gibt an ob ein Static oder ein Streaming Soundbuffer erzeugt werden soll. In einem Static Buffer werden immer alle Sounddaten einer Datei abgelegt, in einen Streaming Buffer werden die Daten nach und nach eingelesen, was Speicherplatz spart. Man sollte Static Buffer am Besten für kurze Sounds, und Hintergrundmusik oder ähnlich lange Klangdateien als Streaming Buffer benutzen. Soviel also dazu. Jetzt rede ich hier die ganze Zeit von Buffern. Aber was ist das überhaupt?

DirectSound speichert Klänge in einem sogenannten Buffer. Dieser enthält die Daten der Datei und kann von uns manipuliert werden, ähnlich wie eine Surface bei DirectDraw.

Um einen solchen Buffer zu erzeugen, deklarieren wir zuerst eine Variable vom Typ DirectSoundBuffer.

Public Function CreateSoundBuffer(FileName As String, BufferName As String, StaticOrStream As Boolean) As Boolean

'Fehlerbehandlung
On Error GoTo ErrOut

'Deklaration eines Buffers
Dim SndBuffer As DirectSoundBuffer


Um jetzt noch die Eigenschaften des Buffers beschreiben zu können, benötigen wir eine DirectSound Buffer Beschreibungsvariable.

'Beschreibung des DirectSound Buffers
Dim SndBufferDesc As DSBUFFERDESC 

'Wave-Struktur
Dim WF As WAVEFORMATEX 
WAVEFORMATEX ist eine benutzerdefinierte Struktur. Damit kann man z.B. festlegen, mit wie viel Hz und Bit, und ob Stereo oder Mono-Ausgabe erfolgen soll. Die Deklaration der Struktur müssen wir allerdings nicht im allgemeinen Deklarationsabschnitt des Klassenmoduls einfügen, da diese Struktur schon innerhalb der Visual Basic 7 Type Library vorhanden ist. Das spart uns ein bisschen Arbeit.

Wir brauchen aber trotzdem noch einen benutzerdefinierten Typ. Dieser wird aber nicht von DirectX bereitgestellt. Der Typ hat folgende Struktur:

'Wir befinden uns im allgemeinen Deklarationsabschnitt

Private Type T_DS_Buffers

SndBufferDesc As DSBUFFERDESC SndBuffer As DirectSoundBuffer SndFormat As WAVEFORMATEX BufferName As String
End Type
Private DS_Buffers() As T_DS_Buffers


SndBufferDesc und SndFormat nehmen die Eigenschaften und Beschreibungen des Buffers auf. SndBuffer ist der Soundbuffer selbst. BufferName ist der Schlüssel, mit dem wir unseren Sound nach dem Laden ansprechen können, um ihn z.B. abzuspielen.

Das dynamische Array DS_Buffers() verwaltet alle erzeugten DirectSound Buffer.

Wir wenden uns nun wieder unserer Methode CreateSoundBuffer zu.

WF.nFormatTag = WAVE_FORMAT_PCM
WF.nChannels = 2
WF.lSamplesPerSec = 22050
WF.nBitsPerSample = 16
WF.nBlockAlign = (WF.nChannels * WF.nBitsPerSample) / 8
WF.lAvgBytesPerSec = WF.lSamplesPerSec * WF.nBlockAlign
WF.nSize = 0 
Wir füllen zuerst die WAVEFORMATEX-Struktur aus. Das Format ist WAVE_FORMAT_PCM und damit eine ganz normale WAVE-Datei (*.wav). Wir wählen 2 Kanäle, also Stereo-Sound, und eine Frequenz von 22 Hz bei 16 Bit aus. Den Rest der Struktur können wir durch Berechnungen ausfüllen.

Jetzt kommt die Surfacebeschreibungsvariable ins Spiel. Die durchschnittliche Anzahl der Bytes pro Sekunde muss dieser Variablen bekannt sein. Da wir das Ergebnis der Berechnung ja schon in der WAVEFORMATEX-Struktur gespeichert haben, müssen wir die entsprechende Eigenschaft nur noch der Beschreibungsvariablen zuweisen.

SndBufferDesc.lBufferBytes = WF.lAvgBytesPerSec

Mit

If StaticOrStream Then

SndBufferDesc.lFlags = DSBCAPS_CTRLPAN Or DSBCAPS_CTRLVOLUME Or DSBCAPS_CTRLFREQUENCY Or DSBCAPS_STATIC

Else

SndBufferDesc.lFlags = DSBCAPS_CTRLPAN Or DSBCAPS_CTRLVOLUME Or DSBCAPS_CTRLFREQUENCY

End If


unterscheiden wir nach Static oder Streaming Buffer. Wenn der Sound als Static-Buffer angelegt werden soll, übergeben wir der Beschreibungsvariable als Flags zu den Kontrollflags für Balance, Lautstärkenregelung und Frequenz noch DSB_STATIC. Dadurch wird für DirectSound erst ersichtlich wie der Buffer angelegt werden soll. Wenn man das Flag auslässt, geht DirectSound davon aus, das wir einen Streaming Buffer haben wollen.

Set SndBuffer = DS.CreateSoundBufferFromFile(FileName,SndBufferDesc, WF)

Mit dieser Zeile wird der Soundbuffer erzeugt. Über die CreateSoundBufferFromFile-Methode werden die Klangdaten aus der Sounddatei in den Buffer geladen. Übergeben werden dabei die Eigenschaften- und Beschreibungsvariablen.

Jetzt speichern wir die erzeugten Variablen in eine T_DS_Buffers-Variable.

Dim DSBUFF As T_DS_Buffers 

DSBUFF.BufferName = BufferName
DSBUFF.SndBufferDesc = SndBufferDesc
DSBUFF.SndFormat = WF
Set DSBUFF.SndBuffer = SndBuffer 
Diese wird über die Add_DS_Buffer-Methode unserer Klasse zum dynamischen Array hinzugefügt. Die Verwaltung unserer Buffer wird aber Thema des nächsten Abschnittes sein.

Call Add_DS_Buffer(DSBUFF) 

CreateSoundBuffer = True
Exit Function 

ErrOut: 

CreateSoundBuffer = False 

End Function 
Das Ende der Funktion wird, wie immer, von der Fehlerbehandlung dominiert.

Zu Beginn habe ich ja versprochen zu zeigen, wie man den belegten Speicherplatz eines DirectSound Buffers wieder freigibt. Dies ist ziemlich einfach, da man nur eine Zeile Code dafür braucht. Mit

Set DSBUFF.SndBuffer = Nothing

könnte man den Buffer löschen. Das wollen wir aber jetzt noch nicht, sondern erst im nächsten Abschnitt, wo ich, wie gesagt, auf die Verwaltung der Buffer innerhalb der Klasse eingehen werde.

Abschnitt 4: Verwaltung von Soundbuffern

In den letzten Tutorials haben wir Funktionen zum Initialisieren und Laden von Sounds entwickelt. In diesem Abschnitt soll es erst mal um die Entwicklung eines Grundsystems, nämlich der Verwaltung der einzelnen Sounds innerhalb der Engine gehen.

Dies bewerkstelligen wir durch Zuhilfename eines dynamischen Arrays, wie ich es auch in vorigen Tutorials schon oftmals erwähnt habe. Hier nochmal die Deklaration des benutzerdefinierten Typs und des Arrays, welche wir im Allgemeinen Deklarationsabschnitt finden.

Private Type T_DS_Buffers 

SndBufferDesc As DSBUFFERDESC
SndBuffer As DirectSoundBuffer 
SndFormat As WAVEFORMATEX 
BufferName As String
End Type 
Private DS_Buffers() As T_DS_Buffers 
SndBufferDesc und SndFormat beinhalten die Beschreibungen des Sounds, SndBuffer ist der Sound selbst und BufferName ist der Name (oder Schlüssel) über den wir verschiedene Aktionen mit einem Buffer ausführen können.

Im letzten Tutorial wurde unter anderem gezeigt, wie ein Sound geladen wird. Dabei haben wir auch eine Funktion namens Add_DS_Buffer verwendet, die den erzeugten Buffer dem dynamischen Array hinzufügt und der somit der Engine für weitere Aktionen zur Verfügung steht. Diese Funktion gilt es nun zu implementieren.

Wir erzeugen im Editorfenster also eine neue Prozedur, die wie folgt definiert wird:

Private Sub Add_DS_Buffer(Buffer As T_DS_Buffers) 

ReDim Preserve DS_Buffers(Get_DS_BufferCount + 1) 
DS_Buffers(Get_DS_BufferCount) = Buffer 

End Sub 
Die Methode ist zwar recht kompakt, wir benutzen aber auch noch die Get_DS_BufferCount-Funktion, die die Anzahl der Sounds ermittelt, die der Engine aktuell zur Verfügung stehen. Die Definition dieser Funktion wird später in diesem Tutorial besprochen. Wir wollen den übergebenen Sound (von unserem benutzerdefinierten Typ T_DS_Buffers) an das Ende des Arrays anfügen und erzeugen zuerst mit dem ReDim-Befehl ein neuen noch leeres Element im Array. Dieses bekommt den neu erzeugten Buffer zugewiesen. Damit hätten wir den Soundbuffer schon mal gespeichert.

Bevor ich jetzt aber mit der Erklärung zum Löschen des Soundbuffers fortfahre, wäre es von Vorteil, auch zu wissen, was innerhalb der Get_DS_BufferCount-Methode passiert. Diese Funktion besteht eigentlich nur aus einer einzigen Zeile Code. Mit

Get_DS_BufferCount = UBound(DS_Buffers) 
lesen wir nämlich einfach nur aus, aus wievielen Elementen das Array, welches die Soundbuffer beinhaltet, besteht. Das Ergebnis wird als Integer zurückgegeben.

Aber kommen wir zurück zum Löschen. Diese Methode ist nicht ganz so einfach wie die Vorangegangenen. Zunächst einmal überlegen wir uns die Vorgehensweise. Der Prozedur wird der Name des zu löschenden Elements übergeben. Wir navigieren mit Hilfe einer Schleife durch alle Elemente und suchen diesen Namen. Wenn wir ihn gefunden haben, löschen wir das Element und verschieben alle danach hinzugefügten Elemente um 1 nach oben, damit die Lücke, die durch den Löschvorgang entstanden ist geschlossen wird. Sehen wir uns die Lösung dafür in VB an:

Private Sub Remove_DS_Buffer(BufferName As String) 

'Zählervariablen 
Dim i As Integer
Dim ii As Integer 

'Nach BufferName suchen
For i = 1 To Get_DS_BufferCount 

'Alle Buffer mit dem angegebenen Namen (BufferName) löschen
If DS_Buffers(i).BufferName = BufferName Then 
'Elemente nachrücken lassen 
For ii = i To Get_DS_BufferCount - 1 
DS_Buffers(ii) = DS_Buffers(ii + 1) 
Next ii 
'Den zu löschenden Buffer aus dem Speicher entfernen
Set DS_Buffers(Get_DS_BufferCount).SndBuffer = Nothing 

'Die Anzahl der Elemente um 1 verringern
ReDim Preserve DS_Buffers(Get_DS_BufferCount - 1) 

End If 
Next i 
End Sub 
Das war's auch schon. Jetzt haben wir ein Grundsystem zur Verwaltung der einzelnen Soundbuffer. Diese Methoden können wir nun bequem aus jeder anderen Funktion aufrufen und uns somit einige Tipparbeit bei der Entwicklung der weiteren Funktionen unserer Klasse sparen.

Im nächsten Abschnitt werde ich darauf eingehen, wie man die im Speicher bzw. Soundbuffer befindlichen Klangdaten manipuliert, also z.B. die Frequenz, Lautstärke oder Balance ändert.

Abschnitt 5: Soundbuffer manipulieren

Im letzten Tutorial haben wir die Verwaltung von Soundbuffern besprochen. Diese Grundlagen benötigen wir nun, um in diesem Tutorial die Soundbuffer innerhalb unserer Klasse nach Belieben zu manipulieren. Wir werden also intensiv von den dort erstellten Funktionen Gebrauch machen.

Zuerst müssen wir natürlich wissen, welche Fähigkeiten wir der Klasse hinzufügen möchten, d.h. was wir unter der Manipulation von Soundbuffern verstehen. Folgende Eigenschaften eines Soundbuffers werden wir in diesem Tutorial bearbeiten:
  1. Lautstärke
  2. Balance
  3. Frequenz
  4. Cursorposition
Für jede der aufgeführten Eigenschaften werden wir unserer Klasse zwei Methoden spendieren, eine zum Setzen der Eigenschaft und eine zum Abfragen. Fangen wir also mit der Lautstärke an.

Zuerst wollen wir besprechen, wie man die Lautstärke setzt. Dazu benötigen wir eine neue Funktion:

Public Function SetBufferVolume(BufferName As String, Volume As Long) As Boolean

End Function


Übergeben wird der Funktion der Soundbuffer, dessen Lautstärke wir manipulieren wollen, und als zweiten Parameter natürlich, die zu setzende Lautstärke.

Sehen wir uns mal den ersten Teil des Funktionsrumpfes an:

Dim i As Integer
Dim BufferIndex As Integer 

On Error GoTo ErrOut 

For i = 1 To Get_DS_BufferCount 

If DS_Buffers(i).BufferName = BufferName Then 

BufferIndex = i
Exit For 

End If 

Next i 
Hier geht es erst mal darum, den übergebenen Soundbuffer zu finden. Dazu gehen wir in einer Schleife alle Elemente des DS_Buffers Arrays durch, in dem ja die einzelnen Soundbuffer gespeichert sind. Stimmt der Name des aktuellen Elements mit dem gesuchten Buffernamen überein, haben wir den gesuchten Buffer gefunden und speichern seinen Index in der Variablen BufferIndex, damit wir sie im zweiten Teil der Funktion weiter verwenden können.

Im zweiten Teil der Funktion werden wir die gewünschten Veränderungen am Soundbuffer durchführen. Sehen wir uns das mal an:

Volume = Volume - &H2710& '10000 

DS_Buffers(BufferIndex).SndBuffer.SetVolume Volume 

SetBufferVolume = True
Exit Function 

ErrOut: 

SetBufferVolume = False 
Zuerst fällt ins Auge, dass wir den übergebenen Volume-Wert, der für die Steuerung der Lautstärke wichtig ist, vor Aufruf der eigentlichen DirectSound-Methode noch verändern. Denn eigentlich erwartet die DirectSound einen Wert zwischen -10.000 und 0 für die Lautstärke. Dies leuchtet aber nicht jedem sofort ein, und so ist es für den Nutzer der Funktion, der dessen Implementierung nicht kennt, schlecht nachvollziehbar. Deshalb wird der Wert so umgerechnet, dass man der Funktion nur noch Werte zwischen 0 und 10.000 übergeben muss. 0 ist dabei unhörbar leise, 10.000 macht den meisten Krach.

Mit

DS_Buffers(BufferIndex).SndBuffer.SetVolume Volume

greifen wir über das Soundbuffer-Array direkt auf die SetVolume-Methode des im ersten Teil ermittelten Soundbuffers zu. Diese erwartet nur einen Parameter, nämlich die zu setzenden Lautstärke. Diese haben wir in der Variablen Volume gespeichert.

Die obligatorische Fehlerbehandlung darf zum Schluss natürlich auch nicht fehlen. Das war dann schon die Funktion; jetzt sind wir tatsächlich in der Lage einem beliebigem Soundbuffer eine bestimmte Lautstärke zu zu weisen.

Wir bleiben vorerst beim Setzen von Eigenschaften. Weiter geht es nämlich mit der Balance. Diese gibt an wie laut ein Sound auf welchem Lautsprecher (links oder rechts) ausgegeben werden darf.

Public Function SetBufferPan(BufferName As String, Pan As Long) As Boolean

End Function


Der Funktionskopf ähnelt dem der vorangegangenen Lautstärke-Funktion schon sehr. Der einzige Unterschied ist, dass die Funktion (natürlich) anders heißt und statt der Lautstärke die Balance erwartet (im englischen als Pan bezeichnet).

Der erste Teil der Funktion stimmt auch tatsächlich mit dem ersten Teil der Lautsprecher-Funktion überein. Deshalb spare ich mir mal, diese hier noch einmal aufzuführen, da der Source ja schon weiter oben zu finden ist.

Der zweite Teil ruft wieder eine DirectSound-Methode des gefundenen Buffers auf, diesmal heißt diese allerdings SetPan und erwartet wiederum nur einen Parameter, nämlich die zu setzende Balance.

Hier das zuständige Codefragment:

DS_Buffers(BufferIndex).SndBuffer.SetPan Pan 

SetBufferPan = True
Exit Function 

ErrOut: 

SetBufferPan = False 
Die Variable enthält dabei die zu setzende Balance. Diese liegt im Bereich von -10.000 (links) bis 10.000 (rechts).

Die dritte Funktion, die sich um die Frequenz des Soundbuffers kümmert, stimmt vom Aufbau her mit den ersten beiden Funktionen überein.

Public Function SetBufferFrequency(BufferName As String, Freq As Long) As Boolean

End Function


Der Wertebereich der Variablen Freq sollte hierbei zwischen 100 (langsam) und 100.000 (schnell) liegen. Die zuständige DirectX-Methode heißt SetFrequency und erwartet, wie könnte es denn auch anders sein, unsere Variable Freq.

Gebräuchliche Frequenzen für Klangdateien sind:

8.000 Hz
11.025 Hz
12.000 Hz
16.000 Hz
22.050 Hz
24.000 Hz
32.000 Hz
44.100 Hz
48.000 Hz


Zum Abschluss darf natürlich auch nicht der beschriebene Codeausschnitt fehlen:

DS_Buffers(BufferIndex).SndBuffer.SetFrequency Freq 

SetBufferFrequency = True
Exit Function 


ErrOut: 

SetBufferFrequency = False 
So, dann bleibt also nur noch eine Funktion übrig, nämlich die zum Setzen der Cursorposition. Diese gibt an, ab welcher Position innerhalb des Buffers Klangdaten z.B. abgespielt werden sollen.

Der Funktionskopf kommt uns auch hier wieder recht bekannt vor:

Public Function SetBufferCurPos(BufferName As String, Pos As Long) As Boolean

End Function


Der Ablauf ist auch der gleiche wie bei den anderen Funktionen. Folgender Quelltext dokumentiert die Funktionsweise:

DS_Buffers(BufferIndex).SndBuffer.SetCurrentPosition Pos

SetBufferCurPos = True
Exit Function

ErrOut:

SetBufferCurPos = False


Das sollte sich nun wirklich von selbst erklären.

Kommen wir lieber zum zweiten Teil dieses Abschnittes, nämlich dem Abfragen der gerade gesetzten Werte. Hierzu implementieren wir einfach die entsprechenden Get-Methoden.

Für die Funktion zum Abfragen der Lautstärke gilt also folgender Funktionskopf:

Public Function GetBufferVolume(BufferName As String) As Long

End Function


Zuerst suchen wir wieder den Index des gewünschten Buffers. Dies funktioniert auch innerhalb der Get-Methoden mit dem Quelltext aus den Set-Methoden. Über das Array mit dem BufferIndex selektieren wir das richtige Element, nämlich den Soundbuffer, dessen Volume-Einstellung wir abfragen möchten.

GetBufferVolume = DS_Buffers(BufferIndex).SndBuffer.GetVolume
GetBufferVolume = GetBufferVolume + 10000


Der Rückgabewert der DirectSound-Funktion ist auch gleichzeitig der Rückgabewert unserer Funktion. Bei der GetBufferVolume addieren wir allerdings ausnahmsweise 10.000 hinzu (aus oben genannten Gründen).

Für die Balance ergibt sich folgender Quelltext:

GetBufferPan = DS_Buffers(BufferIndex).SndBuffer.GetPan

Bei GetBufferFrequency das Gleiche:

GetBufferFrequency = DS_Buffers(BufferIndex).SndBuffer.GetFrequency

Und schließlich noch GetBufferCurPos: Stop! Hier ergeben sich ausnahmsweise wirklich Änderungen. Denn die DirectSound-Funktion, die wir zur aktuellen Cursorposition befragen liefert uns den benutzerdefinierten Typ DSCURSORS zurück. Deshalb deklarieren wir zusätzlich noch:

Dim DSC As DSCURSORS

Dann rufen wir im zweiten Teil unserer Funktion die DirectSound-Methode auf. Diese speichert die aktuelle Position des Cursors in der übergebenen Variablen DSC. Deshalb finden wir dort nachher auch die Cursorposition wieder, und zwar unter DSC.lPlay:

DS_Buffers(BufferIndex).SndBuffer.GetCurrentPosition DSC
GetBufferCurPos = DSC.lPlay


Damit sollten nun alle Funktionen komplett sein.

Da wir in diesem Abschnitt nun alle noch benötigten Vorraussetzungen geschaffen haben, können wir im nächsten Abschnitt nun endlich die Steuerung der Soundausgabe angehen.

Abschnitt 6: Steuerung der Soundausgabe

In diesem Tutorial soll es darum gehen, wie wir die Steuerung der Soundausgabe in unsere Klasse einbauen, also wie wir einen Soundbuffer abspielen, die Ausgabe pausieren oder stoppen.

Zu Beginn soll ein Buffer abgespielt werden. Die zuständige Funktion dafür heißt PlayBuffer und erwartet als ersten Parameter den Namen des Soundbuffers, der abgespielt werden soll. Als zweiter Parameter muss ein Flag übergeben werden, welches bestimmt, ob der Buffer nur 1x (DSBPLAY_DEFAULT) oder unendlich oft (DSBPLAY_LOOPING) wiedergegeben werden soll.

Hier erstmal die vollständige Funktion. Die Einzelheiten werde ich weiter unten erklären.

Public Function PlayBuffer(BufferName As String, 
LoopSound As Boolean) As Boolean 

Dim i As Integer
Dim BufferIndex As Integer
Dim PLAYFLAG As Integer 

On Error GoTo ErrOut 

For i = 1 To Get_DS_BufferCount 

If DS_Buffers(i).BufferName = BufferName Then 

BufferIndex = i
Exit For 

End If 

Next i 

If LoopSound Then
PLAYFLAG = DSBPLAY_LOOPING
Else
PLAYFLAG = DSBPLAY_DEFAULT
End If 

DS_Buffers(BufferIndex).SndBuffer.Play PLAYFLAG 

PlayBuffer = True
Exit Function 

ErrOut: 

PlayBuffer = False 

End Function 

Im ersten Teil der Funktion wird zunächst nach dem Index des Buffers anhand des übergebenen Buffernamens gesucht, damit wir den Soundbuffer nachher direkt ansprechen, und eindeutig innerhalb unserer Klasse identifizieren können.

Im zweiten Teil wird lediglich überprüft, wie der Sound abgespielt werden soll (1x oder unendlich) und das Ergebnis einer lokalen Flag-Variablen zugewiesen. Diese wird dann wiederum direkt an die Play-Methode des DirectSoundBuffer-Objekts übergeben.

Zuletzt wird natürlich, wie immer, die Fehlerbehandlung nicht außer Acht gelassen. Falls aus irgendeinem Grund das Abspielen des Soundbuffers nicht gelingt, gibt die Funktion False zurück, ansonsten True. Der Rückgabewert sollte auf jeden Fall bei Aufruf der Funktion überprüft, und bei False eine Fehlermeldung ausgegeben werden.

Der Aufbau der StopBuffer-Funktion sieht der vorherigen Funktion sehr ähnlich. Auch hier wird zunächst der Index des übergebenen Soundbuffers gesucht:

Public Function PauseBuffer(BufferName As String) As Boolean 

Dim i As Integer
Dim BufferIndex As Integer 

On Error GoTo ErrOut 

For i = 1 To Get_DS_BufferCount 

If DS_Buffers(i).BufferName = BufferName Then 

BufferIndex = i
Exit For 

End If 

Next i 
Haben wir den Index des gesuchten Buffers herausgefunden, so können wir direkt die Stop-Methode des betreffenden Objekts aufrufen. Dabei wird die Wiedergabe gestoppt, der Abspielcursor bleibt an der aktuellen Position stehen.

DS_Buffers(BufferIndex).SndBuffer.Stop 

PauseBuffer = True
Exit Function 

ErrOut: 

PauseBuffer = False 

End Function 
Am Anfang des Tutorials hatte ich auch eine Stop-Funktion für unsere Klasse erwähnt. Doch haben wir diese Funktion nicht eben programmiert? Nicht ganz, denn der Abspielcursor wird bei Aufruf einer Stop-Funktion normalerweise wieder auf den Anfang des Sounds gesetzt. Dies ist bei DirectSound nicht so, deshalb müssen wir dieses Feature "zu Fuß" entwickeln. Das ist aber nicht weiter kompliziert, da die StopBuffer-Methode unserer Klasse sich lediglich durch eine Zeile von der uns bereits bekannten PauseBuffer-Methode unterscheidet.

Hier aber dennoch der komplette Quelltext der StopBuffer-Methode:

Public Function StopBuffer(BufferName As String) As Boolean 

Dim i As Integer
Dim BufferIndex As Integer 

On Error GoTo ErrOut 

For i = 1 To Get_DS_BufferCount 

If DS_Buffers(i).BufferName = BufferName Then 

BufferIndex = i
Exit For 

End If 

Next i 

DS_Buffers(BufferIndex).SndBuffer.Stop
DS_Buffers(BufferIndex).SndBuffer.SetCurrentPosition 0 

StopBuffer = True
Exit Function 

ErrOut: 

StopBuffer = False 

End Function 
Die Methode SetCurrentPosition bewirkt bei Übergabe von 0, dass der Abspielcursor des Buffers wieder auf 0 gesetzt wird.

Das waren auch schon alle Funktionen zur Steuerung der Soundausgabe. Jetzt sind wir fertig und unsere Klasse steht bereit um in einem beliebigen Spieleprojekt benutzt zu werden. Viel Spaß!


[back to top]



Userdaten
User nicht eingeloggt

Gesamtranking
Werbung
Datenbankstand
Autoren:04510
Artikel:00815
Glossar:04116
News:13565
Userbeiträge:16552
Queueeinträge:06247
News Umfrage
Ihre Anforderungen an ein Online-Zeiterfassungs-Produkt?
Mobile Nutzung möglich (Ipone, Android)
Externe API Schnittstelle/Plugins dritter
Zeiterfassung meiner Mitarbeiter
Exportieren in CSV/XLS
Siehe Kommentar



[Results] | [Archiv] Votes: 1157
Comments: 0