Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Toxic

Frischling

  • »Toxic« ist der Autor dieses Themas

Beiträge: 53

Wohnort: Niedersachsen

Beruf: Ingenieur

  • Private Nachricht senden

1

02.04.2009, 17:38

Eine einfache Sound-Klasse mit dem SDL_Mixer

Eine einfache Sound-Klasse mit dem SDL_Mixer

Inhaltsverzeichnis
1.Einleitung
2.Einrichtung der Umgebung
3.Erstellung der Sound-Klasse
4.Sonstiges

1.Einleitung
Ich versuche mich heute mal im schreiben eines kleinen Tutorials. Ich will auch gleich zur Sache kommen, es geht um den SDL_Mixer. Großartig viel kann man damit zwar nicht machen, aber für den Anfang dürfte es ausreichen. Im nachfolgenden werde ich nicht zu weit ins Detail gehen, Details über die Wirkungsweise der einzelnen SDL_Mixer Funktionen findet man recht gut erklärt in der SDL_Mixer Dokumentation.


2.Einrichtung der Umgebung
Als erstes sollte man eine lauffähige SDL-Umgebung haben. Wer hier noch Probleme mit der Einrichtung des ganzen hat, ich hatte es auch.
Hier ist ein Link, ganz unten ist ein MiniTutorial zu finden welches hier bei der Einrichtung hilft. https://www.spieleprogrammierer.de/phpBB2/viewtopic.php?t=11358
Der SDL_Mixer ist als Erweiterung der SDL-Umgebung nicht in dieser vorhanden man muss also die Dateien herunterladen. Das geht am besten bei der libsdl.org und zwar hier, http://www.libsdl.org/projects/SDL_mixer/. Ich habe mir hier unter Binary/Win32 denSDL mixer-devel-1.2.8-VC8 heruntergeladen, wenn ihr Linux oder so verwendet ist das natürlich anders...Ach ja wenn man schon gerade da ist, am besten auch die SDL_Mixer Dokumentation ganz oben herunterladen.

Als nächstes öffnet ihr die zip-file und kopiert die include Dateien in den include Ordner der SDL und die lib Dateien in den lib-Ordner der SDL ganz einfach. Eine potentielle Fehlerquelle besteht darin wenn ihr eine höhere SDL-Version als 1.2.8 verwendet, es kann dann zu Ausführungsfehlern mit dem SDL_Mixer kommen, war bei mir jedenfalls so.

Als nächstes jetzt noch unter Eigenschaften sdl_mixer.lib ins Programm linken und nicht vergessen die Datei sdl_mixer.dll aus dem include Ordner noch zusätzlich in die Projektmappe zu kopieren. Dann ist alles soweit eingerichtet.


3.Erstellung der Sound-Klasse
Die Soundklasse ist darauf zugeschnitten das sie nicht seperat arbeitet sondern in ein bestehendes SDL-Projekt eingebunden wird.
Wer etwas sucht was von sich aus läuft bitte hier gucken.
http://sppro.fkrauthan.de/2008/09/12/sdl-eine-einfuehrung/4/
Zunächst jetzt die Headerdatei Sound.hpp und dann Sound.cpp.

Sound.hpp

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#ifndef SOUND_HPP
#define SOUND_HPP

#include <SDL.h>
#include <SDL_mixer.h>
#include <string>
#include <iostream>

//statt using namespace std; wurde an den einzelnen Stellen std:: verwendet


class CSound
{
public:
//Konstruktor und Destruktor

//Im Konstruktor wurden bereits Standartwerte gesetzt, bei der 

//Initialisierung von CSound können diese aber geändert werden

// so in etwa: m_pSound = new CSound(10000, AUDIO_U8, 1, 1024);

CSound(int audio_rate = 44100, Uint16 audio_format = AUDIO_S16SYS,
       int audio_channels = 2,int audio_buffers = 4096);
~CSound(); 

//Funktionen für einzelne Sounds

void LoadSound(const std::string sFilename, int Volume);
void PlaySound();

//Funktionen für die Hintergrundmusik 

void LoadMusic(const std::string sFilename, int Volume);
void PlayMusic();

private: 
int m_audio_rate;//Abspielrate, höherer Wert besserer Sound aber mehr Rechenzeit nötig

Uint16 m_audio_format;//Audio-Format

int m_audio_channels;//1=mono, 2=stereo

int m_audio_buffers;//Audio_Puffer


Mix_Chunk *m_psound;    //Instanz für den Sound

Mix_Music *m_pmusic;    //Instanz für die Hintergrundmusik 


};

#endif


Und Sound.cpp

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include "Sound.hpp"

//Konstruktor CSound()

//Aufgabe: Allgemeine Initialisierungen 

CSound::CSound(int audio_rate, Uint16 audio_format,int audio_channels,int audio_buffers)
{

   //Membervariablen setzen 

   m_audio_rate = audio_rate; 
   m_audio_format = audio_format; 
   m_audio_channels = audio_channels; 
   m_audio_buffers = audio_buffers;

   //Zeiger auf Null setzen 

   m_psound = NULL;
   m_pmusic = NULL; 

   //Anzahl der Channels setzen 

   //Je mehr gleichzeitig abgespielt werden soll desto höher, ausprobieren...

   Mix_AllocateChannels(10); 
 
}//Ende Konstruktor CSound()


//Destruktor CSound()

//Aufgabe: SDL_Mixer schließen

CSound::~CSound()
{
    Mix_FreeMusic(m_pmusic);//Zeiger für Hintergrundmusik freigeben 

    Mix_FreeChunk(m_psound);//Zeiger für Sound freigeben

    Mix_CloseAudio();//Audio schließen 


}//Ende Destruktor CSound()


////////////////////////////Toneffekte///////////////////////////////////////


//LoadSound()

//Aufgabe: Einen einzelnen Ton laden 

void CSound::LoadSound(const std::string sFilename, int Volume)
{

    //Initialisieren 

    if(Mix_OpenAudio(m_audio_rate, m_audio_format, m_audio_channels, m_audio_buffers) != 0) {
        std::cout << "Konnte Sound nicht starten: " << Mix_GetError() << std::endl;
    }

    //Sound-File laden

    m_psound = Mix_LoadWAV(sFilename.c_str ());
    if(m_psound == NULL) {
        std::cout << "Konnte Sound WAV File nicht laden: " << Mix_GetError() << std::endl;
    }

     //Lautstärke  einrichten 

    //Volume soll ein Wert zwischen 0 und 128 sein.

    Mix_VolumeChunk(m_psound,Volume); 

}//Ende LoadSound()


//PlaySound()

//Aufgabe: Einen einzelnen Ton wiedergeben

void CSound::PlaySound()
{
    //Sound abspielen

    if(Mix_PlayChannel(-1, m_psound, 0)==-1) {
        std::cout << "Konnte Sound WAV Datei nicht abspielen: " << Mix_GetError() << std::endl;
    }
}//Ende PlaySound()



////////////////////////////Hintergrundmusik///////////////////////////////////


//LoadMusic()

//Aufgabe: Eine Musikstück laden

void CSound::LoadMusic(const std::string sFilename,int Volume)
{
    //Initialisieren 

    if(Mix_OpenAudio(m_audio_rate, m_audio_format, m_audio_channels, m_audio_buffers) != 0) {
        std::cout << "Konnte Sound nicht starten: " << Mix_GetError() << std::endl;
    }

    //Hintergrundmusik 

    //Datei laden 

    m_pmusic = Mix_LoadMUS(sFilename.c_str ());
    if(m_pmusic == NULL) {
        std::cout << "Konnte Music WAV File nicht laden: " << Mix_GetError() << std::endl;
    }

    //Lautstärke für Hintergrundmusik einstellen 

       //Volume soll ein Wert zwischen 0 und 128 sein.

    Mix_VolumeMusic(Volume); 

}//Ende LoadMusic


//PlayMusic()

//Aufgabe: Eine Hintergrundmusik abspielen 

void CSound::PlayMusic()
{
    
    //Musik abspielen 

    if(Mix_PlayMusic(m_pmusic, -1) == -1) {
        std::cout << "Konnte Music WAV File nicht abspielen: " << Mix_GetError() << std::endl;
    }

}//Ende PlayMusic()


Damit das ganze nun auch wirklich läuft, muss an der Stelle wo die einzelnen Systeme der SDL aufgerufen werden noch eine Ergänzung um SDL_INIT_AUDIO hinzugefügt werden.

C-/C++-Quelltext

1
if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO ) == -1 )


Nun noch einen Zeiger auf eine Instanz der Klasse CSound erstellen.

C-/C++-Quelltext

1
2
#include "Sound.hpp"
CSound  *m_pSound;              // Sound-Instanz 

Im Konstruktor den Zeiger auf NULL setzen

C-/C++-Quelltext

1
m_pSound = NULL; 

In der Initialisierung Speicherbereich auf dem Heap reservieren und
auch gleich alles soweit laden

C-/C++-Quelltext

1
2
m_pSound = new CSound;
m_pSound->LoadMusic("ACDC/thunderstruck.wav",100);

An der gewünschten Stelle das ganze starten

C-/C++-Quelltext

1
m_pSound->PlayMusic();

Und am Schluss nicht vergessen den Speicherbereich wieder freizugeben

C-/C++-Quelltext

1
 delete (m_pSound);

Das war's, möchte man einen einzelnen Toneffekt abspielen läuft dieses im Grunde genommen genauso ab. Nur mit dem Unterschied das es bei einer Hintergrundmusik eine Wiederholungsschleife gibt und bei einem einzelnen Toneffekt eben nicht.

4.Sonstiges
Insbesondere bei Toneffekten bietet es sich natürlich an diese nicht selber zu erstellen, sondern wo zu laden. Es gibt dafür haufenweise Internetseiten, diese hier habe ich verwendet.
http://www.stonewashed.net/sfx.html
Manchmal bietet es sich an die Töne zu kürzen, insbesondere bei der Hintergrundmusik kann man sehr simple Kompositionen leicht so schneiden das man den Neubeginn des Stückes nicht mehr hören kann.
Das Wave-Pad ist hierfür eine gute Wahl.
http://www.chip.de/downloads/WavePad_28979806.html
Laut SDL_Mixer Doku ist es möglich unter bestimmten Voraussetzungen die Hintergrundmusik auch als mp3 abzuspielen. Was aber auch geht ist das man den mp3-2-wav converter verwendet.
http://www.netzwelt.de/software/2418-mp3-2-wav-converter.html


Ich hoffe mein Tutorial war nicht zu lang, aber es hat sich angeboten ein Kochrezept draus zu machen da ich mich selber gerade frisch in den SDL_Mixer eingearbeitet habe. Viel Spaß damit :D

MFG Toxic
Wenn Architekten ihre Häuser so bauen würden wie Programmierer ihre Programme, könnte ein einziger Specht ganze Städte zerstören !

drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

2

04.04.2009, 13:19

Hi.
Das Tutorial sieht aufgeräumt und anständig aus. Allerdings gibt es am Code noch ein paar Mängel, auf die du (unbedingt) achten solltest.

Das hier:

Zitat

C-/C++-Quelltext

1
using namespace std; 

geht in einer Bibliotheken Header schon mal gar nicht. Benutz den Namensraumauflöser std:: , da du sonst allen Benutzer den gesamten Namensraum ausleerst, wenn er deinen Header included und das ist nicht die Idee von namespaces.

Zitat

C-/C++-Quelltext

1
2
3
4
5
  if (m_pSound != NULL)
  {
    delete (m_pSound);
    m_pSound = NULL;
  } 


Hier reicht das hier:

C-/C++-Quelltext

1
 delete (m_pSound);


Dann solltest du dir mal die Initialisierungsliste anschauen. (google mal)

Dann setzt du beim laden ja ein paar Konstanten. Das würde ich eher so machen, dass man diese Werten im Konstruktor Defaultinitialisiert werden und bei Bedarf verändert werden können. (ist praktischer, wenn jemand einmal etwas anderes machen will in Bezug auf diese Werte und es schränkt die Standard Benutzung überhaupt nichts ein.)

Ich kenne SDL_Mixer jetzt nicht, aber wenn du etwas lädst, dann musst du da doch normalerweise ja auch wieder freigeben.. Das sehe ich in deinem Code nicht. (da würde es sich eigenen eine RAII Klasse draus zu machen und den Destruktor zu benutzen, dann wäre das ganze gesamthaft auch stabiler).

Dann würde ich auch nicht direkt auf cout eine Fehlerausgabe machen, sondern eher Exceptions benutzen, oder eine Möglichkeit anbieten das zu ändern.

Mir ist klar, dass du noch Anfänger bist, aber schau dir mal die paar Sachen an und behalte es ev. im Hinterkopf, dass du dich dann mal damit beschäftigen kannst, wenn du Zeit hast.

Toxic

Frischling

  • »Toxic« ist der Autor dieses Themas

Beiträge: 53

Wohnort: Niedersachsen

Beruf: Ingenieur

  • Private Nachricht senden

3

05.04.2009, 14:26

Zitat

geht in einer Bibliotheken Header schon mal gar nicht. Benutz den Namensraumauflöser std:: , da du sonst allen Benutzer den gesamten Namensraum ausleerst, wenn er deinen Header included und das ist nicht die Idee von namespaces.

Ja klar das macht Sinn, ich kriege nur leider einen Fehler in sound.hpp rein wenn ich das so mache.
Die Parameterliste in den beiden Load-Funktionen wird so nicht richtig angenommen, ich muss dort scheinbar irgendwo ein std setzen, habe schon alles durchprobiert.

C-/C++-Quelltext

1
void LoadSound(const string sFilename, int Volume);


Zitat

Hier reicht das hier:

C-/C++-Quelltext

1
 delete (m_pSound);

Habe es jetzt so gemacht, weil es im Buch mit dem SDL-Game auch gemacht worden ist, aber ich habe das jetzt geändert. Fande die Stelle im Code auch nicht so toll.

Zitat

Dann setzt du beim laden ja ein paar Konstanten.

Ja das stimmt, ist ein wenig unflexibel...Ich habe das jetzt mit ein paar Standartparameterwerten im Konstruktor gelösst. Möchte man andere Werte haben kann man sie bei der Erstellung mit angeben.

C-/C++-Quelltext

1
CSound(int audio_rate = 44100, Uint16 audio_format = AUDIO_S16SYS, int audio_channels = 2,int audio_buffers = 4096);


Zitat

Ich kenne SDL_Mixer jetzt nicht, aber wenn du etwas lädst, dann musst du da doch normalerweise ja auch wieder freigeben..

Ja das habe ich in der Tat vergessen, aber im Destrukter steht jetzt folgende Codezeile.

C-/C++-Quelltext

1
Mix_CloseAudio();


Zitat

Dann würde ich auch nicht direkt auf cout eine Fehlerausgabe machen, sondern eher Exceptions benutzen, oder eine Möglichkeit anbieten das zu ändern.
Das wäre dann mit try & catch über den Einsatz von throw, wenn ich das jetzt richtig eingeordnet habe.
Um die Klasse zu optimieren eine gute Idee, aber fürs Tutorial finde ich cout besser, ist ,,Einsteigerfreundlicher" weil jeder was damit anfangen kann.

C-/C++-Quelltext

1
Mir ist klar, dass du noch Anfänger bist, aber schau dir mal die paar Sachen an und behalte es ev. im Hinterkopf, dass du dich dann mal damit beschäftigen kannst, wenn du Zeit hast.

Vielen Dank für die Hinweise, besonders die Sache mit den Standartparameterwerten hatte ich ganz vergessen und werde das jetzt wohl noch häufiger einsetzen. Oben bei der Sache mit dem namespace bräuchte ich noch Hilfe weil ich nicht weiss wo der Fehler liegt. Ansonsten würde ich das Tutorial dann so verbessern. :D
Wenn Architekten ihre Häuser so bauen würden wie Programmierer ihre Programme, könnte ein einziger Specht ganze Städte zerstören !

drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

4

05.04.2009, 14:45

string ist eine Klasse aus der Standardbibliothek, also:

C-/C++-Quelltext

1
void LoadSound(const std::string sFilename, int Volume);


Ja, Fehlerbehandlung müsste nichteinmal rein in ein Tutorial. Von dem her hast du schon recht. Wollts nur gesagt haben. ;)

n0_0ne

1x Contest-Sieger

  • Private Nachricht senden

5

05.04.2009, 14:54

Wieso reicht denn das delete? Die Abfrage, ob der Zeiger NULL ist, ist egal, NULL kann man ja ohne Bedenken löschen... nur sollte man den Zeiger nach delete nicht dann wenigstens auf NULL setzen?

Anonymous

unregistriert

6

05.04.2009, 15:10

Also es ist immer hilfreich mal auf NULL zu prüfen und auf NULL zu setzen aber pflicht ist es nicht. Es kommt auch auf die Implementierung der Runtime an, ich habe hier z. B. eine runtime wo es ordentlich knallt wenn man einen bereits freigegebenen Zeiger freigibt.

Ein cmp kostet nicht die Welt, also sollte man auch nicht drauf verzichten bei größeren Projekten. Außerdem kann man mit einem cmp auf NULL auch noch gucken ob man Murks gecodet hat, also Speicher freigeben der schon freigegeben ist.

Was mich wohl stört ist bei der Parameterübergabe dieses "const std::string xyz".Warum ist das keine konstante Referenz? So ist das const doch eigentlich eher nonsens. Auch die ungarische Notation hat meiner Meinung nach nichts in C++ zu suchen - nicht mal in C. Wenn ich schon dieses "s" für String lese bekomme ich es zuviel, aber bei "int" kein "n" schreiben.

drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

7

05.04.2009, 15:26

Das 0-setzen ist nicht nötig, wenn das im dtor geschieht, weil danach sicher kein Speicher mehr regulär angefasst werden darf/kann.

Wenn man natürlich eine Variante hat, wo man den Speicher mehrmals freigeben und wieder laden kann, ist das 0-setzen sicher praktisch. Dann kann man auf das Überprüfen auch verzichten, da der Zeiger entweder gültig, oder 0 ist.

Zitat

Es kommt auch auf die Implementierung der Runtime an, ich habe hier z. B. eine runtime wo es ordentlich knallt wenn man einen bereits freigegebenen Zeiger freigibt.

Das sind imo aber 2 verschieden paar Schuhe. Das erneute freigeben eines Speicherbereiches, der bereits freigegeben wurde ist undefiniert und das knallen ein ganz normales Verhalten. Das delete auf 0 ist aber definiert, dass es nichts ausmacht.

Anonymous

unregistriert

8

05.04.2009, 15:40

Da delete 0 ist definiert, drum sollte man immer nach dem delete mal "nullen" ;)

Toxic

Frischling

  • »Toxic« ist der Autor dieses Themas

Beiträge: 53

Wohnort: Niedersachsen

Beruf: Ingenieur

  • Private Nachricht senden

9

05.04.2009, 15:46

Ok es klappt, ich konnte jetzt den namespace std rausnehmen, werde mir das ganze fürs nächste mal merken.

Dann überarbeite ich jetzt das Tutorial noch ein wenig, die Sache mit Try&Catch lasse ich im Tutorial raus werde aber wohl bei Gelegenheit mal versuchen das in meiner Sound-Klasse das zu ändern.
Wenn Architekten ihre Häuser so bauen würden wie Programmierer ihre Programme, könnte ein einziger Specht ganze Städte zerstören !

drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

10

05.04.2009, 15:49

Zitat von »"unsigned long"«

Da delete 0 ist definiert, drum sollte man immer nach dem delete mal "nullen" ;)

Ausser man macht das im dtor. Da besteht keine Gefahr, dass der Zeiger nochmals benutzt wird.. (Ausser man ist so gescheit und macht das im dtor selbst nocheinmal, aber dann ist Hopfen und Malz verloren. :))

Werbeanzeige