Darstellen eines Klanges mit der SDL-Bibliothek unter Linux / X

Das von der mittlerweilen insolventen Softwarefirma Loki mit Hilfe vieler Entwickler auf der ganzen Welt hergestellte Simple Direct Media Layer eignet sich sehr gut für einfache grafische Aufgaben, und unterstützt dabei nicht nur Linux und den X-Server, sondern auch Windows, BeOS und Apple-Systeme. Dieses Dokument soll eine kleine Anleitung für die Verwendung von einfachen Grafiken am Beispiel einer Klangstruktur sein. Dabei verwende ich die Biblitheken SDL und SDL_gfx in C++-Programmen.

1) Benötigte Programmpakete

Zunächst einmal benötigt man die SDL-Programmbibliotheken, die bei der meisten Distributionen inbegriffen und oft schon standartmäßig installiert sind. Außerdem werden - da man ja eigene Programme kompilieren will - die Headerdateien (im Paket SDL-devel enthalten) benötigt, sowie die Bibliotheken und Header des Zusatzmoduls SDL_gfx für die geometrischen Grafiken. Erfahrungsgemäß ist der Download der Quellpakete von [1] und der SDL_gfx-Pakete von [2] der beste Weg. Der nachfolgende Installationsvorgang nimmt zwar mehr Zeit in Anspruch, ist aber meist nicht schwieriger und die erzeugte Version ist aktuell und am richtigen Platz im Dateisystem, was bei distributionsabhängigen Paketen unter Umständen nicht der Fall ist. Die Installation erfolgt nach entpacken durch die Standart-Installationsroutinen:

   ./configure
   make
   make install

Wobei der letzte Schritt als Superuser (root) durchgeführt werden muss und ein nachträgliches make clean überflüssige Dateien löscht. Die SDL selbst muss vor SDL_gfx installiert werden. Für das Beispielprogramm benötigen Sie fmsynth von [3], die Installation erfolgt durch ein einfaches make.

2) Ein Fenster öffnen

Das einfachste grafische Programm ist ein Fenster, dass sich öffnen und wieder schließen lässt. Solch ein Programm ist ohne große Schwierigkeiten zu verwirklichen:

#include <SDL.h>            //Hauptheader der SDL-Library
int main() {
SDL_Init(SDL_INIT_VIDEO);   //das Video-Modul initialisieren
SDL_Init(SDL_INIT_TIMER);   //das Timer-Modul initialisieren
SDL_Surface *screen;        //Oberfläche eines Fensters
/* Fenster mit dem surface screen,
 * den Maßen 640*480, der Farbtiefe 16 bit öffnen
 * SDL_ANYFORMAT = Wenn das nicht geht,
 * wird ein anderes Format verwendet */
screen = SDL_SetVideoMode(640, 480, 16,
         SDL_SWSURFACE | SDL_ANYFORMAT);
SDL_Event event;
// Schleife, die auf das Loslassen einer Taste wartet
while (SDL_WaitEvent(&event) >= 0) {
    switch (event.type) {
        case SDL_KEYUP: {
            SDL_Quit(); //SDL beenden (und Fenster schließen)
            return 0;
        }
    }
}
return 0;
}

Eine weitere Schwierigkeit stellt das Kompilieren von SDL-Programmen dar. G++ müssen die zusätzlichen Bibliotheken und Header mitgeteilt werden. Daher sieht der Befehl so aus:

g++ -o sdl1 sdl1.cpp -L/usr/local/lib -Wl,-rpath,/usr/local/lib
-lSDL -lpthread -I/usr/local/include -I/usr/local/include/SDL

Es empfiehlt sich sehr, ein Shell-Script zu erstellen, um den Befehl nicht jedes Mal neu eintippen zu müssen. Dazu schreibt man den oben stehenden Befehl in eine einfache Textdatei und macht diese mit chmod 777 ausführbar.

3) Grafik mit SDL_gfx

Um nun etwas Farbe und Struktur in unser schwarzes Fenster zu bringen, kann man die Funktionen von SDL_gfx verwenden. Dabei nehme ich hier als Beispiel ein in Form eines Arrays von Char-Variablen (-128 - 128) vorliegendes Oszillogramm eines Klanges. So ein Oszillogramm kann man mit Hilfe meines Programmpaketes fmsynth von [3] erzeugen. Ich erkläre hier das Programm xfmdisplay aus dieser Sammlung. Es ist nicht schwierig, diese Werte mit der Headerdatei fmifstream.h aus einer der mitgelieferten Dateien auszulesen, man kann die Werte natürlich auch anders erzeugen. Ausgangssituation: Die benötigten Header (inklusive SDL_gfxPrimitives.h) sind eingebunden, die Werte liegen in einem Array von Chars vor, die Anzahl der Werte, sowie die Pixelbreiten des Fensters in Integern, SDL_VIDEO und SDL_TIMER sind initialisiert und ein Fenster ist geöffnet. Aufgabe: Das Programm soll die Werte mit einem Faktor (y_faktor) multiplizieren, so dass der Maximalwert der y-Achse (y) nicht überschritten wird. Dann soll es von Wert zu Wert im Array gehen, und immer zwischen zwei benachbarten Werten mit der Funktion lineRBGA(surface, x1, y1, x2, y2,

Rotanteil, Gruenanteil, Blauanteil, Hellichkeit) eine Linie ziehen, wobei

auch hier der maximale Wert der x-Achse (x) nicht überschritten werden darf. Listing:

   int xold;
   int yold = 0;
   int y_recent;
   float x_recent = 0.0;
   float y_faktor;
   y_faktor = float(y) / 256.0;
   xold =int(x_recent + x / f.vlen);
   if((yold = int(float(f.val[0]) * float(y_faktor))) < 0)
       yold = yold + y;
   for(i = 0; i < f.vlen; i++) {
       x_recent = x_recent + float(x) / f.vlen;
       if((y_recent = int(f.val[i] * y_faktor)) < 0)
           y_recent = y_recent + y;
       lineRGBA(screen,int(x_recent),y-y_recent,
                xold,y-yold,128,255,0,255);
       xold = int(x_recent);
       yold = y_recent;
   }

Man muss dabei sehr darauf achten, dass bei manchen Rechnungen mit Dezimalzahlen, bei anderen mit ganzen Zahlen gerechnet wird, und diese teilweise unter Null abrutschen. Auf dem Programmfenster wird erst dann etwas erscheinen, wenn man das Surface neu zeichnet:

   SDL_UpdateRect(screen, 0, 0, 0, 0);

Das mag auf den ersten Blick nur als zusätzlicher Aufwand erscheinen, hat aber

durchaus Vorteile. Die Funktion SDL_UpdateRect(surface, x1, y1, x2, y2);

kann zum Beispiel für Fading-Effekte genutzt werden. Es können einzelne Pixel aufgefrischt oder Umblenden ohne große Schwierigkeiten implementiert werden. Zum Kompilieren muss noch die SDL_gfx-Bibliothek hinzugefügt werden, so dass der Befehl folgendermaßen aussieht:

g++ -o xfmdisplay xfmdisplay.cpp fmifstream.cpp -L/usr/local/lib
-Wl,-rpath,/usr/local/lib -lSDL -lSDL_gfx -lpthread -I/usr/local/include
-I/usr/local/include/SDL

4) Komplettes Listing

#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <SDL.h>
#include <SDL_gfxPrimitives.h>
#include "include/fmifstream.h"
int main(int argc, char *argv[]) {
cout << "Frequenzmuster-Anzeiger" << endl;
if(argc < 2 || argc > 4) {
    cout << "Syntax:  " << argv[0]
         << " [dateiname] ([x] [y])" << endl;
    return 0;
}
int i ;
Fmdata f(argv[1]);
int x;
int y;
int xold;
int yold = 0;
int y_recent;
float x_recent = 0.0;
float y_faktor;
if(f.error) {
    cerr << "Fehler beim Lesen der Datei!" << endl;
    return -1;
}
cout << "Lesen der Datei " << argv[1] << " erfolgreich."

     << endl << "Modus:\t\t" << f.mode << endl << "Bezeichnung:\t"
     << f.name << endl << "Bytes/Wert:\t" << f.bytes << endl;
if(argc < 3) {
    cout << "Bitte x-Achsen-Pixel eingeben:  ";
    cin >> x;
}
else {
    x = atoi(argv[2]);
    cout << "x:\t\t" << x << endl;
}
if(argc < 4) {
    cout << "Bitte y-Achsen-Pixel eingeben:  ";
    cin >> y;
}
else {
    y = atoi(argv[3]);
    cout << "y:\t\t" << y << endl;
}
if(y == 0 || y == 0) {
    cerr << "Fehler bei Eingabe der Daten." << endl;
    return -1;
}
SDL_Init(SDL_INIT_VIDEO);
SDL_Init(SDL_INIT_TIMER);
SDL_Surface *screen;
screen = SDL_SetVideoMode(x, y, 16, SDL_SWSURFACE | SDL_ANYFORMAT);
SDL_WM_SetCaption(argv[1],argv[1]); // Fenstertitel
y_faktor = float(y) / 256.0;
xold = int(x_recent + x / f.vlen);
if((yold = int(float(f.val[0]) * float(y_faktor))) < 0)
    yold = yold + y;
for(i = 0; i < f.vlen; i++) {
    x_recent = x_recent + float(x) / f.vlen;
    if((y_recent = int(f.val[i] * y_faktor)) < 0)
        y_recent = y_recent + y;
    lineRGBA(screen,int(x_recent),y-y_recent,xold,y-yold,128,255,0,255);
    xold = int(x_recent); yold = y_recent;
}
SDL_UpdateRect(screen, 0, 0, 0, 0);
SDL_Event event;
while (SDL_WaitEvent(&event) >= 0) {
    switch (event.type) {
        case SDL_KEYDOWN: {

            SDL_Quit();
            return 0;
        }
    }
}
return 0;
}

Quellen:

[1] http://libsdl.org
[2] http://ferzkopp.net/Software.htm
[3] http://sourceforge.net/projects/fmsynth

Daniel Grün <daniel_gruen@web.de>, 22.04.2002